1、修改路由配置
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
// 工厂函数 每次请求返回一个Router实例
export function createRouter() {
return new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
}
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
2、修改vuex配置
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//createStore
export function createStore() {
return new Vuex.Store({
state: {
count: 99,
},
mutations: {
add(state){
state.count += 1
}
},
actions: {
},
modules: {
}
})
}
3、修改main.js
// main.js
import Vue from 'vue'
import App from './App.vue'
//引用createRouter,createStore
import { createRouter } from './router'
import { createStore } from './store'
Vue.config.productionTip = false
// 需要每次请求 放回一个vue实例(不用挂载app)
export function createApp(context) {
const router = createRouter()
const store = createStore()
const app = new Vue({
router,
store, // 挂载
context, // 用于和外的renderer交互
render: h => h(App)
})
return {app, router}
}
4、在src录下创建服务端入口(entry-server.js)
// src/entry-server.js
// 和渲染器打交道
// 创建vue实例
import { createApp } from './main'
export default context => {
const { app, router } = createApp(context)
return new Promise((resolve, reject) => {
// 跳转首屏地址
router.push(context.url)
// 等待路由就绪
router.onReady(() => {
resolve(app)
}, reject)
})
}
5、在src目录下创建客户端入口文件entry-client.js
// src/entry-client.js
import { createApp } from './main'
const { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
6、webpack配置
npm install webpack-node-externals lodash.merge -D
npm i vue vue-server-renderer -S // 必须和vue保持同一版本
7、在项目根目录下创建vue.config.js并配置
// 两个插件分别负责打包客户端和服务端
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");
// 根据传入环境变量决定入口文件和相应配置项
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
module.exports = {
css: {
extract: false
},
outputDir: './dist/'+target,
configureWebpack: () => ({
// 将 entry 指向应用程序的 server / client 文件
entry: `./src/entry-${target}.js`,
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// target设置为node使webpack以Node适用的方式处理动态导入,
// 并且还会在编译Vue组件时告知`vue-loader`输出面向服务器代码。
target: TARGET_NODE ? "node" : "web",
// 是否模拟node全局变量
node: TARGET_NODE ? undefined : false,
output: {
// 此处使用Node风格导出模块
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的打包文件。
externals: TARGET_NODE
? nodeExternals({
// 不要外置化webpack需要处理的依赖模块。
// 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 还应该将修改`global`(例如polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 服务端默认文件名为 `vue-ssr-server-bundle.json`
// 客户端默认文件名为 `vue-ssr-client-manifest.json`。
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
// cli4项目添加
if (TARGET_NODE) {
config.optimization.delete('splitChunks')
}
config.module
.rule("vue")
.use("vue-loader")
.tap(options => {
merge(options, {
optimizeSSR: false
});
});
}
};
8、脚本配置安装依赖
npm i cross-env -D
9、修改package.json配置
"scripts": {
"serve": "vue-cli-service serve",
"build": "npm run build:server & npm run build:client",
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"
},
10、修改public目录下的index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 1.删掉之前动态标签 -->
<title>{{title}}</title>
</head>
<body>
<!-- 2.把宿主元素变成一个注释 -->
<div id="app">
<!--vue-ssr-outlet-->
</div>
</body>
</html>
11、新增服务器启动文件 (根目录创建文件 server/ssr.js)
// 创建一个express实例
const express = require('express')
const app = express()
// 获取绝对地址
const resolve = dir => require('path').resolve(__dirname, dir)
// 静态文件服务
// 开发dist/client目录,关闭默认的index页面打开功能
app.use(express.static(resolve('../dist/client'), {index: false}))
// 创建渲染器
const { createBundleRenderer } = require('vue-server-renderer')
// 参数1:服务端bundle
const bundle = resolve('../dist/server/vue-ssr-server-bundle.json')
const renderer = createBundleRenderer(bundle, {
runInNewContext: false, // https://ssr.vuejs.org/zh/api/#runinnewcontext
template: require('fs').readFileSync(resolve("../public/index.html"), "utf-8"), // 宿主文件
clientManifest: require(resolve("../dist/client/vue-ssr-client-manifest.json")) // 客户端清单
})
// 只做一个件事,渲染
app.get('*', async (req, res) => {
try {
const context = {
title:'',
url: req.url
}
// 渲染: 得到html字符串
const html = await renderer.renderToString(context)
// 发送回前端
res.send(html)
} catch (error) {
res.status(500).send('服务器内部错误')
}
})
// 监听端口
app.listen(3000, () => console.log('服务器开启'))
12、打包文件
npm run build
//会创建一个dist文件目录,名下包括client和server两个文件件
13、启动服务器node
node ./server/ssr.js
1)、vue与vue-server-renderer版本一致
2)、报错:Error: [webpack-node-externals] : Option 'whitelist' is not supported. Did you mean 'allowlist' //修改vue.config.js(whitelist 改成 allowlist)
3)、在beforeCreate,created除外的生命周期以及全局的执行环境中调用特定的api前需要判断执行环境
4)、每次修改需要重新打包,重启服务器
5)、其他注意事项vue-ssr问题 - 简书