First Identity Vue_Server-side rendering of SSR base

Keywords: Javascript Vue npm Webpack JSON

Resources

https://ssr.vuejs.org

Current Development Version

    "vue": "^2.6.11",
    "vue-router": "^3.3.4",
    "vue-server-renderer": "^2.6.11",
    "vuex": "^3.4.0"

concept

Server-side rendering: Render the vue instance as an HTML string directly back, activate as an interactive program on the front, old-fashioned; SSH can only return HTML strings and cannot activate.

The next step is to develop isomorphic (vue-like) rendering on the server side.

Current way of rendering on the server side

Traditional web development:

In traditional web development, web content is rendered directly on the server and transferred to the browser at one time.Get the html code directly from the database;

The disadvantage is: long server response time; bandwidth consumption, heavy load.This is what you see is what you get.

Single Page App: (spa)

Single use of excellent user experience, making it gradually mainstream, face content rendered by JS, this way is called client-side rendering.
The structure of the html is returned to the front end, but there is no content.Content is rendered HTML by the front-end library, vue or react; then an ajax request is sent requesting data to get data from the data to render.

Disadvantages: Not good for SEO, not good for search engines.If the data is not returned, the first screen will load slowly

Server Side Render Rendering

SSR solutions, back-end rendering returns the full first-screen dom structure or is developed using the vue or react template.What the front end gets includes the first screen (html structure) and the full spa structure (routing....) to make a route jump on the front end; the application still runs in spa mode after activation, which is called server side render.

Advantages of using SSR

  • seo: Search Engine Optimization
  • The first screen rendering content arrives at the time of one request for a response;

If a spa requests a response once, and an ajax requests data after opening the request, the speed is not that fast.

express

npm i express -S

Basic http service code demo:

// nodejs code
const express = require('express')

// Here is an example of getting express.
// You can see from the source code:
//Source path: /node_modules/@types/express/index.d.ts
// Declare function ():Core.Express; export = e is exported at the end; all executions of this function result in an instance
const server = express()

// Need to do routing, otherwise openHttp://localhost: 3000/port will error.
// Write routes for different url processing
// req request
// res response
server.get('/', (req,res)=>{
  res.send('3000') //Browsers will think html is returned
})
// Listening Port
server.listen(3000, ()=>{
  console.log('Executed')
})

Enter the current directory and execute the file, such as node 1-express-start.js;
Showing executed means the code is OK
The same is true for direct access ports.localhost:300

The node file is modified and needs to be run once each time to install the nodemon; the NPM install-g nodemon can be updated in real time; and the nodemon 1-express can be used when starting the nodeStart.js

Basic server-side rendering

Use server to make vue instance into HTML HTML string and return

Vue server renderer: vue-server-renderer**

NPM I vue-server-renderer-S or install Vue NPM I vue-server-renderer-S at the same time; make sure the versions are the same
WX20200615-142832@2x.png

  • After installation, test

Create the file first
There are three steps

 1. Create a vue instance
 2. Get a renderer instance
 3. Render vue instances with a renderer
 // Create vue instance
   const Vue = require ('vue')
   const app = new Vue({
      template:'<div>Hello</div>'
   })

 // Get a renderer instance
   const {createRenderer} = require('vue-server-renderer') // Get the factory function
   const renderer =createRenderer() // You get a renderer

 // Render vue instance with renderer
  // Returns promise, requiring.
 renderer.renderToString(app)
 .then((html)=>{
  console.log(html)
 })
 .catch((err)=>{
  console.log(err);
  
})

Results display:

data-server-rendered server-side rendered

What you need to do now is if the user refreshes the display and combines express

In the express get just now, just return the code written by vue above

// nodejs code
const express = require('express')

const server = express()
// Create vue instance
const Vue = require ('vue')
// Get a renderer instance
const {createRenderer} = require('vue-server-renderer') // Get the factory function
// Render vue instance with renderer
const renderer =createRenderer() // You get a renderer
server.get('/', (req,res)=>{
  // Render a new vue every time the user refreshes
  const app = new Vue({
    template:'<div>Hello~~~~Wow~~~</div>'
  })
  // Returns promise, requiring.
  renderer.renderToString(app)
  .then((html)=>{
    // Return results directly to the browser
    res.send(html)
  })
  .catch(()=>{
    // Return status on error 500
    res.status(500)
    res.send('Internal Server Error, 500')
  })
})
// Listening Port
server.listen(3000, ()=>{
  console.log('Executed')
})

