vue ssr server rendering for beginners

vue ssr server rendering

>Learning ssr in the beginning

At the beginning, there are many doubts about rendering on the vue server. Most of our front-end is a semi monk. The first step is to separate the front and back ends. We don't know about the server. We don't speak java or php language, and we don't even understand the node service. It's still difficult to understand rendering on the server;

There are many entry-level cases of vue service rendering on the Internet, but after reading for a long time, many of them are still confused and don't understand the connection and meaning of these files and keywords:
server.js
entrt-client.js
server-js
built-server-bundle.js
vue-ssr-server-bundle.json
vue-ssrclientmanifest.json
createBundleRenderer
clientManifest

This content will enter the pit in the order of basic server rendering - vue instance rendering - adding vueRouter - adding vueX, and then there should be - development mode - seo optimization - partial rendering. There are not so many pits here first;

>Basic server rendering

As the name suggests, start a service: (build a new project, don't use Vue CLI)

//server.js
const express = require('express');
const chalk = require('chalk');//A chalk is the console..

const server = express();

server.get('*', (req, res) => {
res.set('content-type', "text/html");
res.end(`
<!DOCTYPE html>
<html lang="en">
    <head><title>Hello</title></head>
    <body>Hello</body>
</html>
`)
})

server.listen(8080,function(){
let ip = getIPAdress();
console.log(`Server on: http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//The os module under node can get some information about the server that started the file
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
    var iface = interfaces[devName];
    for (var i = 0; i < iface.length; i++) {
        var alias = iface[i];
        if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
            return alias.address;
        }
    }
}
}

Start node server.js

Look at the normal page again. This is the most basic server-side rendering

In fact, it is a get request that returns a string. The browser displays the returned result by default;
However, the parsing of this string is not clear. For example:

Get rid of this sentence, the page will be like this, the reason is not profound, baidu

>Join vue instance

Skip the application of build-server-bundle.js mentioned on the official website, which means that you don't need to worry about this file, it's just a transitional file, and it won't be used in the project. Directly use the createbundlerender method, and directly use vue-ssr-server-bundle.json;

Look at the current directory structure:

Five new files have been added. The configuration of entry-client.js is not necessary. It doesn't matter here;

app.js is used to create vue instances;

entry-server.js is used to create the configuration accessories needed to generate vue-ssr-server-bundle.json (app.js is required); it is used for webpack.server.config.js;

webpack.server.config.js is used to generate vue-ssr-server-bundle.json;

vue-ssr-server-bundle.json is used for createbundlerender in server.js.

//app.js 
import Vue from 'vue'
import Vue from './App.vue'//Make sure to write. vue here, or it will match app.js. Require is not case sensitive 0.0
export default createApp=function(){
return new Vue({
    render:h => h(App)
})
}

A createApp generates a vue instance;

//App.vue
<template>
<div id='app'>
    //This is a app.
</div>
</template>
<script>
export default {}
</script>

Not yet used < router View >

//weback-base.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
output:{
    path:path.resolve(__dirname,'./dist'),
    filename:'build.js',
},
module: {
    rules: [
        {
            test:/\.js$/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
            exclude:[/node_modules/,/assets/]
        },
        {
            test:/\.vue$/,
            use:['vue-loader']
        }
    ]
},
resolve: {
    alias:{
        '@':path.resolve(__dirname,'../')
    },
    extensions:['.js','.vue','.json']
},
plugins:[
    new VueLoaderPlugin()
]
}

No verbosity about webpack configuration

//webpack.server.config.js is used to generate vue-ssr-server-bundle.json
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = merge(baseConfig, {
  entry: './entry-server.js',

  // This allows webback to handle dynamic import in a Node appropriate fashion,
  // And when compiling Vue components,
  // Tell 'Vue loader' to send server-oriented code.
  target: 'node',

  // Provide source map support for bundle renderer
  devtool: 'source-map',

  // Tell server bundle here to use Node style exports
  output: {
    libraryTarget: 'commonjs2'
  },


  // This is the entire output of the server
  // A plug-in built as a single JSON file.
  // The default file name is ` vue-ssr-server-bundle.json`
  plugins: [
    new VueSSRServerPlugin()
  ]
})

This configuration can be found everywhere. The key point is the plug-in VueSSRServerPlugin. It is the plug-in that generates Vue SSR server bundle.json. If it is removed, it will generate build server bundle.js. For the configuration of merge plug-in, librarytarget and target, Baidu webpack will go to 0.0;

//entry-server.js
import { createApp } from './src/app'

export default context => {
    return createApp()
}

Fixed write method, return a function for createbundlerender;

Generate vue-ssr-server-bundle.json

The plug-ins installed so far are:

Install one by one by yourself.

Generate vue-ssr-server-bundle.json, and use the webpack command

Everything is manual, familiar with webback;

Modify server.js

const express = require('express');
const chalk = require('chalk');

const server = express();
const serverBundle = require('./dist/vue-ssr-server-bundle.json')//* * NEW * * / /
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
    runInNewContext: false, // It is also known from the name that a new Context object is generated. By default, it is true. Changing to false is understood as a caching mechanism to improve the efficiency of the server
    template: require('fs').readFileSync('./index.html', 'utf-8'),
  })//* * NEW * * / /
server.get('*', (req, res) => {
    //res.set('content-type', "text/html");
    //res.end(`
    //<!DOCTYPE html>
    //<html lang="en">
    //    <head><title>Hello</title></head>
    //    <body >
    //    Hello < / div >
    //    </body>
   // </html>
   //Change to the following
   const context = {//The parameter here is not used yet, but this object still needs to be used. It needs to be the parameter of renderToString
    url:req.url
  }
    renderer.renderToString(context, (err, html) => {
      if (err) {
        res.status(500).end('Internal Server Error')
        return
      } else {
        res.end(html)
      }
    })
    `)
  })

server.listen(8080,function(){
    let ip = getIPAdress();
    console.log(`Server on: http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//The os module under the node can get part of the information of the server that starts the file. Go to the node for details
    var interfaces = require('os').networkInterfaces();
    for (var devName in interfaces) {
        var iface = interfaces[devName];
        for (var i = 0; i < iface.length; i++) {
            var alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                return alias.address;
            }
        }
    }
}

Try one: node server.js

Normally, the local official website indicated by the arrow has an explanation. Don't forget to add a line of comments to inde.html:

In the subsequent modification of title and meta header, similar annotation methods are used. The principle is regular matching replacement string -. -

>Join route Vue router

Add several new documents

The files to be modified are:

App.vue//Just add a router view

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

Throw out the app instance and router for entry-server.js

// entry-server.js
import { createApp } from './src/app'

export default context => {
   //There are many reasons for using promise here. One of them is that the following onReady method is asynchronous. Createbundlerender supports promise
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()

    router.push(context.url)

    router.onReady(() => {//The onReady method and the getMatchedComponents method still need to be understood
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      resolve(app)
    }, reject)
  })
}

