SPA (single-page architecture) schemes are very popular nowadays, but most websites still choose multi-page or single-page + multi-page hybrid architecture. Using Express, WebPack this article is a low-cost implementation of concepts including multi-page architecture, automatic refresh, and front and back end separation

On the first project

  1. git repo

    node-pages-webpack-hot

  2. The development of

    npm install
    npm install supervisor -g
    npm run start # Development environment, configure hot Reload
    npm run prod # Production environment
    npm run build Build the front-end production environmentCopy the code
  3. DEMO





    ezgif-2-5c3bbb985e.gif

  4. FE directory:





    Paste_Image.png

  5. SERVER directory:





    Paste_Image.png

In order not to waste your time, read the following:

  • Basic knowledge of Express, and a brief understanding of Node
  • webpackIntermediate understanding, this article useswebpack2implementation

1. Configure the FE terminal

The front-end configuration needs to achieve the following functions:

  • Multi-page architecture automatically generates entry and generates templates for each page through HTML-webpack-plugin. Layout template function is required to select any template engine (SWIg is used as the template engine in this paper)

  • Configure loaders with various file suffixes

  • Use HotModuleReplacementPlugin implement changes since the refresh

1.1 Automatic Entry Analysis

It is stipulated that each page must have a JS file with the same name as the entry of this page. The directory depth is variable, as shown in the figure below, which is decomposed into two entries:





Paste_Image.png

To achieve automatic fetching, glob is used to fetch all.js files and determine if there is.html with the same name. If there is, an entry is generated. If it is dev environment, more hotMiddlewareScript module is added

  // get all js files
  let files = glob.sync(config.src + '/**/*.js');
  let srcLength = config.src.length;

  let entrys = {};

  files.forEach(function (_file) {
    let file = path.parse(_file);
    let htmlFile = path.resolve(file.dir, file.name + '. ' + config.ext);

    // if has same name template file, it is a entry
    if (fs.existsSync(htmlFile)) {
      let pathIndex = file.dir.indexOf(config.src);

      if (config.dev == 'dev') {
        entrys[config.staticRoot + file.dir.slice(srcLength) + '/' + file.name] = [path.resolve(_file), hotMiddlewareScript];
      } else {
        entrys[config.staticRoot + file.dir.slice(srcLength) + '/'+ file.name] = path.resolve(_file); }}});return entrys;Copy the code

1.2 Automatic Generationhtml-webpack-pluginThe template

The essentials of generating a series of HTMLWebPackPlugins are as follows:

  • After obtaining all the.html files, determine whether there are corresponding entry files. If so, create HtmlWebpackPlugin

  • If the page is a Layout template, you need to inject the Common module generated by the CommonsChunkPlugin

