一、一个最简单的vue SSR demo
1. 安装vue-server-renderer express
npm i vue-server-renderer express -D
2.在根目录下创建server.js
const express = require('express')
const app = express()
const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
app.get('/', (req, res) => {
const vm = new Vue({
template: `<div>hello ssr</div>`
})
renderer.renderToString(vm).then(html => {
res.send(html)
})
})
app.listen(3000, () => {
console.log('服务器启动了!')
})
3. 执行node, 访问3000端口
node ./server.js
二、在项目一的基础上修改(添加router)
1. 创建src/router/index.js
const Vue = require('vue')
const VueRouter = require('vue-router')
Vue.use(VueRouter);
const routes = [{
path: "/",
name: "home",
component: {
template: `<div>hello page</div>`
},
},
{
path: "/about",
name: "about",
component: {
template: `<div>about page</div>`
}
},
];
module.exports = function createRouter() {
return new VueRouter({
mode: "history",
routes,
});
}
2.创建src/app.js
const Vue = require('vue')
const createRouter = require('./router')
Vue.config.productionTip = false
module.exports = function createApp(context) {
const router = createRouter()
const app = new Vue({
router,
data: {
msg: 'hello ssr'
},
template: `
<div>
<h1>{{msg}}</h1>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
`
})
return {
app,
router
}
}
3. 创建src/entry-server.js
const createApp = require('./app.js')
module.exports = (context) => {
return new Promise(async(resolve, reject) => {
let { url } = context
let { app, router } = createApp(context)
router.push(url);
router.onReady(() => {
resolve(app)
}, reject)
})
}
4. 创建src/entry-client.js
const createApp = require('./app.js')
const { app, router } = createApp(context)
router.onReady(() => {
app.$mount('#app')
})
5. 在/根目录下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello, SSR</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
6.修改/server.js
const express = require('express')
const app = express()
const path = require('path')
const fs = require('fs')
const renderer = require('vue-server-renderer').createRenderer({
template: fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8')
})
const App = require('./src/entry-server.js')
app.get('*', async(req, res) => {
const { url } = req
const vm = await App({ url })
renderer.renderToString(vm).then(html => {
res.send(html)
}).catch(err => console.log(err))
})
app.listen(3000, () => {
console.log('服务器启动了!')
})
7.执行node ./server.js 访问localhost:3000,到此为止router就可以切换页面了
三、通过webpack打包
1、通过vue-cli创建项目
2、安装以下库
npm i webpack-node-externals lodash.merge cross-env express vue-server-renderer -D
3、在根目录下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello, SSR</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
4、创建/src/app.js
import Vue from 'vue'
import createRouter from './router'
import App from './App.vue'
Vue.config.productionTip = false
export default function createApp() {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return {
app,
router
}
}
5、创建/src/router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
Vue.use(VueRouter);
const routes = [{
path: "/",
name: "home",
component: HomeView
},
{
path: "/about",
name: "about",
component: AboutView
},
];
// 返回一个创建router的方法
export default function createRouter() {
return new VueRouter({
mode: "history",
routes,
});
}
6、创建/src/entry-server.js
import createApp from './app.js'
export default (context) => {
return new Promise(async(resolve, reject) => {
let { url } = context
let { app, router } = createApp(context)
router.push(url);
// 进入首屏
router.onReady(() => {
resolve(app)
}, reject)
})
}
7、创建/src/entry-client.js
import createApp from './app.js'
const { app, router } = createApp(context)
router.onReady(() => {
app.$mount('#app')
})
8、创建/server,js
const express = require('express')
const app = express()
const path = require('path')
const fs = require('fs')
const { createBundleRenderer } = require("vue-server-renderer");
const serverBundle = require('./dist/server/vue-ssr-server-bundle.json');
const clientManifest = require('./dist/client/vue-ssr-client-manifest.json');
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8'),
clientManifest
})
app.get('*', async(req, res) => {
try {
const context = {
url: req.url,
title: 'ssr test'
}
const html = await renderer.renderToString(context);
// eslint-disable-next-line no-console
console.log(html);
res.send(html);
} catch (error) {
res.status(500).send("服务器内部错误");
}
})
app.listen(3000, () => {
console.log('服务器启动了!')
})
9、修改vue.config.js
const { defineConfig } = require('@vue/cli-service')
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 = defineConfig({
transpileDependencies: true,
css: {
extract: false
},
outputDir: './dist/' + target,
configureWebpack: () => ({
entry: `./src/entry-${target}.js`,
devtool: 'source-map',
target: TARGET_NODE ? 'node' : 'web',
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
externals: TARGET_NODE ?
nodeExternals({
allowlist: [/\.css$/]
}) : undefined,
optimization: {
splitChunks: TARGET_NODE ? false : undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
merge(options, {
optimizeSSR: false
})
})
}
})
10、修改package.json的打包命令
"scripts": {
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
"build": "npm run build:server && npm run build:client"
},
11、 通过npm run build进行打包
12、执行node ./server.js运行express server, 访问localhost:3000