vue-server-render vue服务端的渲染器 express服务端的服务器 开发模式开发
npm i vue-server-renderer express -D
server/index.jscss
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 对外暴露createRouterhtml
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
服务端入口vue
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.jsnode
import { createApp } from './app' const { app, router } = createApp() router.onReady(() => { // 挂载 app.$mount('#app') })
安装依赖配置
cnpm i cross-env vue-server-renderer webpack-node-externals lodash.merge -S
vue.config.jswebpack
// 导入两个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 }) }) } }
"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修改git
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('渲染服务器成功了') })
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.jsgithub
// 通用文件:建立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 } }
使用web
<template> <div> <p>num:{{$store.state.count}}</p> <button @click="$store.commit('add')"></button> </div> </template>