Finally, take a look at router.js

//router.js
 import Vue from 'vue'
 import VueRouter from 'vue-router'
//The page should be declared before use, do not ask why
import home from './pages/home'
import store from './pages/store'

Vue.use(VueRouter)
export default new VueRouter({
    mode: 'history',
    routes:[
        {path:'/',name:'home',component:home},
        {path:'/store',name:'store',component:store},
    ]
})

Take a look at the code of the two pages;

    //store.vue 
    <template>
    <div>this is store</div>
    </template>
    <script>
         export default {}
    </script>

It's almost changed. Have a try:

Retype the package webpack --config webpack.server.js

Start node server

>What does entry-client.js do

So far, entry-client.js has not been used to call client configuration. Don't worry about using it. First, do a test and write some logic to try:
Modify store.vue

//store.vue
<template>
<div @click='run'>{{msg}}</div>
</template>
<script>
    export default {
        data(){
            msg:'this is store'
        },
        created(){
            this.msg = 'this is created'
        },
        mounted(){
            this.msg = 'this is mounted'
        },
        methods: {
            run(){
                alert('this is methods')
            }
        }
    }
</script>

It seems that the final display result of the page should be this is mounted, but the result is as follows:

It's easy to explain that the server's understanding of the hook function is also very correct. created will be executed before the page returns, and mounted will be executed after the vue instance is formed, that is, after the page is rendered, it will only be executed by the client. However, why the page comes out without being mounted, and the click event of run does not take effect;
Look at the page:

A js file is not loaded. How to execute the logic is a static page 0.0;
At this time, entry-client.js will appear

Add two files

//entry-client.js 
import { createApp } from './src/app.js';

