vue-cli3 构建 SSR 附件
创建 server 服务端
基于 vue-cli3 初始化项目
-
/server/index.js
服务端const express = require('express'); const server = express(); const fs = require('fs'); const { resolve } = require('path'); const { createBundleRenderer } = require('vue-server-renderer'); const serverBundle = require('../dist/vue-ssr-server-bundle.json'); const clientManifest = require('../dist/vue-ssr-client-manifest.json'); server.use(express.static(resolve('../dist'), { index: false })); server.get('*', async (req, res) => { // /demo, req.url /demo try { const url = req.url; // 2. 创建渲染器 const render = createBundleRenderer(serverBundle, { template: fs.readFileSync('./index.ssr.html', 'utf-8'), clientManifest }); const html = await render.renderToString({url}); res.send(html) // 3. 利用渲染器将vue实例转化成html字符串 } catch (error) { console.log(error); if (error.code == 404) { res.status(404).send('页面去火星了,找不到了,404啦'); return; } res.status(500).send('服务器错误'); } // res.send('hello'); }); server.listen(12306, () => console.log('server is run at 12306'));
-
/server/index.ssr.html
模板<!DOCTYPE html> <html lang="en"> <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"> <title>Document</title> </head> <body> <!--vue-ssr-outlet--> <!-- <script src="/client.bundle.js"></script> --> </body> </html>
创建客户端、服务端入口文件
-
/src/entry/client.entry.js
客户端入口文件import createApp from '../main.js'; const { app, store } = createApp(); app.$mount('#app'); if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__); }
-
/src/entry/server.entry.js
服务端入口文件import createApp from '../main.js'; export default function (ctx) { return new Promise((resolve, reject) => { const { app, router, store } = createApp(); router.push(ctx.url); router.onReady(() => { // 判断当前路由下是否存在组件 const matchedComponents = router.getMatchedComponents(); if (matchedComponents.length == 0) { return reject({ code: 404 }); } Promise.all(matchedComponents.map(c => { if (c.asyncData) { return c.asyncData(store) } })).then(() => { // window.__INITIAL_STATE__ ctx.state = store.state; resolve(app); }).catch(reject); // resolve(app); }, reject) }); }
修改客户端入口文件、根组件
-
main.js
import Vue from 'vue' import App from './App.vue' import createRouter from './router' import createStore from './store' Vue.config.productionTip = false export default function () { const router = createRouter(); const store = createStore(); const app = new Vue({ router, store, render: h => h(App) }); return { app, router, store, } }
-
App.vue
<template> <div id="app"> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/> </div> </template>
修改 router、vuex
-
/src/router/index.js
路由import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ] export default function () { const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }); return router; }
-
/src/store/index.js
vueximport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default function () { return new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } }) }
修改配置文件
-
/vue.config.js
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin'); const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'); const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'; const target = TARGET_NODE ? 'server' : 'client'; module.exports = { configureWebpack: () => ({ entry: { app: `./src/entry/${target}.entry.js` }, target: TARGET_NODE ? 'node' : 'web', output: { libraryTarget: TARGET_NODE ? 'commonjs2' : undefined }, plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin] }), chainWebpack: config => { // 关闭多入口,否则报错 config.optimization.splitChunks(undefined) } }
-
package.json
"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "build:client": "vue-cli-service build", "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server", "build:all": "npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json" // build:all: // - 先打包服务端,再把打包后的 dist 下服务端代码放到项目根路径下 // - 再打包客户端代码,再把根路径下的服务端代码放回 dist 下 },
SSR🤜 vue 手动搭建 SSR
上一篇