Public account: wechat search front end tool person; Take more dry goods

A, the opening

  • Ok,Vue2. X SSR server rendering is constructed from zero analysis, and the advantages and disadvantages are interpretedIs introduced,SSRAdvantages and disadvantages and build configurations from scratch
  • Next upvue-cli3Set upSSRAs well asThermal overload(There is no need to run the package every time during the development process and see the change effect in real time)Improve development efficiency;
  • Hot overloading personally feels like developmentSSRProject essential posture;
  • Here will not introduce the SSR principle, advantages and disadvantages and so on; For details, please refer to the previous article vue2. X SSR server rendering from zero analysis of the construction, the advantages and disadvantages of interpretation
  • Pit is a lot, specific or forget, most are plug-in dependent version of the problem, please refer to my directory, or leave a message; Reply as soon as you see it
  • Source code address:https://github.com/laijinxian/vue2.x-ssr-template/tree/master/vue-cli3.x-ssrTo astar

Second, SSR code construction

1. vue-routerExport a factory function to create a new one

import Vue from 'vue';

import VueRouter from 'vue-router';

import Home from '.. /views/Home'

Vue.use(VueRouter);

/ * *

 * TODO 

* Note: Default routes do not use lazy loading component: () => import()'.. /views/About'), subsequent routes can

Repalce... router ... Wrong or something;

* /

