There was a wave in nineteen nineteen, for details, see Part I. Two years later, there are more modules, and the computer has become stuck. Although the package is delivered to the server of the company’s internal publishing platform, the packaging time of more than 3 minutes is a torment for testing and development. In addition, there was a long wait of more than ten seconds for the hot update of the modified code. Considering that the browser often broke the Websocket from time to time, and the page could only be refreshed manually, it often took more than 20 seconds to see the effect of the code modification. It’s time to do it again. The opportunity came from the nuggets saw this article: Webpack combat from Roadhog2.x to Webpack 4.x. As the author says, a lot of hair was lost.

The frame was built in 2017, and Roadhog + DVA + ANTD was pretty hip at the time. However, I was busy with subsequent iterations and neglected the upgrade of several key libraries. As a result, it has become old and expensive. We can only tinker with the original base, but the improvement is limited after all.

Environment before transformation:

  • Dva: 1.2.1
  • Roadhog: 1.3.4
  • Webpack: 3.12.0
  • Babel – core: 6.26.0

As you can see, the Webpack version is not that low. The problem is with Roadhog.

What is Roadhog?

Here’s what the official document says:

Roadhog is a command line tool that includes dev, build, and test. It is based on react-dev-utils, which is consistent with the creation-react-app experience. You can imagine it as a configurable version of create-React-app. Here are the features:

  1. Out of the box React application development tool, built-in CSS-Modules, Babel, postCSS, HMR, etc
  2. The experience of the create – react – app
  3. Webpack configuration in JSON format
  4. mock
  5. Jest based tests, including UI tests

Items 2 and 3 are still nice enough to quickly skip through a series of complex WebPack configurations right out of the box. Webpack can also be extended through the Roadhogrc configuration file, and webpack.config.js can be built to extend roadhog’s own configuration. Yet the shortcomings of this approach are stark.

  • First, Roadhog comes with its own WebPack configuration, which is invisible to the user and can only be extended in limited ways.
  • Second, there are new features in later versions of WebPack that may not be supported by roadhog’s configuration, and changing the default configuration items through self-built webpack.config.js is mostly ineffective.
  • Finally, the author moved to UMI after the 2.0 update and no longer maintained it.

All of this leads to unbearably slow packaging as the number of project modules increases. There are also a number of articles on the web about improving roadhog compilation and packaging speed, as we did in the previous article. To solve the problem once and for all, I decided to ditch Roadhog and embrace Webpack 4.x.

Transformation process

What does Roadhog output look like?

Through vscode debugging combined with the source code, roughly clear the principle of roadhog. Meanwhile, the final webpack configuration output of development and production in the project is saved as a reference for subsequent modifications.

The original configuration consists of the following parts:

  1. entry

    Import file. Webpack4 defaults to ‘./ SRC /index.js’, which can now be omitted.

  2. output

    Output file directory, this does not need to move, as the original configuration.

  3. module

    For file parsing, configure different parsing rules and specify different loaders for different types of files. Type files can be parsed by multiple Loaders, and they can be combined in order. See the official document for details.

    This is the same configuration as before, except that the babel-loader configuration for JS parsing is put into bablerc, which is also built into Roadhog.

  4. plugins

    The WebPack plugin is used to provide hooks for processing at different stages of the WebPack processing life cycle. There are two main parts, one is webpack built-in plug-in, one is NPM installed plug-in. The following plug-ins were used in the old configuration:

    • HotModuleReplacementPlugin: hot update plug-in
    • DllReferencePlugin: precompiled plug-in
    • DefinePlugin: Variable definition plug-in
    • HtmlWebpackPlugin: HTML file processing plug-in
    • LoaderOptionsPlugin: loader configuration plug-in

    This section is filtered and added according to the actual situation. For details, see the configuration details below. Because Webpack4 deprecates some plugins, some plugins are built in as well. Please refer to official documents for details.

  5. devtool

    sourceMap

  6. externals

    Library files that do not need to be edited

  7. devServer

    Webpack starts the configuration items of the service, which are also largely untouched.

First try

In general, on the basis of the original project configuration, according to the webpack actual practice from Roadhog2. x to webpack4.x adjustment, mainly babel-loader plug-in, CSS extraction using MiniCssExtractPlugin, And upgrade Babel to 7X or above as needed. Of course, the results did not go so well, and encountered the same problems as in this article:

  1. There is a conflict between style-loader and mini-CSs-extract-plugin

    This official document also says use the latter.

  2. The style of ANTD is not loaded

    Previous configuration:

    ["import", { "libraryName": "antd"."libraryDirectory": "es"."style": true }]
    Copy the code

    Change the style from true to CSS.

  3. Export the default failure

    Webpack4 does not support the following import method.

    // a.js
    var a = b = 1;
    export default {
      a,
      b
    };
    
    // c.js
    import {a} from 'a.js';
    Copy the code

    It must be changed to the following:

    // a.js
    export const a = 1;
    export const b = 1;
    Copy the code

    After this change, the compilation does not report errors, but the page content is blank and the route does not feel mounted.

    I tried a lot of things but nothing worked. I think the Babel patch was upgraded to 7.0 or above. There are also many changes from Roadhog 1x to 2x, so the configuration of webpack from Roadhog 2.x to Webpack 4.x is only a partial reference.

