从零开始搭建一个vue-ssr

前言

上次我们已经实现了从零开始,搭建一个简单的vue-ssr的demo:从零开始搭建一个vue-ssr(上)。那么这次呢,我们基于vue-cli,进行webpack的改造,实现vue-cli版本的vue-ssr。

开始改建

补充安装依赖

与上一次不同,这次我们基于vue-cli进行改建,已经有了很多依赖库了,但我们任需要补充一个核心:

npm install vue-server-renderer

修改客户端的webpack配置

修改webpack.dev.conf.js文件,添加插件

const vueSSRClientPlugin = require("vue-server-renderer/client-plugin");const devWebpackConfig = merge(baseWebpackConfig,{    plugins:[        new vueSSRClientPlugin()    ] });

添加了这个配置以后,重新启动项目通过地址就可以访问到vue-ssr-client-manifest.json(http://localhost:8082/vue-ssr-client-manifest.json),页面中出现的内容就是所需要的client-bundle。

修改vue的相关文件

此步骤跟从零开始搭建一个vue-ssr(上)大有相似,不重复描述,直接上代码:
修改 router/index.js

import vueRouter from "vue-router";import Vue from "vue";import HelloWorld from "@/components/HelloWorld";import About from "@/components/About";Vue.use(vueRouter);export default () => {  return new vueRouter({    mode:"history",    routes:[      {        path:"/",        component:HelloWorld,        name:"HelloWorld"      },      {        path:"/about",        component:About,        name:"About"      }    ]  })}

修改app.js

import Vue from "vue";import createRouter from "./router";import App from "./App.vue";export default (context) => {  const router = createRouter();  const app = new Vue({    router,    components: { App },    template: '<App/>'  });  return {    app,    router  }}

修改entry-server.js

import createApp from "./app.js";export default (context) => {    return new Promise((reslove,reject) => {        let {url} = context;        let {app,router} = createApp(context);        router.push(url);        router.onReady(() => {            let matchedComponents = router.getMatchedComponents();            if(!matchedComponents.length){                return reject();            }            reslove(app);        },reject)    })}

修改entry-client.js

import createApp from "./app.js";let {app,router} = createApp();router.onReady(() => {    app.$mount("#app");});

修改webpack客户端入口以及输出

修改webpack.base.conf.js

module.exports = {    entry:{        app:"./src/entry-client.js"    },    output:{        publicPath:"http://localhost:8080/"    }};

创建服务端webpakc配置文件

创建build/webpack.server.conf.js文件,目的将插件vue-server-renderer/server-plugin引入server端执行。

const webpack = require("webpack");const merge = require("webpack-merge");const base = require("./webpack.base.conf");//  手动安装//  在服务端渲染中,所需要的文件都是使用require引入,不需要把node_modules文件打包const webapckNodeExternals = require("webpack-node-externals");const vueSSRServerPlugin = require("vue-server-renderer/server-plugin");module.exports = merge(base,{    //  告知webpack,需要在node端运行    target:"node",    entry:"./src/entry-server.js",    devtool:"source-map",    output:{        filename:'server-buldle.js',        libraryTarget: "commonjs2"    },    externals:[        webapckNodeExternals()    ],    plugins:[        new webpack.DefinePlugin({            'process.env.NODE_ENV':'"development"',            'process.ent.VUE_ENV': '"server"'        }),        new vueSSRServerPlugin()    ]});

创建build/dev-server.js,拿到客户端和服务端的bundle文件以及读取到index.html中的模板用作渲染。

const serverConf = require("./webpack.server.conf");const webpack = require("webpack");const fs = require("fs");const path = require("path");//  读取内存中的.json文件//  这个模块需要手动安装const Mfs = require("memory-fs");const axios = require("axios");module.exports = (cb) => {    const webpackComplier = webpack(serverConf);    var mfs = new Mfs();        webpackComplier.outputFileSystem = mfs;        webpackComplier.watch({},async (error,stats) => {        if(error) return console.log(error);        stats = stats.toJson();        stats.errors.forEach(error => console.log(error));        stats.warnings.forEach(warning => console.log(warning));        //  获取server bundle的json文件        let serverBundlePath = path.join(serverConf.output.path,'vue-ssr-server-bundle.json');        let serverBundle = JSON.parse(mfs.readFileSync(serverBundlePath,"utf-8"));        //  获取client bundle的json文件        let clientBundle = await axios.get("http://localhost:8082/vue-ssr-client-manifest.json");        //  获取模板        let template = fs.readFileSync(path.join(__dirname,"..","index.html"),"utf-8");        cb && cb(serverBundle,clientBundle,template);    })};

在根目录下创建server.js文件,用于启动服务,并利用createBundleRenderer将两个Bundle和html模板渲染出来。

const devServer = require("./build/dev-server.js");const express = require("express");const app = express();const vueRender = require("vue-server-renderer");app.get('*',(request,respones) => {    respones.status(200);    respones.setHeader("Content-Type","text/html;charset-utf-8;");    devServer((serverBundle,clientBundle,template) => {        let render = vueRender.createBundleRenderer(serverBundle,{            template,            clientManifest:clientBundle.data,            //  每次创建一个独立的上下文            renInNewContext:false        });         render.renderToString({            url:request.url        }).then((html) => {            respones.end(html);        }).catch(error => {          respones.end(JSON.stringify(error));        });    });})app.listen(5001,() => {    console.log("服务已开启")});

补充一个html模板

根目录下创建index.html

<!DOCTYPE html><html>  <head>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width,initial-scale=1.0">    <title>vue_cli_ssr</title>  </head>  <body>    <div id="app">      <!--vue-ssr-outlet-->    </div>    <!-- built files will be auto injected -->  </body></html>

最后在package.json中添加一个启动服务端的命令:

"server": "node server.js"

大功告成,开启两个终端,分别输入

npm run devnpm run server

浏览器打开localhost:8082,我们便可以看到由vue-cli改造而成的vue-ssr。

总结

本文是直接基于vue-cli进行改造的一个ssr版本,若想从零开始手撕vue-ssr,可以看上一篇文章从零开始搭建一个vue-ssr(上)。

项目源码

https://github.com/TheWalking...