前言
现在Vue的ssr方案,在我了解到的有如下几种:
- vue-server-renderer服务端渲染
- prerender-spa-plugin客户端的静态页面生成
- Nuxt.js 一个vue的服务端渲染框架,容易上手。
本文主要参考vue官网的ssr文档,研究学习,其中代码比较碎片,所以自己整理出来并且运行成功。
vue ssr官网文档
package.json
{
"name": "ssr-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"server": "webpack --config ./build/webpack.server.conf.js",
"client": "webpack --config ./build/webpack.client.conf.js"
},
"author": "lanfeng",
"license": "ISC",
"dependencies": {
"babel-polyfill": "^6.26.0",
"express": "^4.16.3",
"vue": "^2.5.17",
"vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.17"
},
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^1.0.0",
"style-loader": "^0.23.0",
"vue-loader": "^11.1.4", // 这个地方有一个坑,最新版本是15以上,刚开始用的时候生成的js好像有些问题,以后再验证怎么解决
"vue-template-compiler": "^2.5.17",
"webpack": "^3.6.0",
"webpack-merge": "^4.1.4",
"webpack-node-externals": "^1.7.2"
}
}
项目结构
build&entry
在ssr指南中,主要是构建配置这一块代码有小部分缺失,所以在这里主要指出配置模块详细代码
webpack.base.config.js
module.exports = {
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [],
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
webpack.server.conf.js
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const path = require('path');
const root= path.resolve(__dirname, '..');
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: path.join(root, 'entry/entry-server.js'),
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
output: {
libraryTarget: 'commonjs2',
path: path.join(root, 'dist'),
filename: 'bundle.server.js'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
// 并生成较小的 bundle 文件。
externals: nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: /\.css$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
webpack.client.conf.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const path = require('path');
const root= path.resolve(__dirname, '..');
module.exports = merge(baseConfig, {
entry: path.join(root, 'entry/entry-client.js'),
output: {
path: path.join(root, 'dist'),
filename: 'bundle.client.js'
},
plugins: [
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
// 这也为你的 应用程序/vendor 代码提供了更好的缓存。
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
filename: 'manifest.js',
minChunks: Infinity
}),
// 此插件在输出目录中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
entry-server.js
/* entry-server.js */
import { createApp } from '../src/app'
export default context => {
return new Promise((resolve, reject) => {
const {app, router} = createApp()
// 更改路由
router.push(context.url)
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
const matchedComponents = app.$router.getMatchedComponents()
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) { return reject({code: 404}) }
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app)
}, reject)
})
}
entry-client.js
import { createApp } from '../src/app'
// 客户端特定引导逻辑……
const {app} = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
基本上配置就是这样子了。其他就可以参考官方的代码例子。构建这块就没有问题,最后就是server.js
server.js
/* server.js */
const path = require('path')
const express = require('express')
const app = express()
const { createBundleRenderer } = require('vue-server-renderer')
// 创建renderer
const template = require('fs').readFileSync('./index.ssr.html', 'utf-8')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json') // 这个可以动态将生成的js文件渲染到html模版中
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false, // 推荐
template: template,
clientManifest: clientManifest
})
app.use(express.static(path.join(__dirname, 'dist')))
// 响应路由请求
app.get('*', (req, res) => {
const context = { url: req.url }
// 创建vue实例,传入请求路由信息
renderer.renderToString(context, (err, html) => {
if (err) {
return res.state(500).end('运行时错误')
}
res.send(html)
})
})
// 服务器监听地址
app.listen(8099, () => {
console.log('服务器已启动!')
})
总结
基本上到这个地方关键性构建以及服务模块代码补充完成。一个简单的基于vue-server-renderer例子就可以运行起来。