Second attempt

After a bit of Internet surfing, I finally found a code base related to our project. Babel-core is the same as our version, everything else is pretty much the same, the trial page is out, the route is also available, the only problem is that the custom ANTD theme doesn’t work. This place is stuck on me for a long, long time. The problem is the less-loader part.

Antd theme Settings website introduction, 2 x. Ant. The design/docs/react /… Here’s what the document says:

Styles must load less format.

If you are using the babel-plugin-import style configuration to introduce styles, you need to change the configuration value from ‘CSS’ to true, which will introduce less files.

Finally, we found that the antD less file and the project less file in the code base are divided into two Loaders for processing. It suddenly hit me, because the styles in our project were divided into two main parts:

  • One part is the antD component style, class like “ant-btn”;

  • The other part is our own style, class like “container___3nzXj”. In order to avoid contamination, we use CSS modules for our own styles, written as follows:

    import React from 'react';
    import style from './App.css';
    
    export default() = > {return (
        <h1 className={style.title}>
          Hello World
        </h1>
      );
    };
    Copy the code

Antd does not, so it needs to be split into two loaders. At the same time we inject the subject variable values into antD’s less-loader configuration. The loader configuration of Less is as follows:

{
  test: /\.less$/,
  include: [/antd/].use: [
    'style-loader'.'css-loader',
    {
      loader: 'postcss-loader'.// Automatically prefix
      options: {
        plugins: [
          require('autoprefixer') ({overrideBrowserslist: ['last 5 version'],}),]},}, {loader: 'less-loader'.options: {
        javascriptEnabled: true.// Topic variable injection
        modifyVars: {  
          'primary-color': '#2171FF'.'link-color': '#2171FF'.'border-radius-base': '2px'.'font-size-base': '14px'.'animation-duration-slow': '.2s'.'animation-duration-base': '.2s'.'input-height-lg': '30px'.'btn-height-lg': '30px'.'label-color': '# 222'.'btn-font-size-lg': '14px'.'menu-dark-bg': '# 001529'.'menu-dark-submenu-bg': '# 000',},},},],}, {test: /\.less$/,
  exclude: [/antd/].use: [
    'style-loader',
    {
      loader: 'css-loader'.options: {
        importLoaders: 1.sourceMap: true.modules: true.localIdentName: '[local]___[hash:base64:5]',}}, {loader: 'postcss-loader'.options: {
        plugins: [
          require('autoprefixer') ({overrideBrowserslist: ['last 5 version'],}),],},}, {loader: 'less-loader'.options: {
        javascriptEnabled: true,},},],}Copy the code

The configuration of the development environment is now complete. The configuration of Production is largely unchanged. The only thing to note is that Webpack has removed the CommonsChunkPlugin and needs to use it instead with the splitChunks option under the Optimization configuration.

  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          name: 'vendor'.test: /[\\/]node_modules[\\/]/,
          chunks: 'all'.priority: 10./ / priority}},}},Copy the code

We split the configuration into three files: base configuration, dev configuration for development environment, prod configuration for production environment, and merge configuration for different environments through webpack-Merge.

Detailed configuration

base

/ / public
const path = require('path'); // Node.js contains path parameters
const APP_PATH = path.resolve(__dirname, '.. /src'); // Source file directory
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
constdevMode = process.env.NODE_ENV ! = ='production';

