# 服务端渲染
# 介绍
服务端渲染主要解决两个问题:首屏加载和SEO。首屏加载变快其原理就是返回给用户的HTML已包含当前页面的DOM节点,页面只需要加载完成CSS文件就可以进行渲染,而SPA应用中页面的DOM节点必须加载完Vue运行时代码之后才能进行创建,最后再和CSSOM树进行结合,对页面进行渲染,这之中相差的就是JS文件的加载以及Vue创建DOM节点的时间。
# 基本用法
基本用法可以参照Vue SSR指南 (opens new window)。建议对基本用法实现一遍,然后对其他章节做一定了解。
# 构建配置
# 关于externals
以下构建配置主要介绍如何结合第三方UI库如antd进行配置,基于官方文档的构建配置 (opens new window)。
服务端配置文件中修改:
externals: nodeExternals({
whitelist: /ant-design-vue\/lib\/(.)+\/style\/css/
})
这种配置是基于使用了ant-design-vue文档中推荐的使用babel-plugin-import插件进行按需加载的情况,因为使用babel-plugin-import按需加载antd时,大概原理是这样:
import { DatePicker } from 'ant-design-vue';
=> 转换
import DatePicker from 'ant-design-vue/lib/date-picker'
import 'ant-design-vue/lib/date-picker/style/css';
这里的'ant-design-vue/lib/date-picker/style/css'
是一个js文件,并且这个是node_modules里的,如果whitelist是/\.css$/
,那么就不会处理css.js内引入的css文件,导致服务端运行失败。
# 关于CSS提取
对于CSS,最好选用提取,否则antd中的css会全部内联进html中,访问不同页面就会带来冗余,并且内联了antd的css的html文件会很大,提取还能对css文件做出更好的缓存。
需要注意:
- 服务端不进行提取,否则mini-css-extract-plugin致使服务端运行报错(document在服务端不存在)。
- 服务端不使用vue-style-loader,否则服务端仍然会将antd的css内联进html中,并且会带上link标签引入antd css,导致重复。
- 要开启css sourcemap,有了sourcemap,服务端bundle进行renderToString时才能够自动推到css文件的资源注入。
# 开发环境下服务端的处理
开发环境主要的事:文件变动时重新进行服务端webpack的打包。
const axios = require('axios')
const path = require('path')
const MemoryFS = require('memory-fs')
const webpack = require('webpack')
const serverConfig = require('@vue/cli-service/webpack.config')
const Router = require('koa-router')
const { createBundleRenderer } = require('vue-server-renderer')
const {
resolve
} = require('../utils/utils')
const ssrRouter = new Router()
const template = require('fs').readFileSync(resolve('../../public/index-server.html'), 'utf-8')
const serverCompiler = webpack(serverConfig)
const mfs = new MemoryFS()
serverCompiler.outputFileSystem = mfs
let serverBundle
serverCompiler.watch({}, () => {
const bundlePath = path.join(
serverConfig.output.path,
'vue-ssr-server-bundle.json'
)
serverBundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'))
console.log('new bundle generated')
})
ssrRouter.get('*', async ctx => {
console.log(ctx.url)
if (!serverBundle) {
ctx.body = 'wait a minute'
return
}
const context = { url: ctx.req.url }
const clientManifestResp = await axios.get(
'http://localhost:8080/vue-ssr-client-manifest-legacy.json'
)
let clientManifest = clientManifestResp.data
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false,
template,
clientManifest
})
try {
const html = await renderer.renderToString(context)
ctx.body = html
} catch (err) {
ctx.body = 'no response'
}
})
module.exports = ssrRouter
serverConfig的require路径下的文件是Vue CLI提供的,具体见Vue CLI webpack配置文件 (opens new window)。这个配置文件就相当于打包时的配置文件,就能生成serverCompiler,这个comipler使用的文件系统是虚拟文件系统,也就是构建出来的文件放在内存中,这个和webpack dev server是一样的。接下来就是路由响应的逻辑,其使用的clientManifest(可见Vue SSR文档中介绍,用于想html模板自动注入资源)是来自webpack dev server中构建出来的clientManifest,这个通过axios来获取,然后服务端下注入的资源都会来自webpack dev server中,并且还有热更新的效果。
上面的clientManifest路径是'http://localhost:8080/vue-ssr-client-manifest-legacy.json'
,因此客户端配置文件的publicPath需要做出改变:
// vue.config.js
//...
module.exports = {
publicPath: isProd ? '/' : 'http://localhost:8080/',
devServer: {
port: 8080,
headers: { 'Access-Control-Allow-Origin': '*' },
historyApiFallback: {
rewrites: [
{ from: /.*/, to: '/index.html' }
]
}
}
// ...
}
上面还配置了webpack dev server的headers字段,用于处理跨域问题,因为服务端启的端口(假设:3000)和客户端不同(:8080)。
let proxy;
if (isDev) {
proxy = require('koa-proxy')
app.use(proxy({
host: 'http://localhost:8080/',
match: /(serviceworker)|(static)|(dll)|(workbox)/
}))
}
服务端在开发环境下还要设置代理,因为服务端通过webpack配置是没有处理serviceWorker、workbox相关文件的,另外static路径的文件也要代理是因为希望serviceWorker预缓存的资源不是cors类型的,关于cors类型对workbox的影响,详见workbox文档 (opens new window)。
# 最后
服务端渲染最困难的地方是对其原理的认识、webpack环境的配置,你必须对相关webpack loader和webpack打包有个清楚的认识。
← 服务端渲染 浏览器多进程和js线程 →