Client-side Rendering Service-side Rendering and Code Implementation

1 Definition

1.1 Client Rendering

Prior to this, the page rendering process:

  • Browser requests an HTML text
  • Rendering process parses HTML text to build DOM tree
  • While parsing HTML, if you encounter inline styles or style scripts, download the build style rules, and if you encounter JavaScript scripts, download and execute them
  • The rendering process combines the DOM tree with the style rule to render the tree
  • Rendering process generates layout tree
  • Layer the layout tree, rasterize each layer to get a composite frame
  • Send synthetic frame information to GPU process to display in interface

At this time, when the JS script is executed, the HTML page has started to parse and construct the DOM tree. The knowledge of JS script dynamically changes the structure of the DOM tree. This is dynamic rendering, that is, client rendering. The feature of this is that the DOM tree is built by the browser, and background data files (such as JS) are constantly requested, and then the DOM tree is modified.

Advantage:

  • Separate front and back for high development efficiency
  • User experience is good, when a web page or part of the content is made into a SPA single page application, users will not click frequently to skip

Disadvantages:

  • Due to the need to stitch in the browser, the response from the front end will be slower, especially from the first screen.
  • Adverse to search engine optimization

1.2 Server-side Rendering

When a browser requests a url, the server assembles the HTML text it needs and returns it to the browser. After the HTML text is parsed by the browser, it does not need to be executed by the JS script to construct the desired DOM tree and display it to the page. This server assembles the HTML page to generate the DOM tree, which is called server rendering.

Advantage:

  • No front-end resources, less front-end time, fast response
  • For SEO optimization, complete HTML pages on the back end make it easier for crawlers to crawl information

Disadvantages:

  • Disadvantage of front-end and back-end separation, reduced development efficiency
  • For HTML parsing, the front end is faster, but at the same time increases server pressure

Service-side rendering and client-side rendering are often combined: the first screen is rendered on the server side, and the other pages are rendered on the client side. This ensures that the first screen loads faster and that front-end and back-end separation is completed.

Vue Server Rendering

Initial Simple Edition:

const Vue = require('vue');
const server = require('express')();
const renderer = require('vue-server-renderer').createRenderer();

server.get('*', (rep, res) => {

    //Create a Vue instance
    const app = new Vue({
        data: {
            url: rep.url
        },
        template: '<div>Currently accessed url Yes:{{url}}</div>'
    })

    //Create a renderer
    renderer.renderToString(app, (err, html) => {
        if (err) {
            res.status(500).end('Internal Server Error');
            return;
        }
        //Return HTML and insert an HTML fragment from the Vue instance
        res.end(
            `<!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">
                <title>Hello SSR</title>
            </head>
            <body>
                ${html}
            </body>
            </html>`
        )
    })
})

server.listen(8080,()=>{
    console.log("Server running on port 8080");
})

In the browser:

Source requested by browser:

How to use templates

Use of template method:
server.js

const fs = require('fs');

const Vue = require('vue');
const server = require('express')();
const VueServerRenderer = require('vue-server-renderer');

server.get('*', (rep, res) => {

    //Create a Vue instance
    const app = new Vue({
        data: {
            url: rep.url
        },
        template: '<div>The url currently accessed is:{{url}}</div>'
    })

//fs Get HTML Template File
    const template = fs.readFileSync('./index_template.html','utf-8');
//Incoming when constructing rendering instance
    const renderer = VueServerRenderer.createRenderer({
        template
    })

//Define Variables
    const context = {
        title: 'Vue SSR',
        meta: `
        <meta name="keyword" content="vue,ssr">
        <meta name="description" content="vue srr demo">
        `
    }

//Support for incoming variables
    renderer.renderToString(app,context, (err, html) => {
        if (err) {
            res.status(500).end('Internal Server Error');
            return;
        }
        res.end(html)
    })
})

server.listen(8080,()=>{
    console.log("Server running on port 8080...");
})

Using variables defined in the js file through interpolation in the template file
index_template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{title}}</title>
    <!-- Three braces need not be escaped -->
    {{{meta}}}
</head>
<body>
     <!--vue-ssr-outlet-->
</body>
</html>

Specific implementation


app.js is the entry file, rendering the imported app.vue file

import Vue from 'vue';
import App from './App.vue';