module.exports = {
  entry: {
    index: './src/index.js',},module: {
    rules: [{exclude: [
          /\.(html|ejs)$/./\.(js|jsx)$/./\.(css|less|scss|sass)$/./\.json$/./\.svg$/./\.tsx? $/,].loader: 'url-loader'.options: { limit: 10000.name: 'static/[name].[hash:8].[ext]'}, {},test: /(\.js|\.jsx)$/,
        use: {
          loader: 'babel-loader'.options: {
            cacheDirectory: true,}},include: APP_PATH,
        exclude: /node_modules/}, {test: /\.css$/,
        use: [devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.less$/,
        include: [/antd/].use: [
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          {
            loader: 'postcss-loader'.// Automatically prefix
            options: {
              plugins: [
                require('autoprefixer') ({overrideBrowserslist: ['last 5 version'],}),],},}, {loader: 'less-loader'.options: {
              javascriptEnabled: true.modifyVars: {
                'primary-color': '#2171FF'.'link-color': '#2171FF'.'border-radius-base': '2px'.'font-size-base': '14px'.'animation-duration-slow': '.2s'.'animation-duration-base': '.2s'.'input-height-lg': '30px'.'btn-height-lg': '30px'.'label-color': '# 222'.'btn-font-size-lg': '14px'.'menu-dark-bg': '# 001529'.'menu-dark-submenu-bg': '# 000',},},},],}, {test: /\.less$/,
        exclude: [/antd/].use: [
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader'.options: {
              importLoaders: 1.sourceMap: true.modules: true.localIdentName: '[local]___[hash:base64:5]',}}, {loader: 'postcss-loader'.options: {
              plugins: [
                require('autoprefixer') ({overrideBrowserslist: ['last 5 version'],}),],},}, {loader: 'less-loader'.options: {
              javascriptEnabled: true,},},],}, {test: /\.html$/,
        loader: 'html-loader'.options: { name: '[name].[ext]'}, {},test: /\.svg$/,
        loader: 'file-loader'.options: { name: 'static/[name].[hash:8].[ext]'}},],},plugins: [
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '.. /public'),}, {from: 'src/assets'.to: 'assets',},]),],resolve: {
    extensions: [
      '.web.js'.'.web.jsx'.'.web.ts'.'.web.tsx'.'.js'.'.json'.'.jsx'.'.ts'.'.tsx',]},externals: { echarts: 'echarts'}};Copy the code

dev

/ / development
const path = require('path');
// const webpack = require('webpack');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const DIST_PATH = path.resolve(__dirname, '.. /dist');

module.exports = merge(baseWebpackConfig, {
  mode: 'development'.// Source error check
  devtool: 'cheap-module-eval-source-map'.output: {
    filename: '[name].js'.path: DIST_PATH,
    publicPath: '/'.// Resolve multiple routing errors
    libraryTarget: 'var'.chunkFilename: '[name].async.js',},plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve('src'.'index.ejs'),
      inject: 'body'.minify: {
        html5: true,},hash: false.isDev: true,})],devServer: {
    port: 9001.contentBase: path.resolve('src'.'public'),
    compress: true./ / enable gzip
    historyApiFallback: true.hot: true./ / open
    https: false.noInfo: true.disableHostCheck: true.stats: {
      modules: false.assets: false.entrypoints: false.cachedModules: false.cachedAssets: false.children: false.chunks: false.chunkGroups: false.chunkModules: false.chunkOrigins: false.warnings: false,}}});Copy the code

prod

/ / production
const path = require('path');
const merge = require('webpack-merge'); // Merge the configuration
const baseWebpackConfig = require('./webpack.base.conf');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); // Copy static resources such as images, fonts, etc
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const DIST_PATH = path.resolve(__dirname, '.. /dist');

module.exports = merge(baseWebpackConfig, {
  mode: 'production'.//mode is the new mode of webpack4
  stats: {
    all: false.timings: true.// Time analysis
    assets: true.// Outputs the final package file
    errors: true.// Output content when an error is encountered
    warnings: false./ / silent warning
    moduleTrace: true.// Locate the file when an error is encountered
    errorDetails: true.// Output specific errors
  },
  output: {
    filename: '[name].[chunkhash:8].js'.path: DIST_PATH,
    publicPath: '/business/'.libraryTarget: 'var'.chunkFilename: '[name].[chunkhash:8].async.js',},plugins: [
    new CleanWebpackPlugin(['.. /dist'] and {allowExternal: true }), // Delete the dist file
    new MiniCssExtractPlugin({
      filename: '[name].[chunkhash:8].css'.chunkFilename: '[name].[chunkhash:8].css'.ignoreOrder: true,}).new HtmlWebpackPlugin({
      template: path.resolve('src'.'index.ejs'),
      filename: 'index.html'.minify: {
        removeComments: true./ / comment
        collapseWhitespace: true./ / space
        removeAttributeQuotes: true./ / move quotes}}),new CopyWebpackPlugin([
      {
        from: 'src/assets'.to: 'assets',},]),],optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          name: 'vendor'.test: /[\\/]node_modules[\\/]/,
          chunks: 'all'.priority: 10./ / priority},},},},});Copy the code

Extensive research

HappyPack and pre-compiled dllPlugin have been tried, but the speed increase is negligible, so don’t use it for now.

results

Compilation time increased from 40-50s to 20-30s, and hot update compilation time increased from 10-20s to 2-3s. The packing speed has been increased from about 300s to less than 100s. Packing volume reduced by nearly half. Most importantly, the speed of page access has become so fast.

Afterword.

Have to say, Webpack configuration is still very hurt brain, each configuration items may affect each other, some errors are very mysterious. But if you compare the official documents, understand the role of webpack each major configuration items, in fact, a set of matches down or a lot of harvest.