Automatically generate HtmlWebpackPlugin code as follows:

  let htmls = [];

  // get all templates
  let files = glob.sync(config.src + '/ * * / *. + config.ext);
  let srcLength = config.src.length;

  files.forEach(function (_file) {
    let file = path.parse(_file);

    let chunks = [];
    let chunkName = config.staticRoot + file.dir.slice(srcLength) + '/' + file.name;

    // if has same name entry, create a html plugin
    let c = entrys[chunkName];
    c && chunks.push(chunkName);

    // layout will contains common chunk
    if (file.name == config.serverLayoutName) {
      chunks.push(config.staticRoot + '/common');
    }

    let plugin = new HtmlWebpackPlugin({
      filename: config.templateRoot + file.dir.slice(srcLength) + '/' + file.base,
      template: path.resolve(file.dir, file.base),
      chunks: chunks,
      inject: false
    });

    htmls.push(plugin);
  });

  return htmls;Copy the code

Due to the introduction of template extends support, inject=false will not automatically inject assets files

Write a webpack plug-in to inject the js assets and CSS assets of the page into:

two replacement text places, such as the page template:

{% extends'.. /base/base.html' %}

{% block title %}My Page{% endblock %}

{% block style %}<! --webpack_style_placeholder-->{%endblock%}

{% block head %}
  {% parent %}
{% endblock %}

{% block content %}
<p>This is just an home page!!</p>

<div class="color-area">
  clouds
</div>

<a href="/users/list">link page2</a>
{% endblock %}

{% block script %}<! --webpack_script_placeholder-->{%endblock%}Copy the code

After compilation and replacement:

{% extends'.. /base/base.html' %}

{% block title %}My Page{% endblock %}

{% block style %}<link rel="stylesheet" href="/static/page/home/home.css"/>{%endblock%}

{% block head %}
  {% parent %}
{% endblock %}

{% block content %}
<p>This is just an home page!!</p>

<div class="color-area">
  clouds
</div>

<a href="/users/list">link page2</a>
{% endblock %}

{% block script %}<script src="/static/page/home/home.js"></script>{%endblock%}Copy the code

1.3 Various Loader Configurations to extract the CSS

The Dev environment is configured with Webpack-hot-middleware so CSS cannot be extracted or hot updated

The loader-related configurations are as follows:

var extractInstance = new ExtractTextPlugin('[name].css');

if (config.env == 'dev') {
    var stylusLoader = [
      {
        loader: 'style-loader'
      },
      {
        loader: 'css-loader'
      },
      {
        loader: 'stylus-loader'
      }
    ];

    var cssLoader = [
      {
        loader: 'style-loader'
      },
      {
        loader: 'css-loader'
      }
    ];
  } else {
    var stylusLoader = extractInstance.extract(['css-loader', 'stylus-loader']);
    var cssLoader = extractInstance.extract(['css-loader']);
  }Copy the code

And put all loaders in the same file for maintenance:

  var rules = [
    {
      test: /\.styl$/,
      exclude: /node_modules/,
      use: stylusLoader
    },
    {
      test: /\.css$/,
      exclude: /node_modules/,
      use: cssLoader
    },
    {
      test: /\.html$/,
      use: {
        loader: 'html-loader'.        options: {
          minimize: false}}},... . ]Copy the code

1.4 Path Configuration

Unified control of generated templates, static file output directory, easy to combine various back-end architectures

const port = process.env.PORT || 8080;
const env = process.env.NODE_ENV || 'dev';

const CONFIG_BUILD = {
  env: env,
  ext: 'html'.// tempate ext
  src: path.resolve(__dirname, '.. /src'), // source code path
  path: env= ='dev' ? '/' : path.resolve(__dirname, '.. /dist'), // base output path
  templateRoot: 'templates'.// tempate output path
  staticRoot: 'static'.// static output path
  serverLayoutName: 'base'.// swig layout name , only one file
  publicPath: env= ='dev' ? ('http://localhost:' + port + '/') : '/'
}Copy the code

2. Configure the SERVER

The Express service is set up on the server and provides the following functions:

  1. Use Webpack-dev-Middleware for Webpack compilation

  2. Hot Reload was implemented using Webpack-hot-middleware

  3. The Supervisor service listens for node file changes and restarts automatically

  4. The render template writes files in memory to hard disk for rendering

2.1 WebPack access express

  • generatewebpackcompiler
  var webpack = require('webpack'),
    webpackDevConfig = require(path.resolve(config.root, './fe/webpack.config.js'));

  var compiler = webpack(webpackDevConfig);Copy the code
  • willcompilerAs aexpressThe middleware
  // attach to the compiler & the server
  app.use(webpackDevMiddleware(compiler, {
    // public path should be the same with webpack config
    publicPath: webpackDevConfig.output.publicPath,
    noInfo: false,
    stats: {
      colors: true
    }
  }));Copy the code

In the command, publicPath indicates the root path of the assets request. The value is http://localhost:8080/

2.2 hot reloadplan

2.2.1 Js, CSSSelf-refresh

Self-refresh of JS and CSS is implemented by configuring Webpack-hot-middleware (fe is also required).

  // server
  const webpackHotMiddleware = require('webpack-hot-middleware');
  app.use(webpackHotMiddleware(compiler));
  // fe
  webpackPlugins.push(
    new webpack.HotModuleReplacementPlugin()
  );Copy the code
2.2.2 nodeSelf-refresh

Node file changes are automatically refreshed by configuring the Supervisor service

Installation services:

npm install supervisor -gCopy the code

Configure startup parameters:

// package.json
"scripts": {
  "start": "cross-env NODE_ENV=dev supervisor -w server -e fe server/server.js"
}Copy the code

Supervisor monitors all changes in the server folder. After changes are made, the Express service restarts. To implement automatic browser refresh, add the following code to the layout template:

  {% if env == 'dev' %}
    <script src="/reload/reload.js"></script>
  {% endif %}Copy the code

2.3 Render the Template

When WebPack is used as express middleware, all files generated are stored in memory, including of course the template files generated by the HTML-webpack-plugin. Express’s render function, however, can only specify one template that exists in the file system, so the Dev environment requires the render template to be retrieved from memory and stored in the file system.

module.exports = (res, template) = > {if (config.env == 'dev') {
    let filename = compiler.outputPath + template;
    // load template from 
    compiler.outputFileSystem.readFile(filename, function(err, result) {
      let fileInfo = path.parse(path.join(config.templateRoot, filename));


      mkdirp(fileInfo.dir, () => {
        fs.writeFileSync(path.join(config.templateRoot, filename), result);

        res.render(template);
      });
    });
  } else {
    res.render(template); }}Copy the code

Layout template storage requires a middleware:

  app.use((req, res, next) => {
    let layoutPath = path.join(config.templateRoot, config.layoutTemplate);
    let filename = compiler.outputPath + config.layoutTemplate;

    compiler.outputFileSystem.readFile(filename, function(err, result) {
      let fileInfoLayout = path.parse(layoutPath);

      mkdirp(fileInfoLayout.dir, () => {
        fs.writeFileSync(layoutPath, result);
        next();
      });
    });
  });Copy the code

The rest are express basic use, just refer to the documentation

supplement

In this paper, a simple frame for front and rear end separation is constructed, but there are still many imperfections. Real online applications also need to consider nodeJS maintenance costs, logging, monitoring, and more.