Modify it to use data display

  const app = new Vue({
    template:'<div>{{context}}</div>',
    data(){
      return {
        context:'vvvvvue'
      }
    }

How do I interact?
Can I do this if I write directly on the server side?

    template:'<div @click="onClick">{{context}}</div>',
    data(){
      return {
        context:'vue-ssr'
      }
    },
    methods: {
      onClick(){
        console.log('Can I click on it?')
      }
    }

Page:

Binding did not succeed because:
It is not possible to convert to a string and send it to the front end.So you need to activate the process

Solve Routing, Homogeneous Problems

Route

npm i vue-router -s
In the absence of an SSR, a Router instance of an instance is returned.

In the case of server-side rendering, to avoid the problem of Router pollution, each request returns a new Router.

//As a factory function, a new router instance is returned each time a user requests it
export default function createRouter(){
 return new VueRouter({
   mode: 'history',
   base: process.env.BASE_URL,
   routes
 })
}

How can I get the route rendered by the server to the front end?
You need to understand the build process first:

Or do you need to package with a webpack?
There are two service-side entries: Server entry, Client enrty
Two packages will be generated - -------
Generate file: Server Bundle "Server bundle" is used for rendering the first screen of the service side (not the first page, what page is requested or what is the first screen), Client Bundle "Client bundle" is used for client activation (generated js code is attached to html, create a new vue instance, such as the implementation of the click event tested above) because the service side is passing in a string.Front end needs to be activated.

Code structure

src  
├── router  
├────── index.js # Route
├── store  
├────── index.js # Global state  
├── main.js # Create vue instance  
├── entry-client.js # Client Entry, Static Content Activated 
└── entry-server.js # Server Entry, First Screen Content Rendering
  • Main.jsAdjustments

src/main.js

import Vue from 'vue'
import App from './App.vue'
import createRouter from './router'

Vue.config.productionTip = false

// Get a separate vue instance per request
// `The caller is an entry-server (first screen rendering) which passes parameters as context objects`
export function createApp(context){
  const router = createRouter()
  const app =new Vue({
    router,
    context, // You can get some parameters with context
    render: h => h(App)
  }).$mount('#app')
  // Export app and router instances
  return {app, router}
}
  • Entry-Server.jsFirst Screen Rendering

src/entry-server.js

`// First Screen Rendering`
`// Code executed on the server side`
import {createApp} from './main'

// Create an instance of vue
`// The caller is renderer`
export default context =>{
  // In order for renderer to wait for the final result to be processed, the return ed should be a promiss
  return new Promise((resolve, reject)=>{
    // Create vue instance and routing instance
    const {app, router} =createApp(context)

    // To render the first screen, get the current url The renderer gets the current url
    // Jump to the first screen.
    // The source of the url.Is available from the request.Pass to renderer
    router.push(context.url) // Consider asynchronous task processing such as ajax requests on the current page.To wait for the asynchronous task to finish processing on the jump page

    // Listen for router ready, really asynchronous task completed
    router.onReady(()=>{
      //This method queues a callback and calls it when the route completes its initial navigation, which means it can resolve all asynchronous incoming hooks and the asynchronous components associated with route initialization.
      //This can effectively ensure that both server and client outputs are consistent when rendering on the server side.
      resolve(app)
    }, reject) // Failure function handling as onReady event
  })
}
  • Entry-Client.jsClient Activation

src/entry-client.js

`// Client activation is client interaction such as click
// Code Executed in Browser`
import {createApp}~~~~ from './main'
// Create vue instance
const {app, router}  =createApp()

//Wait for router to be ready
router.onReady(()=>{
  //Mount Activation
  app.$mount('#app')
})

Both entry-server and entry-client are usedMain.jsThe createApp rendering instance in must have an instance of vue;

webpack packaging
  • Installation Dependency
npm install webpack-node-externals lodash.merge \-D
  • Create aVue.config.jsIn the root directory
// Two plug-ins are responsible for packaging the client and the server
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");
// Determine entry files and corresponding configuration items based on incoming environment variables
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";

module.exports = {
  css: {
    extract: false
  },
  outputDir: './dist/'+target, // Output path target see above judgment
  configureWebpack: () => ({ // enter path
    // Point entry to the application's server / client file
    entry: `./src/entry-${target}.js`, // Entrance
    // Provide source map support for bundle renderer
    devtool: 'source-map',
    // target is set to node so that the webpack handles dynamic imports in the way Node applies.
    // It also tells `vue-loader` to output server-oriented code when compiling a Vue component.
    target: TARGET_NODE ? "node" : "web",
    // Whether to simulate node global variables
    node: TARGET_NODE ? undefined : false,
    output: {
      // Export module using Node style here
      libraryTarget: TARGET_NODE ? "commonjs2" : undefined
    },
    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // Externalized applications depend on modules.This allows the server to build faster and generate smaller packaged files.
    externals: TARGET_NODE
      ? nodeExternals({
          // Do not externalize the dependent modules that the webpack needs to handle.
          // You can add more file types here.For example, the original *.vue file was not processed.
          // Dependency modules that modify `global'(such as polyfill) should also be whitelist
          whitelist: [/\.css$/]
        })
      : undefined,
    optimization: {
      splitChunks: undefined
    },
    // This is a plug-in that builds the entire output of the server into a single JSON file.
    // Server-side default file name is `vue-ssr-server-bundle.json`
    // Client default file name is `vue-ssr-client-manifest.json`.
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  chainWebpack: config => {
    // cli4 Project Add
    if (TARGET_NODE) {
        config.optimization.delete('splitChunks')
    }
      
    config.module
      .rule("vue")
      .use("vue-loader")
      .tap(options => {
        merge(options, {
          optimizeSSR: false
        });
      });
  }
};
Package.jsonAdjust
  • Installation Dependency:
npm i cross-env \-D
  "scripts": {
    "build": "npm run build:server & npm run build:client",
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"
  },
Execute npm run build for packaging

  • dist file

  • Index.htmlHost File

public/index.html

The format of the comment is agreed upon. Do not add spaces

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
  </head>
  <body>
      <!--vue-ssr-outlet-->
  </body>
</html>
  • Adjust the node file in the server

server/4-ssr.js

const express = require('express')
const app = express()

// Static Resource Service
// Open this path (.. /dist/client) so that users can download files
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
// Relative paths are unreliable (.. /dist/client), absolute paths are required
app.use(express.static(resolve('../dist/client'),{index: false}))//Specify the root directory and develop it for users to see
//The purpose of the {index: false} setting is because there is aIndex.html; so instead of going through the code below, you just go back to the client index.html

// Renderer: bundleRenderer, which gets the two json files generated earlier
const { createBundleRenderer } = require('vue-server-renderer')
//Point to absolute path
const bundle = resolve('../dist/server/vue-ssr-server-bundle.json')
//Get a renderer that can directly render vue instances
const renderer = createBundleRenderer(bundle, {
  // option
  runInNewContext: false, // Https://ssr.vuejs.org/zh/api/#runinnewcontextDocument Address
  // Host File
  template: require('fs').readFileSync(resolve("../public/index.html"), "utf-8"), // Convert host file utf-8 to string
  clientManifest: require(resolve("../dist/client/vue-ssr-client-manifest.json")) // Client Inventory Optimizations
})

app.get('*',async(req,res)=>{
   const context = {
     url: req.url
    }
  try{
    // Rendering Get html
    // Create vue instance Create first screen rendering is now static and non-interactive
    const html = await renderer.renderToString(context)
    res.send(html)//Send to front-end interactive entry-client to render as soon as it hangs
  }catch(error){
    res.status(500).send('Internal Server Error')
  }
})

app.listen(3001)
  • Code Test Results


Success!!!

(Note) To the current development progress, each project modification requires the npm run build and start node file again

Integrate vuex

Install vuex
`npm install -S vuex`  
If you are creating a project with vue-cli, you can use 
vue add vuex installation, results are the same
  • Generate store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
// `Need to be independent`
export default function createStore(){
  return new Vuex.Store({
    state: {
      count:100
    },
    mutations: {
      add(state){
        state.count+=1
      }
    },
    actions: {
    },
    modules: {
    }
  })
}
  • stayMain.jsHang in sotre
import Vue from 'vue'
import App from './App.vue'
import createRouter from './router'
`import createStore from './store'`

Vue.config.productionTip = false

// Get a separate vue instance per request
// Caller is entry-server (first screen rendering) passing parameter is context object
export function createApp(context){
  const router = createRouter()
  const store = createStore()
  const app =new Vue({
    router,
     // You can get some parameters with context
    context,
    store, `// Mount`
    render: h => h(App)
  }).$mount('#app')
  // Export app and router instances
  return {app, router, store}
}
  • stay Hello.vue Add in

Test the import of vuex

<p @click="$store.commit('add')">{{$store.state.count}}</p>
Data prefetching

Server-side rendering is a "snapshot" of the application, and if the application relies on some asynchronous data, it needs to be prefetched and parsed before rendering can begin.
Prepare the data on the server side and render the page with the data.

  • First in store/Index.jsInitialization of adding a data to
mutations: {
  // Add Initialization Data
  init(state,count){
    state.count =count
  }
},
  • After that in sotre/Index.jsInitiate an asynchronous request using actions to mimic the request from an interface
    actions: {
      `// An asynchronous request for data triggers init to mimic an interface`
      getCount({commit}){
        return new Promise((reslove)=>{
          setTimeout(() => {
            commit('init', Math.random()*100) // Generate random numbers as initial values
            reslove()
          }, 1000);
        })
      }
    },
  • Next, do the data prefetching logic on the routing page where you need to invoke the dataHello.vue
export default {
  name: 'Hello',
  asyncData({store, router}) {
  console.log(router, 'asyncDtata-router')
    return store.dispatch('getCount')
  }
}

AsyncData allows you to get data asynchronously before rendering components.The asyncData method is called before each load of the component (page component only).It can be invoked on the server side or before routing updates.

  • First screen processing data in entry-server.js
// First Screen Rendering
//Code executed on the server side
import {createApp} from './main'

//Create an instance of vue
// Caller is renderer
export default context =>{
  // In order for renderer to wait for the final result to be processed, the return ed should be a promiss
  return new Promise((resolve, reject)=>{
    // Create vue instance and routing instance
    `const {app, router,store} =createApp(context)`

    // To render the first screen, get the current url The renderer gets the current url
    // Jump to the first screen.
    // The source of the url.Is available from the request.Pass to renderer
    router.push(context.url) // Consider asynchronous task processing such as ajax requests on the current page.To wait for the asynchronous task to finish processing on the jump page
    // Listen for router ready, really asynchronous task completed
    router.onReady(()=>{
      //This method queues a callback and calls it when the route completes its initial navigation, which means it can resolve all asynchronous incoming hooks and the asynchronous components associated with route initialization.
      //This can effectively ensure that both server and client outputs are consistent when rendering on the server side.

      `// Processing asynchronous data first, then storing it in rendering
      // So you need to match whether the asyncData option exists in the build
      const matchedComponents =router.getMatchedComponents() // Get URLs that match all matching build arrays`

      //User is a mistyped address, which may cause matchedComponents to get Error 404
      if(!matchedComponents.length){
        return reject({code:404})
      }

      `//You need to iterate through the array to see if the group matches asyncData`
      Promise.all(
        matchedComponents.map(comp =>{
          // See if the build matches asyncData
          if(comp.asyncData){  
            //  Pass store to find actions corresponding to dispatch
            // router is passed if url is followed by a parameter &wd=vue
            return comp.asyncData({store,route:router.currentRoute})// The asynchronous call therefore returns a promise, which returns an array of promises each time
          }
        })).then(()=>{
          // Placing data on the front end of the store does not know this step, so you need to notify the front end
          //Next, make an appointment
          // After all prefetched data resolve s
          //  store has been entered to populate the current data state
          //Data needs to be synchronized to the front end
          // Serialization operation, converted to string front end using window. uINITIAL_STATE_uObtain
          // Assign toContext.state; is a convention,
          context.state = store.state
          resolve(app)
        }).catch(reject) // Catch Exceptions
    }, reject) // Failure function handling as onReady event
  })
}

In entry-client.js
Restore store

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

if(window.__INITIAL_STATE__){
  console.log(window.__INITIAL_STATE__, 'window.__INITIAL_STATE__');
  // Restore state
  store.replaceState(window.__INITIAL_STATE__)
}



When rendered directly by the server, a string is generated directly after deserialization and inserted here, which becomes an object as soon as it is executed on the front end.

  • About as the first page, refreshed and returned to home page, asyncData did not execute

If, the route switches toHttp://localhost: 3001/about, when you refresh in About and cut back to the home page, you will not walk the server's data, but the local state data.
About refresh results:

Cut it back like this:

problem

It only solves the problem of loading data on the first screen, not the problem of routing switching on the client side.
If the configuration item asyncData is also found in the client's build, it also needs to be executed

thinking

Join global mixin
stayMain.jsMedium mixed mixin

// Add global mixin
// Mix into beforeMount hooks
// beforeMount will not be triggered on the server because it renders pages directly to the server and Dom does not hang, so this hook will not be triggered
// So beforMount will only be executed on the client side
Vue.mixin({
  beforeMount() { // The vue instance already exists when this hook executes and has been mounted on the front end.So you can get sotre from this
    const {asyncData} =this.$options
    if(asyncData){
      //Call as it exists
      asyncData(
        {
          store: this.$store, // this refers to the vue instance
          route: this.$route
        }
      )//Pass parameter here
    }
  },
})

OK~That's where the ssr server rendering for first-time vue comes in.
There may be mistyping, but it does not affect the transfer of knowledge, Ha-Ha-Ha-Ha!
If you have any questions, please leave a message at any time. We will discuss and make progress together. Thank you.

Go on with Yes ok!

learning resource

Native: vue ssr https://ssr.vuejs.org/zh/
Framework:nuxt.js https://nuxtjs.org/
github: https://github.com/speak44/ss...

Posted by LikPan on Tue, 16 Jun 2020 09:21:51 -0700