export function createApp() {
  const app = new Vue({
    render: h => h(App)
  })

  return { app }
}

Client entry file client-entry.js, load mounts Vue instance

import { createApp } from './app';

const {app} = createApp();

app.$mount('#app');

server-entry.js, the server-entry file, uses functions to ensure that each request is a new Vue instance in order to avoid contamination:

import { createApp } from "./app";

export default () => {
    const { app } = createApp();
    return app;
}



Then packaged with WebPack, the packaged ServerBundle file is rendered in BundleRenderer to generate HTML, but at this point the HTML is only a static resource, then ClientBundle is activated to interact dynamically

webpack configuration file:

  • Configuration common to webpack.base.js client JS files
const path = require('path');

const VueLoaderPlugin = require('vue-loader/lib/plugin');

const resolve = (dir) => {
  return path.resolve(__dirname, dir);
};

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

  • webpack.client.js introduces a common configuration through merge, with the main extra declaration entry file
const path = require('path');

const { merge } = require('webpack-merge');
const base = require('./webpack.base');

const resolve = (dir) => {
  return path.resolve(__dirname, dir);
};

module.exports = merge(base, {
  entry: {
    client: resolve('../src/client-entry.js'),
  }
})
  • The same is true for the webpack.server.js service side, which additionally declares the entry file, target declares node, and in the webpack HTML plug-in by declaring the target file and template
const path = require('path');

const { merge } = require('webpack-merge');
const base = require('./webpack.base');

const HtmlWebpackPlugin = require('html-webpack-plugin');

const resolve = (dir) => {
  return path.resolve(__dirname, dir);
};

module.exports = merge(base, {
  entry: {
    server: resolve('../src/server-entry.js'),
  },
  target: 'node',
  output: {
    libraryTarget: 'commonjs2',
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.ssr.html',
      template: resolve('../public/index.ssr.html'),
      excludeChunks: ['server'],
      minify: {
        removeComments: false, //Keep comment nodes in templates
      },
    }),
  ],
});
  • App.vue file
<template>
  <div id="app">
    <div class="demo" @click="onClick">
      A paragraph of content
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    onClick() {
      console.log('click!!!!!');
    }
  }
}
</script>

<style>
.demo {
  width: 300px;
  height: 300px;
  background-color: orange;
}
</style>
  • Template file index.ssr.html
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Vue SSR</title>
</head>
<body>
  <!--vue-ssr-outlet-->
</body>
</html>
  • package.json configuration file:
{
  "name": "vue-ssr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon server.js",
    "client:dev": "webpack-dev-server --config ./build/webpack.client.js --mode development",
    "client:build": "webpack --config ./build/webpack.client.js --mode production",
    "server:build": "webpack --config ./build/webpack.server.js --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "babel-loader": "^8.1.0",
    "css-loader": "^3.6.0",
    "html-webpack-plugin": "^4.4.1",
    "koa": "^2.13.0",
    "koa-router": "^9.4.0",
    "koa-static": "^5.0.0",
    "nodemon": "^2.0.4",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-server-renderer": "^2.6.12",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.1.4"
  }
}

  • server.js main file, using Koa framework
const fs = require('fs');
const path = require('path');

const Vue = require('vue');
const Koa = require('koa');
const KoaRouter = require('koa-router');
const serve = require('koa-static');
const VueServerRenderer = require('vue-server-renderer');

//Introducing packaged web pack template files and server-side Bundle files
const serverBundle = fs.readFileSync('./dist/server.bundle.js', 'utf-8');
const template = fs.readFileSync('./dist/index.ssr.html', 'utf-8');

//Pass Template and SerrBundle into Renderer
const renderer = VueServerRenderer.createBundleRenderer(serverBundle, {
  template
})

const app = new Koa();
const router = new KoaRouter();

router.get('(.*)', async ctx => {
  ctx.body = await renderer.renderToString(); //Decouple from Vue code, render and mount in Vue file
})

//Resolve absolute paths by path and import client bundle files by the use method of koa-static
app.use(serve(path.resolve(__dirname, 'dist')))

app.use(router.routes())

app.listen(8080, () => {
  console.log('Server running on port 8080...')
});

Manually import the client bundle.js file into the template file exported from the dist folder

Tags: Javascript html computer networks

Posted on Sun, 26 Sep 2021 12:18:17 -0400 by azsolo