preface

Before, I made the activity release page and put it into Baidu, 360 and other channels, adopting the way of KOA2 + template engine. Found several problems

  1. Compared with framework development pages are less efficient and less maintainable
  2. Compatibility problems. After adding buried points on the page, it was found that some users’ data could not be obtained. After checking, it was found that the devices of users who came through various channels still contained a large number of browsers of earlier versions.

Server side rendering

Server-side rendering is different from single-page rendering

If you look at the following two images, you can see that if you render on the server side, you get the full HTML structure in the browser. Single pages are js files introduced by script tags, which ultimately hang the virtual DOM on top of the #app container.

@vue/cli 4 to build the project structure

The following code uses a minimal example and the complete code will be posted on Github

Step1 install the latest scaffold initialization project

yarn global add @vue/cli

Step2 add the server file

  1. Start a Web service in the code belowhttp://localhost:9000That’s where we finally get to the address
const Koa = require('koa')
const path = require('path')

const resolve = file => path.resolve(__dirname, file)
const app = new Koa()
const router = require('./router')
const port = 9000
app.listen(port, () => {
  console.log(`server started at localhost:${port}`)
})
module.exports = app
Copy the code
  1. Here is just to start the service, we need to read the server and client to the file, the following code is the key step of the server rendering
const fs = require('fs')
const path = require('path')
const send = require('koa-send')
const Router = require('koa-router') const router = new router () // Get the absolute path of the current file const resolve = file => path.resolve(__dirname, file) const { createBundleRenderer } = require('vue-server-renderer')
const bundle = require('.. /dist/vue-ssr-server-bundle.json')
const clientManifest = require('.. /dist/vue-ssr-client-manifest.json'RenderToString renders the bundle as a string const renderer = createBundleRenderer(bundle, { runInNewContext:false,
  template: fs.readFileSync(resolve('.. /src/index.temp.html'), 'utf-8'),
  clientManifest: clientManifest
})

const handleRequest = async ctx => {
  ctx.res.setHeader('Content-Type'.'text/html'In version 2.5.0+, this callback function is optional. When no callback is passed, this method returns a Promise object that, after resolve, returns the final rendered HTML. ctx.body = await renderer.renderToString(Object.assign({}, ctx.state.deliver, { url })) } router.get('/home',handleRequest)
module.exports = router
Copy the code
  • vue-server-renderProvide an API called createBundleRenderer to use as follows
const { createBundleRenderer } = require('vue-server-renderer')

const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false, // recommend template, // (optional) page template clientManifest // (optional) client build manifest})Copy the code
  • The render object produced by the createBundleRenderer method above renders the bunlde as a string, returning the final HTML to the client.
bundleRenderer.renderToString([context, callback]): ? Promise<string>Copy the code

Step3 add entry-client.js and entry-server.js entry files

Except for these two entry files in SRC, all other files are common to the client and server. Let’s see what’s going on in these two entry files.

The general process is: The server creates a vUE instance, sends the page’s asynchronous request data to a container –> The client receives the HTML sent by the server and mount it in active mode, automatically adding data-server-Rendered =”true” special attribute to the root element #app

main.js

import Vue from 'vue'
import App from './App.vue'.export function createApp() {/ /... const app = new Vue({ router, store, render: h => h(App) })return { app, router, store }
}
Copy the code

entry-server.js

import { createApp } from './main.js'
exportDefault context => {// Since it might be an asynchronous routing hook function or component, we will return a Promise so that the server can wait for everything to be ready before rendering.returnnew Promise((resolve, reject) => { const { app, router, Router.push (context.url) router.onReady(() => {store} = createApp() router.push(context.url) Const matchedComponents = router. GetMatchedComponents () / / can't match routing, perform reject function, and returns 404if(! matchedComponents.length) {return reject({ code: 404 })
      }
      Promise.all(
        matchedComponents.map(component => {
          if (component.asyncData) {
            returncomponent.asyncData({ store, context, route: Router.currentroute})}})).then(() => {// After all preFetch hooks resolve, // our store is now populated with the state needed to render the application. // When we attach the state to the context, // and the 'template' option is used for renderer, // the state is automatically serialized to 'window.__INITIAL_STATE__' and injected into HTML. Context.state = store.state resolve(app)}). Catch (reject)}, reject)})}Copy the code

entry-client.js

import { createApp } from './main'
const { app, router, store } = createApp()

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
    let diffed = false
    const activated = matched.filter((c, i) => {
      returndiffed || (diffed = prevMatched[i] ! == c) })if(! activated.length) {return next()
    }

    Promise.all(
      activated.map(component => {
        if (component.asyncData) {
          component.asyncData({
            store,
            route: to
          })
        }
      })
    )
      .then(() => {
        next()
      })
      .catch(next)
  })
  app.$mount('#app')})Copy the code

The last

Refer to github address for complete code

Post this picture by the way