const routes = [{

  path: '/'.

  name: 'Home'.

  component: Home

}, {

  path: '/about'.

  name: 'About'.

  component: () => import(/* webpackChunkName: 'about'* /'.. /views/About')

}];

export function createRouter() {

  return new VueRouter({

    mode: 'history', // Must behistorymodel

    routes

  })

}

Copy the code

2. main.jsExport a factory function to create a new one

// The Vue build version to load with the `import` command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from 'vue'

import App from './App.vue'

import { createRouter } from './router'

import { createStore } from './store'

import { sync } from 'vuex-router-sync'

import '@/mixins'



// Export a factory function to create a new one

export function createApp () {

// Create router and Store instances

  const router = createRouter()

  const store = createStore()



// Synchronize route state to store

  sync(store, router)



// Create an application instance to inject router and Store

  const app = new Vue({

    router,

    store,

    render: h => h(App)

  })



// Expose app, Router, and Store.

  return { app, router, store }

}

Copy the code

3. srcUnder the newentry-client.jsentry-server.js

// entry-client.js

/ / write 1

// import { createApp } from './main'

// const { app, router, store } = createApp()

// if (window.__INITIAL_STATE__) {

//   store.replaceState(window.__INITIAL_STATE__)

// }

// router.onReady(() => {

//   app.$mount('#app')

// })



/ / write 2

import { createApp } from './main'

const { app, router, store } = createApp()

if (window.__INITIAL_STATE__) {

  store.replaceState(window.__INITIAL_STATE__)

}

router.onReady(() => {

// Add a routing hook function to handle asyncData.

// execute after initial route resolve,

// So that we do not double-fetch the existing data.

// Use 'router.beforeresolve ()' to ensure that all asynchronous components are resolved.

  router.beforeResolve((to, from, next) => {

    const matched = router.getMatchedComponents(to)

    const prevMatched = router.getMatchedComponents(from)



// We only care about non-prerendered components

// So we compare them to find the different components of the two matching lists

    let diffed = false

    const activated = matched.filter((c, i) => {

      returndiffed || (diffed = (prevMatched[i] ! == c))

    })



    if(! activated.length)return next()



// If there is a loading indicator, it will trigger



    Promise.all(activated.map(c => {

      if (c.asyncData) {

        return c.asyncData({ store, route: to })

      }

    })).then(() = > {

// Stop loading indicator

      next()

    }).catch(next)

  })

  app.$mount('#app'.true)

})

Copy the code
// entry-server.js

import { createApp } from './main'



// The export function will be called by the Bundlerender

// Prefetch is asynchronous, so a Promise should be returned

export default context => {

  return new Promise((resolve, reject) => {

    const { app, router, store } = createApp()



// Set the router location on the server

    router.push(context.url);



// Wait until the router has resolved possible asynchronous components and hook functions

    router.onReady(() => {

     

      const matchedComponents = router.getMatchedComponents();



Reject (reject); reject (reject); reject (reject)

      if(! matchedComponents.length) {

// Do something about it

// Vue-cli3 reject directly causes file import errors

        // return reject(new Error({ code: 404 }))

      }



// Call asyncData() on all matching routing components

      Promise.all(matchedComponents.map(Component => {

        if (Component.asyncData) {

          return Component.asyncData({

            store,

            route: router.currentRoute

          })

        }

      })).then(() = > {

// After all preFetch hooks resolve,

// Our store is now populated with the state needed to render the application.

// When we append state to context,

// When the 'template' option is used with renderer,

// The state will be automatically serialized to 'window.__initial_state__' and injected with HTML.

        context.rendered = () => {

          context.state = store.state

        }

        resolve(app)

      }).catch(reject)

      resolve(app)

    }, reject)

  })

}

Copy the code

4. Increasevue.config.jsThis is avue-cli3webpackPackage the built configuration file

const VueSSRServerPlugin = require('vue-server-renderer/server-plugin');

const VueSSRClientPlugin = require('vue-server-renderer/client-plugin');

const nodeExternals = require('webpack-node-externals');

const merge = require('lodash.merge');

const TARGET_NODE = process.env.WEBPACK_TARGET === 'node';

const target = TARGET_NODE ? 'server' : 'client';

 

module.exports = {

  configureWebpack: () => ({

// Point entry to the application's server/client file

    entry: `./src/entry-${target}.js`,

// The bundle renderer is providedsourceThe map support

    devtool: 'source-map'.

    target: TARGET_NODE ? 'node' : 'web'.

    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

// Externalize application dependency modules. Can make server builds faster,

// Generate smaller bundles.

    externals: TARGET_NODE

      ? nodeExternals({

// Do not externalize dependent modules that WebPack needs to handle.

// You can add more file types here. For example, the *.vue raw file is not processed,

// You should also whitelist dependent modules that modify 'global' (e.g. polyfill)

          allowlist: [/\.css$/]

        })

      : undefined,

    optimization: { splitChunks: TARGET_NODE ? false : undefined },

    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]

  }),

  chainWebpack: config => {

    config.module

      .rule('vue')

      .use('vue-loader')

      .tap(options => {

        merge(options, {

          optimizeSSR: false

        });

      });

    // fix ssr hot update bug

    if (TARGET_NODE) {

      config.plugins.delete("hmr");

    }

  }

}

Copy the code

5. srcUnder the newtemplate/index.ssr.htmlThe notes are too important to delete

<! DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">

  <meta http-equiv="X-UA-Compatible" content="ie=edge">

  <title>index.template.html</title>

</head>

<body>

<! --vue-ssr-outlet-->

</body>

</html>

Copy the code

6. srcUnder the newserver/index.js

const fs = require("fs");

const Koa = require("koa");

const express = require('express')

const path = require("path");

const koaStatic = require("koa-static");

const app = new Koa();

// const app = express()



const resolve = file => path.resolve(__dirname, file);

// 1. Open the dist directory

app.use(koaStatic(resolve("./dist")));



Get a createBundleRenderer

const { createBundleRenderer } = require("vue-server-renderer");

const serverBundle = require(resolve('.. /dist/vue-ssr-server-bundle.json'))

const clientManifest = require(resolve('.. /dist/vue-ssr-client-manifest.json'))

const template = fs.readFileSync(resolve('.. /src/template/index.ssr.html'), 'utf-8')



const renderer = createBundleRenderer(serverBundle, {

  runInNewContext: false.

  template: template,

  clientManifest: clientManifest

});



function renderToString(context) {

  return new Promise((resolve, reject) => {

    renderer.renderToString(context, (err, html) => {

      err ? reject(err) : resolve(html);

    });

  });

}



// 3. Add a middleware to handle all requests

app.use(async ctx => {

  const context = {

    title: "ssr test".

    url: ctx.url

  };



// Render the context data into HTML

  const html = await renderToString(context);

  ctx.body = html;

});



const port = process.env.PORT || 8090

app.listen(port, () => {

  console.log(`server started at localhost:${port}`)

})



Copy the code

7. Package. json Added the package command

.

"start""node server/index.js".

"build:client""vue-cli-service build".

"build:server""cross-env WEBPACK_TARGET=node vue-cli-service build --mode server".

"build""npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json"

.

Copy the code

8. Run commands to build the database

  • yarn run build or npm run build
  • yarn run start or npm run start
  • Browser inputhttp://localhost:8090/You can see the page
  • How to tell whether the server render successfully please seearticleHave introduced

Three, hot overload -> development tools

1. The principle of reference pot bagger hot overload article

  • throughcompilercompilewebpackConfiguration file, listen for file modification, get the latestvue-ssr-server-bundle.jsonReal-time compilation
  • throughwebpack dev serverGet the latestvue-ssr-client-manifest.json
  • In combination withvue-ssr-server-bundle.jsonvue-ssr-client-manifest.jsonApply colours to a drawinghtmlThe page is returned to the browser

2. Install required plug-ins

  • yarn add -D webpack memory-fs concurrently or cnpm install webpack memory-fs concurrently -D
  • yarn add koa-router axios or cnpm install koa-router axios -S

3. src/serveUnder the newsetup-dev-server.js

const webpack = require("webpack");

const axios = require("axios");

const MemoryFS = require("memory-fs");

const fs = require("fs");

const path = require("path");

const Router = require("koa-router");



/ / 1. Webpack configuration file in/node_modules / @ vue/cli - service/webpack config. Js

const webpackConfig = require("@vue/cli-service/webpack.config");

const { createBundleRenderer } = require("vue-server-renderer");



2. Compile the WebPack configuration file

const serverCompiler = webpack(webpackConfig);

const mfs = new MemoryFS();



// Specify the output file to the memory stream

serverCompiler.outputFileSystem = mfs;



// 3. Monitor file modification, compile real-time to obtain the latest vue-SSR-server-bundle. json

let bundle;

serverCompiler.watch({}, (err, stats) => {

  if (err) {

    throw err;

  }

  stats = stats.toJson();

  stats.errors.forEach(error => console.error(error));

  stats.warnings.forEach(warn => console.warn(warn));

  const bundlePath = path.join(

    webpackConfig.output.path,

    "vue-ssr-server-bundle.json"

  );

  bundle = JSON.parse(mfs.readFileSync(bundlePath, "utf-8"));

  console.log("new bundle generated");

});



// Process the request

const handleRequest = async ctx => {

  console.log("path", ctx.path);

  if(! bundle) {

    ctx.body = "Wait for webpack to complete before accessing";

    return;

  }

// 4. Obtain the latest vue-ssr-client-manifest.json

  const clientManifestResp = await axios.get(

    "http://localhost:8080/vue-ssr-client-manifest.json"

  );

  const clientManifest = clientManifestResp.data;



  const renderer = createBundleRenderer(bundle, {

    runInNewContext: false.

    template: fs.readFileSync(

      path.resolve(__dirname, ".. /src/index.temp.html"),

      "utf-8"

    ),

    clientManifest: clientManifest

  });

  const html = await renderToString(ctx, renderer);

  ctx.body = html;

};



function renderToString(context, renderer) {

  return new Promise((resolve, reject) => {

    renderer.renderToString(context, (err, html) => {

      err ? reject(err) : resolve(html);

    });

  });

}



const router = new Router();



router.get("*", handleRequest);



module.exports = router;

Copy the code

4. src/serveUnder the newssr.jsDevelop hot overload entry files

// server/ssr.js

const Koa = require("koa");

const KoaStatis = require("koa-static");

const path = require("path");



const resolve = file => path.resolve(__dirname, file);

const app = new Koa();



const isDev = process.env.NODE_ENV ! = ="production";

const router = isDev ? require("./setup-dev-server.js") : require("./index.js");



app.use(router.routes()).use(router.allowedMethods());



// Open directory

app.use(KoaStatis(resolve(".. /dist")));



const port = process.env.PORT || 3000;



app.listen(port, () => {

  console.log(`server started at localhost: ${port}`);

});



module.exports = app;

Copy the code

5. package.jsonAdd build commands

.

"dev""concurrently \"npm run serve\" \"npm run dev:serve\" ".

"dev:serve""cross-env WEBPACK_TARGET=node node ./server/ssr.js".

.

Copy the code

6. Run commands to build

  • yarn run dev or npm run dev
  • Browser inputhttp://localhost:8081/You can see the effect
  • Test any onevueA file changes something and the browser automatically reloads it

7. Suggest

Hot overload principle can refer to the pot bagger hot overload article has a detailed introduction

Four, conclusion

Vue3.0 SSR should be added later, but the timing is uncertain