装依赖
vue-server-render vue服务端的渲染器 express服务端的服务器 开发模式开发
npm i vue-server-renderer express -D
server/index.js
const express = require('express')
const Vue = require('vue')
// express实例
const app = express()
// 创建Vue实例
const vm = new Vue({
data: { count: 1 },
template: `
<div>{{count}}</div>
`
})
// 创建渲染器
const renderer = require('vue-server-renderer').createRenderer()
// 服务器路由声明
app.get('/', async function (req, res) {
// renderToString是一个异步处理 用then 或async await
try {
const html = await renderer.renderToString(vm)
res.send(html)
} catch (error) {
res.status(500).send('internal Server Error')
}
})
app.listen(3000, () => {
console.log('渲染服务器成功了')
})
构建步骤
webpack根据执行环境生成server bundle和client bundle
路由 Vue-router
单页应用的页面路由,都是前端控制,后端只负责提供数据
一个简单的单页应用,使用vue-router,为了方便前后端公用路由数据,我们新建router.js 对外暴露createRouter
import Vue from 'vue'
import Router from 'vue-router'
// 页面
import Index from '@/components/Index'
import Detail from '@/component/Detail'
Vue.use(Router)
// 导出应当是Router实例工厂函数
export default createRouter() {
return new Router({
mode:'history',
routes:[
{path:'/',component:Index},
{path:'/detail',component:Detail}
]
})
}
main.js同级 app.js
// 通用文件:创建vue实例
import { createRouter } from './router'
import App from './App.vue'
import Vue from 'vue'
export function createApp (context) {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return { app, router }
}
main.js同级entry-server.js
服务端入口
import { createApp } from './app'
export default context => {
// 返回一个Promise
// 确保路由或组件准备就绪
return new Promise((resolve, reject) => {
// 创建Vue实例
const { app, router } = createApp(context)
// 跳转首屏地址
router.push(context.url)
// 路由就绪完成promise
router.onReady(() => {
resolve(app)
}, reject)
})
}
客户端的入口entry-client.js
import { createApp } from './app'
const { app, router } = createApp()
router.onReady(() => {
// 挂载
app.$mount('#app')
})
webpack配置 后端加入webpack
安装依赖配置
cnpm i cross-env vue-server-renderer webpack-node-externals lodash.merge -S
vue.config.js
// 导入两个webpack的插件 分别负责生成服务端和客户端的bundle
const VueSSRServerPlugin = require('vue-server-render/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
// 优化策略
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
// 根据WEBPACK_TARGET 环境变量做相应输出
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'
module.exports = {
css: {
extract: false
},
configureWebpack: () => ({
// 将entry指向应用程序server/client文件
entry: TARGET_NODE ? `./src/${target}-entry.js` : './src/main.js',
// 对bundle renderer 提供source map支持
devtool: 'source-map',
target: TARGET_NODE ? 'node' : 'web',
// mock node中的一些全局变量
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
// https://webpack.js.org/configuration/externals/#function //https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块,可以使服务器构建速度更快,
// 并生成较小的bundle文件
externals: TARGET_NODE ? nodeExternals({
// 不要外置化webpack 需要处理的依赖模块
// 你可以在这里添加更多的文件类型,例如,未处理*.vue原始文件
// 你还应该将修改`global`(例如polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
}) : undefined,
optimization: { // 优化策略分块
splitChunks: undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module.rule('vue')
.use('vue-loader')
.tap(options => {
merge(options, {
optimizeSSR: false
})
})
}
}
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:client && npm run build:server"
},
宿主文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
server/index.js修改
const express = require('express')
const fs = require('fs')
const Vue = require('vue')
// express实例
const app = express()
const { createBundleRenderer } = require('vue-server-renderer')
const bundle = require('../dist/server/vue-ssr-server-bundle.json')
const clientManifest = require('../dist/client/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(bundle, {
runInNewContext: false,
template: false.readFileSync('./src/index.temp.html', 'utf-8'),
clientManifest: clientManifest
})
function renderToString (context) {
return new Promise((resolve, reject) => {
renderer.renderToString(context, (err, html) => {
if (err) {
reject(err)
return
}
resolve(html)
})
})
}
// 创建Vue实例
// const vm = new Vue({
// data: { count: 1 },
// template: `
// <div>{{count}}</div>
// `
// })
// 创建渲染器
// const renderer = require('vue-server-renderer').createRenderer()
// 服务器路由声明
app.use(express.static('../dist/client'))
app.get('*', async function (req, res) {
// renderToString是一个异步处理 用then 或async await
try {
const context = {
title: 'ssr - test',
url: req.url
}
const html = await renderer.renderToString(context)
res.send(html)
} catch (error) {
res.status(500).send('internal Server Error')
}
})
app.listen(3000, () => {
console.log('渲染服务器成功了')
})
整合vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export function createStore () {
return new Vuex.Store({
state: {
count: 199
},
mutations: {
add (state) {
state.count += 1
}
},
actions: {
}
})
}
挂载store app.js
// 通用文件:创建vue实例
import { createRouter } from './router'
import App from './App.vue'
import Vue from 'vue'
import { createStore } from './store'
export function createApp (context) {
const router = createRouter()
const store = createStore()
const app = new Vue({
router,
store,
render: h => h(App)
})
return { app, router }
}
使用
<template>
<div>
<p>num:{{$store.state.count}}</p>
<button @click="$store.commit('add')"></button>
</div>
</template>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。