The overall architecture

The early stage of the plan

As the current project faces the C-side, the following issues need to be considered

  1. First screen render time
  2. SEO optimization
  3. Development efficiency (code portability, collaborative development environments…)

From these simple points, the following figure is constructed The front-end code applies the current mainstream react framework and works with server rendering in react to maximize the coverage of the above three points: 1,2,3. As for the development efficiency problem, because it involves multi-person collaboration and many scenarios need to carry out real machine test in the test environment, it is planned to adopt pM2 + Ngnix method to carry out the deployment application project, so as to achieve the simultaneous and parallel real machine test in multi-environment deployment. Pm2 + NgniX simple configuration will be described in the following chapters. First of all, let’s look at the front-end application project code.

Note ⚠️ This article is largely explained by code comments

Technology stack

First let’s take a look at the core tripartite libraries currently required (esLint, PostCSS and other optimized infrastructure libraries will be added later)

"Develdependencies ": {"babel-core": "^6.26.3", // "^7.1.5", // webpack babel-loader "babel-preth-ES2015 ": "^6.24.1",// override ES5" babel-preth-react ": "^6.24.1",// Handle JSX "babel-stage-0 ": "^6.24.1",// Various latest syntax can be used, of which the most 🐂🍺 can be used in JSX to write if else "clean-webpack-plugin": "^4.0.0-alpha.0", // Remove the old file "html-webpack-plugin": "^5.3.1", // automatically generate HTML file "nodemon": "^" 2.0.7, / / listening js file changes automatically run "uglifyjs - webpack - plugin" : "^" 2.2.0, / / compression code "webpack" : "^ 5.35.0", "webpack - cli" : "^4.6.0", "webpack-dev-middleware": "^4.1.0" "^ 0.21.1", / / request necessary HTTP library "express" : "^ 4.17.1", / / a lightweight application server "react" : "^ 17.0.2", "the react - dom" : "^ 17.0.2"}Copy the code

Let’s take a look at the general directory file (dist). Since we’re using Webpack-dev-Middleware in our test environment, this file will be injected into memory without being packaged.

Where server/index.js is our overall file entry for now we only give it to the test environment and the relevant codebase environment will be shown in a later section. The index.js code is as follows

Note ⚠️:babel-register must be imported in order for it to be effective, and not directly in the file where babel-Register is currently imported

// index.js
require('babel-register') ({presets: ['es2015'.'react'."stage-0"]});// All incoming code below it can be processed by configuring the presets above

if(process.env.NODE_ENV === 'production') {console.log('Not built yet')}else {
  require('./app');
}
Copy the code

Then we follow to see what is in the app. The app mainly specifies port 3000 for opening, and the configuration file of Webpack is packed. When we execute the sentence webpack(webpackConfig), a Compiler object will be generated. The Compiler module is the main engine for WebPack, and most user-facing plug-ins are first registered with Compiler.

import React from 'react';
import path from 'path';
import express from 'express';
import webpack from 'webpack';
import webpackConfig from '.. /webpack/webpack.config'
import webpackDevMiddleware from 'webpack-dev-middleware'

const app = express(); // Create an application instance
const port = 3000; // Listen on port 3000
const compiler = webpack(webpackConfig); // Get the Compiler object

app.use(webpackDevMiddleware(compiler, { // Use dev middleware to automatically refresh files after changes
  publicPath: webpackConfig.output.publicPath / / output
}));

app.use(function (err, req, res, next) { // Specializes in error output
  console.warn('Catch Exception in error handling', err);
  res.send('Internal error');
});

app.listen(port, () = > {
  console.log(`Example app listening at http://localhost:${port}`)});Copy the code

You can see that the most critical front-end code must be in webpackConfig, so let’s go to webpackConfig to take a look. The whole idea is that the server-side code is directly generated by.. The/SRC /server/index file entry is output by the React renderToString method and injected directly into the HTML generated from the template, but since some elements require click events and so on, So the idea is that the server will render once and the client will render again to bind click events and so on

import HtmlWebpackPlugin from'html-webpack-plugin'; // Build the HTML plug-in
import { CleanWebpackPlugin } from 'clean-webpack-plugin'; // Remove the last package plug-in
import UglifyjsWebpackPlugin from 'uglifyjs-webpack-plugin'; // Compress the code
import path from 'path';
import serverCode from '.. /src/server/index';// Server render code

export default {
  mode:'development'.// Development environment
  entry: {// Pack the entry
    client:'./src/client/index.js',},output: {filename:'js/[name].[chunkhash:8].js'.// Package exit named
    path:path.resolve(__dirname,'.. /dist'),
    publicPath: '/' // Change the server path line to its own domain name and remember to configure root at ngnix
  },
  module: {rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        loader:'babel-loader'.options: {
          presets: [
            'env' // Use bleed-preset -env for recoldage].cacheDirectory: true // Enable caching to reduce repeated file compilation and increase compilation efficiency}}},resolve: {extensions: ['.js'.'.json'.'.scss'].// The suffix is added by default if there is no suffix
  },
  plugins: [new CleanWebpackPlugin(),
    New GetJsFilesWebpackPlugin((jsArr)=>{// New GetJsFilesWebpackPlugin((jsArr)=>
    // console.log(' Start packing ')
    // }),
    new HtmlWebpackPlugin({ // Configure HTML file packaging
      template:'./template/index.html'.// Create HTML file templates that support EJS syntax
      title: 'Server render title'.body: serverCode, // The injection parameter name can be customized severCode for server rendering code
      inject: false.// No automatic js injection defaults to true because you want to customize the js chain position more
      showErrors: true // Enable error message injection
    }),
    new UglifyjsWebpackPlugin(),// The compressed code test environment does not need to be enabled].optimization: {// Optimize the configuration
    splitChunks: {// Split the code
      chunks: 'all'.// Split package type async split for asynchronously loaded code, all split for all
      minSize: 20000.cacheGroups: {// Split the cache code configuration
        defaultVendors: {test: /[\\/]node_modules[\\/]/.// Third party libraries are packaged to implement cache reuse
          priority: -10.reuseExistingChunk: true.// When a chunk references an existing chunk that has been removed, it is not created but reused
          name:'vendors' // Split the name, if this attribute is not added will cause the name to become extremely long
        },
        default: {
          minChunks: 2.// Multi-entry or asynchronous loading when a module is shared more than 2 times will be pumped
          priority: -20.// Which weight to execute when both are satisfied
          reuseExistingChunk: true,}}}},}Copy the code

Let’s take a look at the server rendering code. It’s pretty easy to just write the React component as we did, and then call the renderToString method. Note that everything that needs JS binding needs to be executed on the client again. And be careful to run component code on the server side without browser-specific objects such as document objects and their methods.

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from '.. /common/app/index';

// Let's take a look at the App component here
// class App extends React.PureComponent{
// handleClick=(e)=>{
// alert(e.target.innerHTML);
/ /}
// render(){
// return (
// 

// Hello yuxiansen // ); / /} // } export default renderToString(<App/>); Copy the code

Finally, the client code react-dom provides the hydrate method. The hydrate strategy is different from the Render strategy in that it does not patch the entire DOM tree, it only patches the text Content.

import React from 'react';
import { hydrate } from 'react-dom';
import App from '.. /common/app/index';

hydrate(<App/>.document.getElementById("root"));
Copy the code

Well, the detailed code and notes believe that you can also feel the charm of the server rendering speed to open the first screen locally, but in the actual business must be weighed, otherwise it will produce greater pressure on the server. Later, there will be a style separation, esLint specification injection, production packaging, and multi-environment deployment.