Egg + Vue side rendering engineering services to achieve

Before implementing the egg + vue side rendering engineering services to achieve, let's take a look at the front and two on Webpack build Egg articles:

  • In  Webpack engineering solutions easywebpack  article we mentioned solutions are based on Vue built  easywebpack-VUE . Easywebpack-VUE supports pure front-end mode and Node floor building, which provides support for Vue server-side rendering, we only need a simple configuration the key entry and alias can be completed Vue build front-end rendering and Node floor building, which greatly simplifies the server-side rendering Vue construction work, allows us to achieve the above into the center server Vue rendering.
  • In  Egg + Webpack hot update implement  article we achieved through Message communication mechanisms Egg framework Webpack memory compilers hot update to achieve plug-  Egg-WebPACK , guarantee Node layer code changes the restart, Webpack compiled examples still exist for local development Node-level code modify and hot update provides support.

Vue server (Node) rendering mechanism

Vue from the official support of us know, Vue support server-side rendering, but also provides the official rendering plug-vue-server-renderer provides rendering based JSBundle or JSON file mode and stream rendering mode. Here we are mainly talking about the server-based rendering JSBundle implementation flow rendering mode currently in Egg Egg frame inside the plug part conflict (Header posting timing problems), the follow-up as a separate research topic. Also built on the venue and render Vue JSON file string  VueSSRPlugin  this program is currently based on the official Plugin Vue can only build a single page (generates a json manfiest, there will be multiple conflicts) in the construction of the above, the perfect solution to continue research needs .

 

First, let's take a look at vue-server-renderer provided createBundleRenderer and renderToString how to JSBundle compiled into HTML.

vue-server-renderer implemented based on the primary code JSBundle:

the require the renderer = const ( 'VUE-Server-the renderer' );
 // filepath Webpack to build server code 
const = bundleRenderer renderer.createBundleRenderer (filepath, RenderOptions);
 // Data Node end of the acquired data 
