The last article showed you how to build a simple client-only WebPack configuration from scratch. Let’s write a simple front – and back-end isomorphism DEMO based on the previous code.

Rewrite the entrance

When writing client-only code, we are used to evaluating the code in a new context every time. However, the Node.js server is a long-running process. When our code enters the process, it takes a value and keeps it in memory. This means that if you create a singleton object, it will be shared between each incoming request.

In order to avoid state singletons, rewrite the entry, Vue SSR official documentation introduced in more detail, be sure to take a look. After creating the corresponding file, the SRC directory looks like this:

. ├ ─ ─ App. Vue ├ ─ ─ App. Js ├ ─ ─ assets │ └ ─ ─ logo. The PNG ├ ─ ─ entry - client. Js └ ─ ─ entry - server. JsCopy the code

Rewrite app.js to rewrite the section that creates Vue instances to a factory function that creates and returns Vue instances.

// app.js
import Vue from 'vue'
import App from './App.vue'

export function createApp () {
  const app = new Vue({
    render: h= > h(App)
  })
  return app
}Copy the code
// entry-client.js
import { createApp } from './app.js'

const app = createApp()
app.$mount('#app')Copy the code
// entry-server.js
import { createApp } from './app.js'

export default context => {
  const app = createApp()
  return app
}Copy the code

Rewrite the WebPack configuration

Because the server rendering configuration and the client configuration are slightly different, but there are many common configurations, the official recommendation is to use three different configuration files: base, client, server, through the webpack-merge plug-in to achieve the base configuration file overwrite and extend.

├─ ├─ webpack.server.conf.js ├─ webpack.client.conf.js ├─ webpack.server.conf.jsCopy the code

Copy the contents of webpack.config.js to webpack.base.conf.js. Add SSR client plug-in to webpack.server.conf.js.

const webpack = require('webpack')
const path = require('path')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')

module.exports = merge(baseConfig, {
  plugins: [
    new VueSSRClientPlugin()
  ]
})Copy the code

The client configuration is complete. The server side needs to modify the configuration of input and output, as well as the format of source-map output. CSS files introduced in Module should not be packaged into Module, and the SERVER side plug-in of SSR should be added.

const webpack = require('webpack')
const path = require('path')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
const nodeExternals = require('webpack-node-externals')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = merge(baseConfig, {
  entry: './src/entry-server.js',
  output: {
    filename: 'server-bundle.js',
    libraryTarget: 'commonjs2'Node.js uses commonjs2}, target:'node'// Specify the code to run in node devtool:'#source-map',
  externals: nodeExternals({
    whitelist: /\.css$/
  }),
  plugins: [
    new VueSSRServerPlugin()
  ]
})Copy the code

Then add the compiled command to package.json:

"scripts": {
  "test": ""."dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --config build/webpack.client.conf.js"."server": "node server.js"."build": "rimraf dist && npm run build:client && npm run build:server"."build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.conf.js --progress --hide-modules"."build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules"
},Copy the code

Run nom Run Build in the dist directory to generate the built file, and change the index. HTML file to indext.template. HTML. There are two different files in the dist directory, vue-ssR-client-manifest. json and vue-SSR-server-bundle. json. For details on how to use and implement it, please refer to the Bundle Renderer Guide · GitBook.

server.js

Then write a simple Node Server, I use Koa here, everything else is the same. The contents of server.js are as follows:

const Koa = require('koa')
const Vue = require('vue')
const { createBundleRenderer } = require('vue-server-renderer')
const path = require('path')
const fs = require('fs')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const app = new Koa()
const template = fs.readFileSync(path.resolve(__dirname, './index.template.html'), 'utf-8')
const renderer = createBundleRenderer(serverBundle, {
  basedir: path.resolve(__dirname, './dist'),
  runInNewContext: false,
  template,
  clientManifest
})

const renderToString = function (context) {
  return new Promise((resolve, reject) = > {
    renderer.renderToString(context, (err, html) => {
      if (err) reject(err)
      resolve(html)
    })
  })
}

app.use(async ctx => {
  console.log(ctx.req.url)
  if (ctx.req.url === '/favicon.ico' || ctx.req.url === '/robots.txt') {
    ctx.body = ' '
    return 
  }
  // Simple static file processing
  if (ctx.req.url.indexOf('/dist/') > - 1) {
    const urlpath = ctx.req.url.split('? ') [0].slice(1)
    const filepath = path.resolve(__dirname, '/', urlpath)
    ctx.body = fs.readFileSync(filepath)
    return
  }
  let html = ' '
  try {
    html = await renderToString({})
  } catch(err) {
    ctx.throw(500, err)
  }
  ctx.body = html  
})

app.listen(3000)
console.log('Server listening on http://localhost:3000.')Copy the code

Run nom Run Server to see the page rendered by the server.

Adding front-end methods

This is just a simple static page, there is no JS method to dynamically create some content, let’s add some front-end methods to see if the rendered page client JS running is ok. Modify app. vue file:

<template>
  <div class="demo" id="app"> <h1>Simple-webpack demo</h1> <p> This is a Simple Vue demo</p> <img SRC ="./assets/logo.png" alt=""> <p> Test SSR</p> <p v-for="(text, index) in textArr" :key="index">{{ text }}</p>
    <button @click="clickHandler"</button> </div> </template> <script>export default {
  data () {
    return {
      textArr: []
    }
  },
  methods: {
    clickHandler () {
      this.textArr.push(`${this.textArr.length + 1}.Here is the new text. `) } } } </script>Copy the code

Then build the entire project again and restart the server.


Simple data injection

Render a news page and expect the title to be rendered directly from the page? What should be done? Vue.js SSR provides methods to insert template variables. Simply add template variables to index.template.html to insert data like any other back-end template.

SSR demo – {{title}} Then the first argument in the renderToString method in server.js is passed {title: ‘First SSR Demo’}. Finally, restart the background service again, as shown below, our page title has become our definition.

If we want more complex data we can only inject a window global variable. At this time, we could not use the static method of the component to inject through the background service. Because we did not use the Router, we did not know whether the component in the app had been instantiated, so we could not obtain the static method in the component. __INIT_STATE in SSR. First add a script tag to index.template. HTML to add template variables, and then pass the data into server.js. Last modified App. Vue file in mounted judgment for this variable, the variable assigned to the component data property, specific code is as follows:

<! -- index.template.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>SSR demo - {{ title }}
  </title>
  <script>
    {{{ injectData }}}
  </script>
</head>
<body>
  <! --vue-ssr-outlet-->
</body>
</html>Copy the code
// server.js
html = await renderToString({
  title: 'First SSR Demo'.injectData: 'window.__INIT_DATA__ = ' + JSON.stringify({
    text: 'This is server injected data. '})})Copy the code
<! -- App.vue -->
<template>
  <div class="demo" id="app">
    <h1>Simple-webpack demo</h1>
    <p>This is a simple Vue demo</p>
    <img src="./assets/logo.png" alt="">
    <p>Test the SSR</p>
    <p> {{ serverData.text }}</p>
    <p v-for="(text, index) in textArr" :key="index">{{ text }}</p>
    <button @click="clickHandler">Add a line of text</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      textArr: [].serverData: ' '
    }
  },
  mounted () {
    this.serverData = window.__INIT_DATA__
  },
  methods: {
    clickHandler () {
      this.textArr.push(`The ${this.textArr.length + 1}.Here is the new text. `)}}}</script>Copy the code

After recompiling and restarting the service, the page should have an additional paragraph of text, as shown below:

Success! All the code is on this wheato/ SSR-step-by-step

reference

Vue. Js server side rendering guide