// webpack-base-config.js const { VueLoaderPlugin } = require('vue-loader'); const path = require('path'); module.exports = { //輸出路徑爲EggJs靜態資源文件夾public下的dist文件夾 output: { publicPath: '/public/dist/', path: path.join(__dirname, '../public/dist'), }, resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', } }, module: { rules: [{ test: /\.vue$/, loader: 'vue-loader' }, { test: /\.css$/, use: ["vue-style-loader", "css-loader", 'less-loader'] }, { test: /\.less$/, use: ["vue-style-loader", "css-loader", 'less-loader'] }, { test: /\.(gif|png|jpg|woff|svg|ttf|eot)\??.*$/, loader: { loader: 'url-loader', options: { limit: 8192, name: './resource/[name].[ext]', }, } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }], }, plugins: [ new VueLoaderPlugin(), ] }
webpack-client-config.jscss
const webpack = require('webpack'); const merge = require('webpack-merge'); const baseConfig = require('./webpack-base-config.js'); const VueSSRClientPlugin = require('vue-server-renderer/client-plugin'); const path = require('path'); module.exports = merge(baseConfig, { // 爲了兼容安卓4.0版本,編譯成es5語法 entry: [ 'babel-polyfill', path.join(__dirname, '../src/entry-client.js') ], plugins: [ new webpack.optimize.SplitChunksPlugin({ name: 'manifest', minChunks: Infinity }), new VueSSRClientPlugin() ] })
webpack-server-config.jshtml
const merge = require('webpack-merge'); const nodeExternals = require('webpack-node-externals'); const baseConfig = require('./webpack-base-config'); const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'); const path = require('path'); module.exports = merge(baseConfig, { entry: [ 'babel-polyfill', path.join(__dirname, '../src/entry-server.js') ], target: 'node', devtool: 'source-map', output: { libraryTarget: 'commonjs2' }, externals: nodeExternals({ whitelist: /\.css$/ }), plugins: [ new VueSSRServerPlugin() ] })
這裏因爲是服務端渲染,hash模式的路由並不可以讓服務器獲取到路由改變事件,因此咱們的路由模式必須是history模式而不是hash模式,具體router文件以下:vue
import Vue from 'vue'; import Router from 'vue-router'; const home = (resolve) => { require(["../views/home.vue"], resolve) }; Vue.use(Router); let router = new Router({ mode: 'history', routes: [{ path: '/home', component: home }] }); export function createRouter() { return router; }
Vue項目的入口文件須要由原來的直接new Vue()實例並mount修改成暴露一個createApp實例的方法:node
import Vue from 'vue'; import App from './App.vue'; import { createRouter } from './router/index.js'; export function createApp() { // 建立router實例 const router = createRouter() // 建立vue對象實例 const app = new Vue({ render: h => h(App), router }); return { app, router }; }
客戶端打包入口文件:webpack
import { createApp } from './app'; const { app, router } = createApp(); router.onReady(() => { app.$mount('#app', true); });
服務器端打包入口文件:git
import { createApp } from './app'; export default context => { return new Promise((resolve, reject) => { const { app, router } = createApp(); router.push(context.url); router.onReady(() => { const matchedComponents = router.getMatchedComponents(); if (!matchedComponents.length) { return reject({ code: 404 }); } resolve(app); }, reject); }); };
配置完以上文件以後運行咱們熟悉的npm run build指令在dist文件夾下會生成一個服務端渲染所需的bundle和客戶端混合所需的manifest文件:github
const { createBundleRenderer } = require('vue-server-renderer'); const serverBundle = require('./app/public/dist/vue-ssr-server-bundle.json'); const clientManifest = require('./app/public/dist/vue-ssr-client-manifest.json'); const path = require('path'); const file = require('fs'); class AppBootHook { constructor(app) { this.app = app; } // 配置文件加載完畢事件 async willReady() { let renderer = createBundleRenderer(serverBundle, { runInNewContext: false, template: file.readFileSync(path.join(__dirname, './app/public/index.html'), 'utf-8'), clientManifest }); this.app.renderer = renderer; } } module.exports = AppBootHook;
以後再在controller中根據請求的url路徑調用renderToString方法把頁面渲染爲html字符串返回給瀏覽器就大功告成了。web
'use strict'; const Controller = require('egg').Controller; class HomeController extends Controller { async index() { let renderer = this.app.renderer; let context = { url: this.ctx.request.url }; renderer.renderToString(context, (err, html) => { if (err) { if (err.code === 404) { this.ctx.body = "404"; } else { this.ctx.body = process.env.NODE_ENV; } } else { this.ctx.body = html; } }); } } module.exports = HomeController;
git地址:https://github.com/cgy-tiaopi/egg-vue-ssrvue-router