const context = { State: Data};
 return  new new Promise ((Resolve, Reject) => { 
  bundleRenderer.renderToString (context, (ERR, HTML) => {
   IF (ERR) { 
    Reject (ERR); 
  } the else { 
    Resolve (HTML); 
  } 
});

 

There just simply consider the compilation, for caching, resource dependence are not considered. In fact, in doing Vue server-side rendering, the key here is that the place, how to ensure the Vue rendering speed, but also to meet the actual needs of the project.

 

Cache

  • Currently createBundleRenderer method provides options extended parameters, provides cache interface, support for component-level cache, and then taking a step forward to support us page cache, which is based on the file to createBundleRenderer cached.
  • runInNewContext: By default, for each rendering, bundle renderer will create a new V8 context and re-execute the entire bundle. This has some advantages - for example, the application code and the server process isolation, we do not need to worry about the documents mentioned in the state of a single case of problems . However, this model has some significant performance overhead because recreate the context and implementation of the entire bundle is still quite expensive, particularly when applied great time. For backward compatibility reasons, this option defaults to true, but it is recommended that you use runInNewContext as: false or runInNewContext: 'once' (this information comes from Vue's official website: ). From a statistical analysis of the actual project also confirms the performance overhead issues here: runInNewContext = false can significantly increase render speed line from actual statistical point of view, runInNewContext = false can significantly improve render speeds more than three times (more than one module of 5 list page screen, render time when runInNewContext = true average in 60-80ms, render time when runInNewContext = false average 20-30ms).

 

Based on the above two points, we realized the  egg-view-vue  plug-in, provided Vue rendering engine. Egg project in which we can get examples Vue rendering engine by this.app.vue, then you can Vue compiled into HTML according to the methods provided.

  • Examples vue egg-view-vue exposed
const Engine = require('../../lib/engine');
const VUE_ENGINE = Symbol('Application#vue');

module.exports = {

  get vue() {
    if (!this[VUE_ENGINE]) {
      this[VUE_ENGINE] = new Engine(this);
    }
    return this[VUE_ENGINE];
  },
};

 

  • Vue View Engine Design and Implementation
'use strict';

const Vue = require('vue');
const LRU = require('lru-cache');
const vueServerRenderer = require('vue-server-renderer');

class Engine {
  constructor(app) {
    this.app = app;
    this.config = app.config.vue;
    this.vueServerRenderer = vueServerRenderer;
    this.renderer = this.vueServerRenderer.createRenderer();
    this.renderOptions = this.config.renderOptions;

    if (this.config.cache === true) {
      this.bundleCache = LRU({
        max: 1000,
        maxAge: 1000 * 3600 * 24 * 7,
      });
    } else if (typeof this.config.cache === 'object') {
      if (this.config.cache.set && this.config.cache.get) {
        this.bundleCache = this.config.cache;
      } else {
        this.bundleCache = LRU(this.config.cache);
      }
    }
  }

  createBundleRenderer(name, renderOptions) {
    if (this.bundleCache) {
      const bundleRenderer = this.bundleCache.get(name);
      if (bundleRenderer) {
        return bundleRenderer;
      }
    }
    const bundleRenderer = this.vueServerRenderer.createBundleRenderer(name, Object.assign({}, this.renderOptions, renderOptions));
    if (this.bundleCache) {
      this.bundleCache.set(name, bundleRenderer);
    }
    return bundleRenderer;
  }

  renderBundle(name, context, options) {
    context = context || /* istanbul ignore next */ {};
    options = options || /* istanbul ignore next */ {};

    return new Promise((resolve, reject) => {
      this.createBundleRenderer(name, options.renderOptions).renderToString(context, (err, html) => {
        if (err) {
          reject(err);
        } else {
          resolve(html);
        }
      });
    });
  }

  renderString(tpl, locals, options) {
    const vConfig = Object.assign({ template: tpl, data: locals }, options);
    const vm = new Vue(vConfig);
    return new Promise((resolve, reject) => {
      this.renderer.renderToString(vm, (err, html) => {
        if (err) {
          reject(err);
        } else {
          resolve(html);
        }
      });
    });
  }
}

module.exports = Engine;
 

 

Resource dependence

  • On page resource dependency we can combine Webpack of webpack-manifest-plugin plugin generates each page resource dependency table. Then in the render, we find the corresponding resource dependence based on the file name, and then incorporated into the specified location of HTML.
  • When the Vue rendering server, server-side rendering when we know that, just Vue compiled into HTML text, as the events page of binding and some of the browser initialization also need to deal with our own, and to deal with these, we also need to Vue template file data binding of the original data, so here we also need to deal with INIT_STATE unified data problems. Here we are in the render, unified data output by the script tag to the page. Here We  serialize-javascript  will be unified serialization. Note: Some sensitive data is output to the page do not generally recommended when the original data to get through the API, data cleansing, data Vue only the template file required threw render function.

Based on the above two points, we realized the  egg-view-vue-ssr  plug-solving resource dependence and data issues. The plug-in is based on  egg-view-vue  expansion comes, it will cover the render method. The current implementation will have a problem, see the specific  multi-engine problem  .

inject(html, context, name, config, options) {
    const fileKey = name;
    const fileManifest = this.resourceDeps[fileKey];
    if (fileManifest) {
      const headInject = [];
      const bodyInject = [];
      const publicPath = this.buildConfig.publicPath;
      if (config.injectCss && (options.injectCss === undefined || options.injectCss)) {
        fileManifest.css.forEach(item => {
          headInject.push(this.createCssLinkTag(publicPath + item));
        });
      } else {
        headInject.push(context.styles);
      }
      if (config.injectJs) {
        fileManifest.script.forEach(item => {
          bodyInject.push(this.createScriptSrcTag(publicPath + item));
        });
        if (!/window.__INITIAL_STATE__/.test(html)) {
          bodyInject.unshift(`<script> window.__INITIAL_STATE__= ${serialize(context.state, { isJSON: true })};</script>`);
        }
      }
      this.injectHead(headInject);
      html = html.replace(this.headRegExp, match => {
        return headInject.join('') + match;
      });

      this.injectBody(bodyInject);
      html = html.replace(this.bodyRegExp, match => {
        return bodyInject.join('') + match;
      });
    }
    return config.afterRender(html, context);
  }
 

Vue server (Node) building

At the beginning we mentioned  easywebpack-vue  constructing a scenario, we can do Webpack through the solution + Vue building program. See the specific implementation  Webpack engineering solutions easywebpack  and  easywebpack-vue plug. Here, we directly webpack.config.js configuration, rendering the distal end to complete the construction and Node Vue layer constructed according to the configuration.

'use strict';
const path = require('path');
module.exports = {
  egg: true,
  framework: 'vue',
  entry: {
    include: ['app/web/page', { 'app/app': 'app/web/page/app/app.js?loader=false' }],
    exclude: ['app/web/page/[a-z]+/component', 'app/web/page/test', 'app/web/page/html', 'app/web/page/app'],
    loader: {
      client: 'app/web/framework/vue/entry/client-loader.js',
      server: 'app/web/framework/vue/entry/server-loader.js',
  Aka: {
    }
  },
    server: 'app/web/framework/vue/entry/server.js',
    client: 'app/web/framework/vue/entry/client.js',
    app: 'app/web/framework/vue/app.js',
    asset: 'app/web/asset',
    component: 'app/web/component',
    framework: 'app/web/framework',
    store: 'app/web/store'
  }
};

 

 

Local development and online decoupling

We know that at the time of local development, we will use Webpack hot update feature. The heat Webpack updated implementation is based on memory compiler implementation.

When running on a line, we can build a good JSBundle directly read files, when local development, when rendering Egg server, how to get to JSBundle contents of the file, without coupling line of code.

Here we combine  Egg + Webpack hot update realize  which referred to the plug-  Egg-WebPACK  , The plugin provides app.webpack.fileSystem instance egg app context, we can get the contents of the file into memory Webpack compiled based on the file name. With this step, we are providing support for the development of local content in real time to read the file from the memory Webpack inside. As for the line of code is not coupled line of code we write separately about plug-ins, covering  Egg-View-VUE  Engine renderBundle method of exposure. See specific implementation implemented as follows.

if (app.vue) { const renderBundle = app.vue.renderBundle; app.vue.renderBundle = (name, context, options) => { const filePath = path.isAbsolute(name) ? name : path.join(app.config.view.root[0], name); const promise = app.webpack.fileSystem.readWebpackMemoryFile(filePath, name); return co(function* () { const content = yield promise; if (!content) { throw new Error(`read webpack memory file[${filePath}] content is empty, please check if the file exists`); } return renderBundle.bind(app.vue)(content, context, options); }); }; } 

Based on the above realization, we can encapsulate the  egg-webpack-vue  plug for Egg + Webpack + Vue local development model.

 

Project to build

With the above three rendering related Egg plug-ins and easywepback-vue build plug-ins, how to build a project based rendering Egg + Webpack + server Vue it?

You can project by  easywebpack-cli  directly to complete the initialization or clone  Egg-VUE-WebPACK-boilerplate . The following describes how to build a Egg + Webpack + Vue server-side rendering projects from scratch.

  • Project initialization egg by egg-init
egg-init egg-vue-ssr
// choose Simple egg app
npm i easywebpack-vue --save-dev
npm i egg-webpack --save-dev
npm i egg-view-vue --save
npm i egg-view-vue-ssr --save
  • Add Configuration
  1. /config/plugin.local.js added as follows $ {app_root}
exports.webpack = { enable: true, package: 'egg-webpack' }; exports.webpackvue = { enable: true, package: 'egg-webpack-vue' }; 

2. /config/config.local.js added as follows $ {app_root}

const EasyWebpack = require('easywebpack-vue'); // 用于本地开发时,读取 Webpack 配置,然后构建 exports.webpack = { webpackConfigList: EasyWebpack.getWebpackConfig() }; 
  • Configuring $ {app_root} /webpack.config.js
'use strict';
const path = require('path'); module.exports = { egg: true, framework: 'vue', entry: { include: ['app/web/page', { 'app/app': 'app/web/page/app/app.js?loader=false' }], exclude: ['app/web/page/[a-z]+/component', 'app/web/page/test', 'app/web/page/html', 'app/web/page/app'], loader: { client: 'app/web/framework/vue/entry/client-loader.js', server: 'app/web/framework/vue/entry/server-loader.js', } }, alias: { server: 'app/web/framework/vue/entry/server.js', client: 'app/web/framework/vue/entry/client.js', app: 'app/web/framework/vue/app.js', asset: 'app/web/asset', component: 'app/web/component', framework: 'app/web/framework', store: 'app/web/store' }, loaders: { eslint: false, less: false, // 没有使用, 禁用可以减少npm install安装时间 stylus: false // 没有使用, 禁用可以减少npm install安装时间 }, plugins: { provide: false, define: { args() { // 支持函数, 这里仅做演示测试,isNode无实际作用 return { isNode: this.ssr }; } }, commonsChunk: { args: { minChunks: 5 } }, uglifyJs: { args: { compress: { warnings: false } } } } }; 
  • Running locally
node index.js 或 npm start
  • Webpack compile the file to disk
// 首先安装 easywebpack-cli 命令行工具
npm i easywebpack-cli -g
// Webpack 编译文件到磁盘
easywebpck build dev/test/prod

Project Development

Side rendering service

Create a home directory in the app / web / page list below, home.vue file entry is automatically created Webpack according .vue file entry, embodied see  webpack.config.js

  • home.vue write interface logic, the root element for the layout (custom components, global registration, unified html, meta, header, body)
<template>
  <layout title="基于egg-vue-webpack-dev和egg-view-vue插件的工程示例项目" description="vue server side render" keywords="egg, vue, webpack, server side render"> {{message}} </layout> </template> <style> @import "home.css"; </style> <script type="text/babel"> export default { components: { }, computed: { }, methods: { }, mounted() { } } </script> 
  • Create a controller file home.js
exports.index = function* (ctx) { yield ctx.render('home/home.js', { message: 'vue server side render!' }); }; 
  • Add routing configuration
app.get('/home', app.controller.home.home.index); 

 

Front-end rendering

  • Create a controller file home.js
exports.client = function* (ctx) { yield ctx.renderClient('home/home.js', { message: 'vue server side render!' }); }; 
  • Add routing configuration
app.get('/client', app.controller.home.home.client); 

Please refer to the skeleton more practical items: Egg-VUE-WebPACK-boilerplate

 

Operating principle

 

Local operating mode

  • First execution node index.js or npm start start Egg application
  • Koa start service in Egg Agent inside, and start compiling Webpack service koa service inside
  • Mounting Webpack memory file reading method of reading the local file logical coverage
  • Worker Webpack compiled state monitor, detect Webpack compilation is complete, if not completed, the display Webpack compile Loading, if compilation is complete, the browser automatically opens
  • Webpack compiled, Agent sends a message to the Worker, Worker detected compiled automatically open the browser, Egg services available official

 

Local development server-side rendering page views

 

  • Browser, enter the URL request address, Egg then receives the request, then enter Controller
  • After acquiring the data layer Node (Node call Java backend API data interface via http / rpc mode), enter the template render process
  • 进入render流程后, 通过 worker 进程通过调用 app.messenger.sendToAgent 发送文件名给Agent进程, 同时通过 app.messenger.on 启动监听监听agent发送过来的消
  • Agent进程获取到文件名后, 从 Webpack 编译内存里面获取文件内容, 然后Agent 通过 agent.messenger.sendToApp 把文件内容发送给Worker进程
  • Worker进程获取到内容以后, 进行Vue编译HTML, 编译成HTML后, 进入jss/css资源依赖流程
  • 如果启动代理模式(见easywebpack的setProxy), HTML直接注入相对路径的JS/CSS, 如下:

页面可以直接使用 /public/client/js/vendor.js 相对路径, /public/client/js/vendor.js 由后端框架代理转发到webpack编译服务, 然后返回内容给后端框架, 这里涉及两个应用通信. 如下:

<link rel="stylesheet" href="/public/client/css/home/android/home.css"> <script type="text/javascript" src="/public/client/js/vendor.js"></script> <script type="text/javascript" src="/public/client/js/home.js"></script> 
  • 如果非代理模式(见easywebpack的setProxy), HTML直接注入必须是绝对路径的JS/CSS, 如下:

页面必须使用  绝对路径

<link rel="stylesheet" href="http://127.0.0.1:9001/public/client/css/home/android/home.css"> <script type="text/javascript" src="http://127.0.0.1:9001/public/client/js/vendor.js"></script> <script type="text/javascript" src="http://127.0.0.1:9001/public/client/js/home.js"></script> 

其中  是 Agent里面启动的Webpack编译服务地址, 与Egg应用地址是两回事

  • 最后, 模板渲染完成, 服务器输出HTML内容给浏览器

 

发布模式构建流程和运行模式

  • Webpack通过本地构建或者ci直接构建好服务端和客户端渲染文件到磁盘
  • Egg render直接读取本地文件, 然后渲染成HTML
  • 根据manfifest.json 文件注入 jss/css资源依赖注入
  • 模板渲染完成, 服务器输出HTML内容给浏览器.

 

相关插件和工程

 

 

 

转载于:https://www.cnblogs.com/hubcarl/p/7623325.html

Guess you like

Origin blog.csdn.net/weixin_34356138/article/details/93817359