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>