const { app } = createApp();

app.$mount('#app');

Basic configuration;

//webpack.client.config.js

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

module.exports = merge(baseConfig, {
  entry: './entry-client.js',
  optimization:{
    runtimeChunk:true
  },
  plugins: [
    // This plug-in is in the output directory
    // Generate 'Vue SSR client manifest. JSON'.
    new VueSSRClientPlugin(),
  ]
})

In addition to VueSSRClientPlugin generating vue-ssr-client-manifest.json, optimization is a webpack4 product, which is used to separate and generate common chunk s. The configuration is quite complex. You can see here Summary of webback4 optimization

Modify server.js
//server.js

   const express = require('express');
    const chalk = require('chalk');

    const server = express();

    const serverBundle = require('./dist/vue-ssr-server-bundle.json')
    const clientManifest = require('./dist/vue-ssr-client-manifest.json')//Newly added
    const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
        runInNewContext: false, // Recommend
        template: require('fs').readFileSync('./index.html', 'utf-8'),
        clientManifest // / / add
      })
    server.get('*', (req, res) => {
        res.set('content-type', "text/html");
        const context = {
            url:req.url
          }

            renderer.renderToString(context, (err, html) => {
              if (err) {
                res.status(500).end('Internal Server Error')
                return
              } else {
                res.end(html)
              }
            })

      })

    server.listen(8080,function(){
        let ip = getIPAdress();
        console.log(`Server on: http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
    })

    function getIPAdress(){//The os module under the node can get part of the information of the server that starts the file. Go to the node for details
        var interfaces = require('os').networkInterfaces();
        for (var devName in interfaces) {
            var iface = interfaces[devName];
            for (var i = 0; i < iface.length; i++) {
                var alias = iface[i];
                if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                    return alias.address;
                }
            }
        }
    }

Under packaging: webpack --config webpack.client.config.js

node server, take a look at the page

js has it, but why not? You can't click 0.0;
Have a look. The Austrian newspaper was wrong.

Unable to read static file;
Modify server.js and add a static file hosting:


I want to see others

Events also exist. The page hasn't changed. console will find that the value has changed, just lost the response. That's why we use vuex;

> accession to vuex

At first, I wanted to use this.$set method in the page, but it didn't work, and I couldn't rewrite this method for every value;

Add sotre.js

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

  export default new Vuex.Store({
    state: {
      msg: ''
    },
    actions: {
        setMsg ({ commit }, val) {
          commit('setMsg', val)
      }
    },
    mutations: {
        setMsg (state, val) {
        Vue.set(state, 'msg', val)//crux
      }
    }
  })

Very basic logic, the key is Vue.set, which adds a new type of response;
Modify app.js

//app.js
    import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'//Just add a store
export  function createApp(){
    const app = new Vue({
        router,
        store,
        render:h => h(App)
    })
    return {app,router}
}

Change store.vue to this

<template>
    <div @click='run'>{{msg}}</div>
</template>
<script>
    export default {
        data(){},
        created(){
            this.$store.dispatch('setMsg','this is created')
        },
        computed:{
            msg(){
                return this.$store.state.msg;
            }
        },
        mounted(){
            this.$store.dispatch('setMsg','this is mounted')
        },
        methods: {
            run(){
               alert('this is methods')
            }
        }
    }
</script>

Repackage. Think about it. If you modify the page, you only need to repackage the client. If you modify the app.js, you need to repackage both of them;

node server


This time, it's finished;

> summary

There are quite a lot of things to render on the server side, covering a wide range of fields, such as vue, webback, node, which have a huge and terrible ecosystem and need to learn a lot,
There are many pits, large pits and deep pits. There are still many problems to be solved

Asynchronous data loading; / / render the data obtained by some interfaces before returning html
 How to do seo optimization; / / it's an important reason to do server-side rendering. This is also the reason to deal with asynchronous data loading
 How to add cache;
Build the development environment; / / you don't want to manually type a package every time you change a line of code and restart 0.0
 How to implement partial page ssr? / / it is impossible for a project to render all pages on the server, which is too performance consuming and the server is under great pressure;

There are still many doubts:

For example, why does it lose its responsiveness? How should webback be configured..

Tags: node.js Vue JSON Webpack Java

Posted on Sat, 09 Nov 2019 05:02:28 -0500 by dsdsdsdsd