再看 entry-server.js,它只需返回 App.vue 的实例。
// entry-server.js export default function createApp() { const app = new Vue({ render: h => h(App) }); return app; };
entry-server.js 与 entry-client.js 这两个入口主要区别如下:
- entry-client.js 在浏览器端执行,所以需要指定 el 并且显式调用 $mount 方法,以启动浏览器的渲染。
- entry-server.js 在服务端被调用,因此需要导出为一个函数。
2. 拆分 Webpack 打包配置
在第一步中,由于只有 app.js 一个入口,只需要一份 Webpack 配置文件。现在有两个入口了,自然就需要两份 Webpack 配置文件:webpack.server.conf.js 和 webpack.client.conf.js,它们的公共部分抽象成 webpack.base.conf.js。
关于 webpack.server.conf.js,有两个注意点:
- libraryTarget: 'commonjs2' → 因为服务器是 Node,所以必须按照 commonjs 规范打包才能被服务器调用。
- target: 'node' → 指定 Node 环境,避免非 Node 环境特定 API 报错,如 document 等。
3. 编写服务端渲染主体逻辑
Vue SSR 依赖于包 vue-server-render,它的调用支持两种入口格式:createRenderer 和 createBundleRenderer,前者以 Vue 组件为入口,后者以打包后的 JS 文件为入口,本文采取后者。
// server.js 服务端渲染主体逻辑 // dist/server.js 就是以 entry-server.js 为入口打包出来的 JS const bundle = fs.readFileSync(path.resolve(__dirname, 'dist/server.js'), 'utf-8'); const renderer = require('vue-server-renderer').createBundleRenderer(bundle, { template: fs.readFileSync(path.resolve(__dirname, 'dist/index.ssr.html'), 'utf-8') }); server.get('/index', (req, res) => { renderer.renderToString((err, html) => { if (err) { console.error(err); res.status(500).end('服务器内部错误'); return; } res.end(html); }) }); server.listen(8002, () => { console.log('后端渲染服务器启动,端口号为:8002'); });
这一步的最终渲染效果如下图所示,从图中我们可以看到,组件已经被后端成功渲染了。源码请参考这里。
第三步:后端渲染(预获取 Ajax 数据)
这是关键的一步,也是最难的一步。
假如第二步的组件各自都需要请求 Ajax 数据的话,该怎么处理呢?官方文档给我们指出了思路,我简要概括如下:
- 在开始渲染之前,预先获取所有需要的 Ajax 数据(然后存在 Vuex 的 Store 中);
- 后端渲染的时候,通过 Vuex 将获取到的 Ajax 数据分别注入到各个组件中;