14

I. Introduction

Friends who study from 0 to 1 can refer to the pre-study article:

Pre-article learning Webpack5 Road (Elementary) concept webpack made a brief introduction, learning Webpack5 Road (practice papers) from the configuration to proceed with webpack build a SASS + TS + project React's.

This article will introduce how to optimize the webpack project from the four perspectives of optimizing the development experience, accelerating the compilation speed, reducing the package size, and accelerating the loading speed.

The webpack version information that this article relies on is as follows:

  • webpack-cli@4.7.2
  • webpack@5.46.0

2. Optimize efficiency tools

Before optimization starts, some preparatory work needs to be done.

Install the following webpack plugins to help us analyze and optimize efficiency:

1. Compilation progress bar

Generally speaking, the first compilation time of a medium-sized project is 5-20s, and no progress bar waits much. You can progress-bar-webpack-plugin plug-in, so that we can grasp the compilation status.

Install:

npm i -D progress-bar-webpack-plugin

webpack.common.js configuration method of 06130aec28cdea is as follows:

const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
module.exports = {
  plugins: [
    // 进度条
    new ProgressBarPlugin({
      format: `  :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
    }),
  ],
};
Intimately add bold and green highlight styles to the progress percentage.

Including content, progress bar, progress percentage, and time consumed, the effect of the progress bar is as follows:

image.png

2. Compile speed analysis

To optimize the webpack build speed, we first need to know which plug-ins and which loaders take a long time to facilitate our targeted optimization.

Through the speed-measure-webpack-plugin plug-in to analyze the build speed, you can see the build time of each loader and plugin, and then you can optimize the time-consuming loader and plugin later.

Install:

npm i -D speed-measure-webpack-plugin

webpack.dev.js configuration method of 06130aec28ceb9 is as follows:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
  // ...webpack config...
});

Contains the time-consuming construction of various tools, and the effects are as follows:

image.png

Note: The style of these gray texts is because I am running on the vscode terminal, which causes the colored fonts to be displayed in gray. change the terminal, such as 16130aec28cf15 iTerm2 .

3. Packing volume analysis

Similarly, to optimize the packaging volume, it is also necessary to analyze the proportion of each bundle file for optimization.

Use webpack-bundle-analyzer view the bundle volume analysis generated after packaging, and display the bundle content as a convenient, interactive, and scalable tree diagram form. Help us analyze the output to check where the module ends.

Install:

npm i -D webpack-bundle-analyzer

webpack.prod.js configuration method of 06130aec28cf98 is as follows:

const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    // 打包体积分析
    new BundleAnalyzerPlugin(),
  ],
};

Contains volume analysis of each bundle, the effect is as follows:

image.png

3. Optimize the development experience

1. Automatic update

automatic update refers to the function of automatically compiling the code and updating the compiled code without manually recompiling after modifying the code during the development process.

webpack provides the following optional methods to realize automatic update function:

  1. webpack's Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

webpack official recommended way is webpack-dev-server , in learning path Webpack5 of (practice papers) - devserver chapter has introduced webpack-dev-Server help automatically compile code that implements the changes in our place in the code automatically updated of The usage is not repeated here.

This is for the optimization of the development environment, modify the webpack.dev.js configuration.

2. Hot Update

hot update refers to that in the development process, after modifying the code, only the content of the modified part is updated without refreshing the entire page.

2.1 Modify webpack-dev-server configuration

Use the built-in HMR plug-in of webpack to update the webpack-dev-server configuration.

webpack.dev.js configuration method of 06130aec28d197 is as follows:

module.export = {
  devServer: {
    contentBase: "./dist",
    hot: true, // 热更新
  },
};

2.2 Introduce react-refresh-webpack-plugin

Use react-refresh-webpack-plugin hot update the react components.

Install:

npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh

webpack.dev.js configuration method of 06130aec28d209 is as follows:

const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");

module.exports = {
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new ReactRefreshWebpackPlugin(),
  ],
};

encountered by

After the SpeedMeasurePlugin is configured, the hot update is invalid, and runtime is undefined will be prompted.

image.png

solution:

Only open the SpeedMeasurePlugin plug-in when analyzing the build speed. Here we first turn off the use of SpeedMeasurePlugin to view the hot update effect.

final result:

When updating the react component code, there is no need to refresh the page, only the component part is updated.

Fourth, speed up the construction

1. Update version

1.1 webpack version

Use the latest webpack version, through webpack's own iterative optimization, to speed up the build.

This is very effective. For example, compared to webpack4, webpack5 has added optimizations such as persistent cache and improved caching algorithm. For the new features of reference material .

1.2 Package management tool version

Node.js and package management tools (such as npm or yarn ) to the latest version will also help improve performance. The newer version can build a more efficient module tree and improve the parsing speed.

The version information this article relies on is as follows:

  • webpack@5.46.0
  • node@14.15.0
  • npm@6.14.8

2. Cache

2.1 cache

By configuring webpack persistent cache cache: filesystem to cache the generated webpack modules and chunks to improve the construction speed.

Simply put, cache: filesystem , which greatly improves the speed of the second build and the packaging speed. When the build is suddenly interrupted and the second build is performed, it can be directly pulled from the cache, which can speed up 90% .

webpack.common.js configuration method of 06130aec28d3fe is as follows:

module.exports = {
  cache: {
    type: "filesystem", // 使用文件缓存
  },
};

After the introduction of caching, the first build time will increase by 15%, and the second build time will be reduced by 90%. The effect is as follows:

image.png

2.2 dll ❌

See the introduction of dll in webpack official website construction performance

Dlls can generate separate compilation results for code that changes infrequently. Can improve the compiling speed of the application program.

I eagerly started looking for the configuration instructions of the dll. It was too complicated. Then I found a plug-in autodll-webpack-plugin assist in the configuration of the dll. As a result, it directly stated that the out-of-the-box persistent cache of webpack5 is more than dll. Excellent solution.

So, there is no need to configure dll anymore, the cache described above is obviously more fragrant.

2.3 cache-loader ❌

That's right, cache-loader does not need to be introduced, the above cache has already helped us cache.

3. Reduce loaders and plugins

Each loader, plugin has its startup time. Use tools as little as possible and delete unnecessary loaders and plugins.

3.1 Specify include

Specify include for loader to reduce the scope of loader application and only apply to the minimum number of necessary modules.

webpack build performance document

rule.exclude can exclude the module range, and can also be used to reduce the loader application range.

webpack.common.js configuration method of 06130aec28d5a6 is as follows:

module.exports = {
  rules: [
    {
      test: /\.(js|ts|jsx|tsx)$/,
      include: paths.appSrc,
      use: [
        {
          loader: "esbuild-loader",
          options: {
            loader: "tsx",
            target: "es2015",
          },
        },
      ],
    },
  ],
};

After defining the include of the loader, the build time will be reduced by 12%, and the effect is as follows:

image.png

3.2 Management Resources

Use the webpack resource module (asset module) to replace the old assets loader (such as file-loader / url-loader / raw-loader etc.) to reduce the number of loader configurations.

The configuration method is as follows:

module.exports = {
  rules: [
    {
      test: /\.(png|svg|jpg|jpeg|gif)$/i,
      include: [paths.appSrc],
      type: "asset/resource",
    },
  ],
};

After the introduction of the resource module, the construction time will be reduced by 7%, and the effect is as follows:

image.png

4. Optimize resolve configuration

resolve used to configure how webpack resolves modules. The default configuration items can be overridden by optimizing the resolve configuration to reduce the resolution range.

4.1 alias

alias can create import or require to simplify the introduction of modules.

webpack.common.js configuration method of 06130aec28d710 is as follows:

module.exports = {
  resolve: {
    alias: {
      "@": paths.appSrc, // @ 代表 src 路径
    },
  },
};

4.2 extensions

extensions represents a list of file types that need to be parsed.

According to the file type in the project, define extensions to cover the default extensions of webpack to speed up the parsing speed.

Since the parsing order of webpack is from left to right, the most frequently used file types should be placed on the left. As follows, I will put tsx on the far left.

webpack.common.js configuration method of 06130aec28d78d is as follows:

module.exports = {
  resolve: {
    extensions: [".tsx", ".js"], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。
  },
};

4.3 modules

Modules represent the directories that need to be parsed when webpack parses modules.

Specifying a directory can narrow the scope of webpack parsing and speed up the build.

webpack.common.js configuration of 06130aec28d7e4 is as follows:

module.exports = {
  modules: ["node_modules", paths.appSrc],
};

4.4 symlinks

If the project does not use symlinks (such as npm link or yarn link ), you can set resolve.symlinks: false to reduce the analysis workload.

webpack.common.js configuration method of 06130aec28d846 is as follows:

module.exports = {
    resolve: {
        symlinks: false,
    },
}

After optimizing the resolve configuration, the build time will be reduced by 1.5%, and the effect is as follows:

image.png

5. Multi-process

As you can see above, the build time of sass-loader is 1.56s, which occupies 60% of the entire build process. Is there any way to speed up the build speed of sass-loader?

It can be achieved through multiple processes. Just imagine running sass-loader in an independent worker pool, it will not hinder the construction of other loaders, and can greatly speed up the construction.

5.1 thread-loader

Use thread-loader put the time-consuming loader in a separate worker pool to run, speed up the loader construction.

Install:

npm i -D thread-loader

webpack.common.js configuration method of 06130aec28d915 is as follows:

module.exports = {
  rules: [
    {
      test: /\.module\.(scss|sass)$/,
      include: paths.appSrc,
      use: [
        "style-loader",
        {
          loader: "css-loader",
          options: {
            modules: true,
            importLoaders: 2,
          },
        },
        {
          loader: "postcss-loader",
          options: {
            postcssOptions: {
              plugins: [["postcss-preset-env"]],
            },
          },
        },
        {
          loader: "thread-loader",
          options: {
            workerParallelJobs: 2,
          },
        },
        "sass-loader",
      ].filter(Boolean),
    },
  ],
};
webpack official website mentioned that there is a thread blocking bug from the Node.js thread pool in node-sass When using thread-loader , you need to set workerParallelJobs: 2 .

Since the thread-loader is introduced, it takes about 0.6s to start a new node process, and the amount of code in this project is small. It can be seen that after the thread-loader is introduced, the build time has increased by 0.19s.

Therefore, we should only introduce thread-loader before the very time-consuming loader.

The effect is as follows:

image.png

5.2 happypack ❌

happypack also used to set up multi-threading, but in webpack5, do not use happypack anymore. Officially, it is no longer maintained. It is recommended to use the thread-loader described above.

6. Distinguish the environment

In learning Webpack5 Road (practice papers) - mode (mode) chapter has introduced a built-in optimization of different modes of webpack.

In the development process, do not use tools that are only used in the production environment in the development environment. For example, in the development environment, tools such as [fullhash] / [chunkhash] / [contenthash] should be excluded.

Similarly, in the production environment, you should avoid using tools that are only used in the development environment, such as webpack-dev-server and other plug-ins.

7. Other

7.1 devtool

Different devtool settings will cause performance differences.

In most cases, the best choice is eval-cheap-module-source-map .

The detailed distinction can be 16130aec28daf7 webpack .

webpack.dev.js configuration method of 06130aec28db09 is as follows:

export.module = {
    devtool: 'eval-cheap-module-source-map',
}

7.2 The output result does not carry path information

By default, webpack will generate path information in the output bundle. Deleting the path information can slightly increase the build speed.

module.exports = {
    output: {
        pathinfo: false,
      },
    };
}

Fourth, reduce the packaging volume

1. Code Compression

The first step of volume optimization is to compress the code, through the webpack plug-in, to compress JS, CSS and other files.

1.1 JS compression

Use TerserWebpackPlugin to compress JavaScript.

webpack5 comes with the latest terser-webpack-plugin , no manual installation is required.

terser-webpack-plugin parallel: true configuration enabled by default, and the default number of concurrent runs: os.cpus().length - 1 , the number of parallel configured in this article is 4, and multi-process concurrent running compression is used to improve the build speed.

webpack.prod.js configuration method of 06130aec28dbfb is as follows:

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
    ],
  },
};

The volume is reduced by 10%, and the effect is as follows:

image.png

1.1 ParallelUglifyPlugin ❌

You may have heard of the ParallelUglifyPlugin plug-in, which can help us compress JS in multiple processes. The TerserWebpackPlugin of webpack5 enables multi-process and caching by default, so there is no need to introduce ParallelUglifyPlugin.

1.2 CSS compression

Use CssMinimizerWebpackPlugin compress CSS files.

optimize-css-assets-webpack-plugin , css-minimizer-webpack-plugin uses query strings in source maps and assets more accurately, and supports caching and concurrent operation.

CssMinimizerWebpackPlugin will search for CSS files during Webpack construction to optimize and compress CSS.

Install:

npm install -D css-minimizer-webpack-plugin

webpack.prod.js configuration method of 06130aec28dd2b is as follows:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
        parallel: 4,
      }),
    ],
  },
};

Since CSS is placed in the JS file by default, this example is based on the effect of separating the CSS code in the next chapter.

2. Code separation

Code separation can separate the code into different bundles, and then load these files on demand or in parallel. Code separation can be used to obtain smaller bundles and control resource loading priority, which can shorten page loading time.

2.1 Extract duplicate code

SplitChunksPlugin plug-in is used out of the box, you can extract public dependent modules into an existing entry chunk, or extract to a newly generated chunk.

webpack will automatically split chunks based on the following conditions:

  • The new chunk can be shared, or the module comes from the node_modules folder;
  • The volume of the new chunk is greater than 20kb (the volume before min+gz);
  • When loading chunks on demand, the maximum number of parallel requests is less than or equal to 30;
  • When loading the initialization page, the maximum number of concurrent requests is less than or equal to 30;
    Separate public libraries such as react through splitChunks without reintroducing the occupied volume.
Note: Remember not to define a fixed name for cacheGroups, because when cacheGroups.name specifies a string or a function that always returns the same string, all common modules and vendors will be merged into one chunk. This will result in a larger initial download and slow down the page loading speed.

webpack.prod.js configuration method of 06130aec28deaa is as follows:

module.exports = {
  splitChunks: {
    // include all types of chunks
    chunks: "all",
    // 重复打包问题
    cacheGroups: {
      vendors: {
        // node_modules里的代码
        test: /[\\/]node_modules[\\/]/,
        chunks: "all",
        // name: 'vendors', 一定不要定义固定的name
        priority: 10, // 优先级
        enforce: true,
      },
    },
  },
};

Pack the common modules separately and not introduce them again. The effect is as follows:

image.png

2.2 CSS file separation

MiniCssExtractPlugin The plugin extracts CSS into a separate file, creates a CSS file for each JS file containing CSS, and supports on-demand loading of CSS and SourceMaps.

Install:

npm install -D mini-css-extract-plugin

webpack.common.js configuration method of 06130aec28df63 is as follows:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.module\.(scss|sass)$/,
        include: paths.appSrc,
        use: [
          "style-loader",
          isEnvProduction && MiniCssExtractPlugin.loader, // 仅生产环境
          {
            loader: "css-loader",
            options: {
              modules: true,
              importLoaders: 2,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [["postcss-preset-env"]],
              },
            },
          },
          {
            loader: "thread-loader",
            options: {
              workerParallelJobs: 2,
            },
          },
          "sass-loader",
        ].filter(Boolean),
      },
    ],
  },
};
Note: MiniCssExtractPlugin.loader should be placed after style-loader.

The effect is as follows:

image.png

2.3 Minimize entry chunk

By configuring optimization.runtimeChunk = true , an additional chunk is created for the runtime code to reduce the size of the entry chunk and improve performance.

webpack.prod.js configuration method of 06130aec28e037 is as follows:

module.exports = {
    optimization: {
        runtimeChunk: true,
      },
    };
}

The effect is as follows:

image.png

3. Tree Shaking

Shaking a tree, as the name implies, is to shake down the yellow fallen leaves, leaving only the living leaves on the tree. The withered yellow leaves represent useless code not quoted in the project, and the living leaves represent the source code actually used in the project.

3.1 JS

JS Tree Shaking code (Dead Code) in the JavaScript context, and package.json the "sideEffects" attribute of 06130aec28e0c9 as a mark to provide hints to the compiler indicating which files in the project are "pure (pure ES2015 modules)". This can safely delete unused parts of the file.

Dead Code generally has the following characteristics:

  • The code will not be executed and cannot be reached;
  • The result of code execution will not be used;
  • The code only affects dead variables (write only, not read).
3.1.1 webpack5 sideEffects

Through the "sideEffects" property of package.json, to achieve this way.

{
  "name": "your-project",
  "sideEffects": false
}

It should be noted that when the code has side effects, you need to change sideEffects to provide an array and add the file path of the side effect code:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

After adding TreeShaking, unquoted code will not be packaged, and the effect is as follows:

image.png

3.1.2 Optimization of component library references

webpack5 sideEffects can only clear references without side effects, and references with side effects can only be performed by optimizing the reference method Tree Shaking .

1. lodash

Similar to import { throttle } from 'lodash' is a side-effect reference, which will package the entire lodash file.

The optimization method is to use import { throttle } from 'lodash-es' instead of import { throttle } from 'lodash' , lodash-es to Lodash library to the ES module, support ES modules-based tree shaking, and implement on-demand introduction.

2. ant-design

ant-design supports tree shaking based on ES modules by default. For the js part, directly introducing import { Button } from 'antd' will have the effect of loading on demand.

If only a few components are introduced into the project, import { Button } from 'antd' also has side effects, and webpack cannot tree-shaking other components. At this time, narrow the reference range , and the introduction method can be modified to import { Button } from 'antd/lib/button' for further optimization.

3.2 CSS

The Tree Shaking operation is performed on the JS code above. Similarly, the CSS code also needs to shake the tree. When the CSS code is packaged, the useless CSS code is shaken away, which can greatly reduce the size of the packaged CSS file.

Use purgecss-webpack-plugin for CSS Tree Shaking.

Install:

npm i purgecss-webpack-plugin -D

Because the CSS is placed in the JS file by default when packaging, it should be used in conjunction with the webpack separating CSS file plugin mini-css-extract-plugin . The CSS file is separated first, and then CSS Tree Shaking is performed.

webpack.prod.js configuration method of 06130aec28e358 is as follows:

const glob = require("glob");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurgeCSSPlugin = require("purgecss-webpack-plugin");
const paths = require("paths");

module.exports = {
  plugins: [
    // 打包体积分析
    new BundleAnalyzerPlugin(),
    // 提取 CSS
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    // CSS Tree Shaking
    new PurgeCSSPlugin({
      paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true }),
    }),
  ],
};

In order to test the CSS compression effect above, I introduced a lot of invalid CSS code, so the Tree Shaking effect is also very obvious, the effect is as follows:

image.png

3. CDN

The above is the optimization of webpack configuration, on the other hand, CDN can also be used to reduce the packaging volume.

The primary purpose of introducing the CDN here is to reduce the packaging volume, so only a part of the large static resources are manually uploaded to the CDN and the local import path is modified. To speed up the loading speed below, we will introduce another CDN optimization method.

Upload large static resources to CDN:

  • Font: Compressed and uploaded to CDN;
  • Picture: Compressed and uploaded to CDN.

Five, speed up the loading speed

1. Load on demand

Provided by webpack Import () syntax dynamic import function code separated by demand loading, greatly enhance the page loading speed.

The usage is as follows:

export default function App() {
  return (
    <div>
      hello react 111
      <Hello />
      <button onClick={() => import("lodash")}>加载lodash</button>
    </div>
  );
}

The effect is as follows:

Untitled.gif

2. browser cache

Browser caching means that after entering a certain website, the loaded static resources are cached by the browser. After entering the website again, the cached resources will be pulled directly to speed up the loading speed.

webpack supports the creation of a hash id based on the content of the resource. When the content of the resource changes, a new hash id will be created.

Configure JS bundle hash, webpack.common.js configuration method is as follows:

module.exports = {
  // 输出
  output: {
    // 仅在生产环境添加 hash
    filename: ctx.isEnvProduction
      ? "[name].[contenthash].bundle.js"
      : "[name].bundle.js",
  },
};

Configure CSS bundle hash, webpack.prod.js configuration method is as follows:

module.exports = {
  plugins: [
    // 提取 CSS
    new MiniCssExtractPlugin({
      filename: "[hash].[name].css",
    }),
  ],
};

Configure optimization.moduleIds so that the hash of the public package splitChunks will not change due to new dependencies, and reduce unnecessary hash changes. The webpack.prod.js is as follows:

module.exports = {
  optimization: {
    moduleIds: "deterministic",
  },
};

By configuring contenthash/hash, the browser caches the unchanged files, and only reloads the changed files, which greatly speeds up the loading speed.

3. CDN

Upload all static resources to the CDN, and use CDN acceleration to improve the loading speed.

webpack.common.js configuration method of 06130aec28e725 is as follows:

export.modules = {
output: {
    publicPath: ctx.isEnvProduction ? 'https://xxx.com' : '', // CDN 域名
  },
}

6. Comparison before and after optimization

In the case where the warehouse code only has a different configuration of webpack, check the comparison before and after optimization.

  • [Github address before optimization]()
  • [Github address after optimization]()

1. Build speed

typeFirst buildUnmodified content rebuildModified content secondary construction
Before optimization2.7s2.7s2.7s
Optimized2.7s0.5s0.3s

image.png

image.png

2. Packing volume

typeSize
Before optimization250 kb
Optimized231 kb

image.png

Seven, summary

From the previous chapter [Comparison before and after optimization] we can see that in small projects, adding too many optimized configurations does not have much effect, but will increase the construction time due to additional loaders and plugins.

In terms of speeding up the build time, the most important thing is to configure the cache, which can greatly speed up the second build.

In terms of reducing the packaging volume, the most important thing is to compress the code, separate the repetitive code, and Tree Shaking, which can reduce the packaging volume to the greatest extent.

In terms of speeding up loading, the effects of on-demand loading, browser caching, and CDN are all significant.

This is the end of this article. There is a better way to optimize webpack. Please let me know in the comment section~

Source code of this article:

Hope it can be helpful to you, thanks for reading~

Don’t forget to give me a like and encourage me, refill ❤️

Reference


Welcome to follow the blog of Lab: 16130aec28ed1f aotu.io

Or follow the AOTULabs official account (AOTULabs) and push articles from time to time.


凹凸实验室
2.3k 声望5.5k 粉丝

凹凸实验室(Aotu.io,英文简称O2) 始建于2015年10月,是一个年轻基情的技术团队。