The previous “WebPack Basics (1) : Basic Configuration” mainly describes the Loader /plugin required for basic WebPack resolution. With the daily use of WebPack, we focus more on how to build faster, build smaller, build compliant… Hopefully this article will give you some answers.

First, webpack4 construction optimization

1. Speed up construction

1.1 Optimized Configuration

The main types of optimizations described here are as follows:

  1. Narrow the build
  • Exclude and include ranges
  • noParse
  • IgnorePlugin
  1. Multiple processes
  • thread-loader/happypack
  1. The cache
  • Cache-loader /cacheDirectory: cache the processing result of loader to the local PC
  • Dll cache, some infrequently changed module building products cached locally

If you have a configuration that you haven’t used before, you can follow the instructions below and skip this section if you are already familiar with it

1. Exclude and include

Configure to translate as few files as possible (exclude takes precedence over include)

const rootDir = process.cwd();

{
        test: /\.(j|t)sx? $/,
        include: [path.resolve(rootDir, 'src')].exclude: [
          /(.|_)min\.js$/],}Copy the code

PS. Use include instead of exclude

2. noParse

If some libraries do not depend on other libraries and do not need to parse them, they can be introduced to speed up compilation.

noParse: /node_modules\/(moment|chart\.js)/
Copy the code

3. IgnorePlugin

Ignore the third-party package specified directory. (It is a built-in plugin for WebPack)

For example: Moment (version 2.24.0) packages all localized content along with the core functionality, and we can use IgnorePlugin to ignore localized content (language pack) when packaged, as shown in the figure below.

plugins: [
  // Ignores the locale folder contents under moment
  new webpack.IgnorePlugin(/^\.\/locale$/./moment$/)]Copy the code

4.1 the thread – loader

Put thread-loader before other loaders, and subsequent loaders will run in a separate worker pool.

// The babel-loader takes a long time in the project, so you can configure thread-loader
rules: [{test: /\.jsx? $/, 
        use: ['thread-loader'.'cache-loader'.'babel-loader']}]Copy the code

4.2 happypack

Webpack running on Node.js is a single thread, which splits the task of file parsing into multiple sub-processes concurrently, and then sends the result to the main process after the sub-process finishes the task to improve the speed of project components.

(But because process allocation and management also need time, so after use may not be fast, need project access experiment)

const Happypack = require("happypack");
module.exports = {
  module: {
    rules: [{test: /\.js[x]? $/,
        use: "Happypack/loader? id=js".include: [path.resolve(__dirname, "src")]}, {test: /\.css$/,
        use: "Happypack/loader? id=css".include: [
          path.resolve(__dirname, "src"),
          path.resolve(__dirname, "node_modules"."bootstrap"."dist")]}, {test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2|.gexf)$/,
        use: "Happypack/loader? id=file".include: [
          path.resolve(__dirname, "src"),
          path.resolve(__dirname, "public"),
          path.resolve(__dirname, "node_modules"."bootstrap"."dist"),],},],},plugins: [
    new Happypack({
      id: "js".// this is the rule id=js
      // Configure the loader from the previous rule here
      use: ["babel-loader"].// Must be an array
    }),
    new Happypack({
      id: "css".// This corresponds to the rule id= CSS
      use: ["style-loader"."css-loader"."postcss-loader"],}).new Happypack({
      id: "file".// Corresponding to id=file in rule
      use: [{loader: "url-loader".options: {
            limit: 10240.//10K},},],}),],};Copy the code

5. cache-loader/cacheDirectory

Used on loaders with high performance overhead to build results to disk in the cache.

(The default directory is node_modueles/. Cache /cache-loader.)

CacheDirectory examples:

rules: [
      {
            test: /\.(j|t)sx? $/,
            use: [
              {
                loader: 'babel-loader'.options: {
                  cacheDirectory: true,},}}]Copy the code

Cache – loader examples:

rules: [
    {
        test: /\.(css)$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'cache-loader' },
          { loader: 'css-loader' },
          { loader: 'postcss-loader'}}]]Copy the code

6. Dll Cache (Dynamic link Library)

Package reusable third-party modules into DLLS and reuse them when you build again, so you only need to repackage the business code.

(Note that DLL cache greatly reduces the first build time, like the previous cache-loader optimization, which reduces the rebuild time)

Use related plug-ins:

  • DllPlugin plugin: used to package individual dynamic link library files.
  • DllReferencePlugin: Used to introduce the DllPlugin packed dynamic link library file into the main configuration file.

(1) Add a webpack configuration to compile DLL files ([name].dll. Js, [name].manifest.json)

// Add a new webpack-dll.config.js configuration file

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
const distPath = path.resolve(__dirname, 'dll');
module.exports = {
  entry: {
    // Put the React related modules into a separate dynamic link library
    react: ['react'.'react-dom'].// Put all polyfills needed for the project into a single dynamically linked library
    polyfill: [
      'core-js/fn/object/assign'.'core-js/fn/object/entries'. ] ,},output: {
    // The name of the dynamic link library file output, [name] represents the current dynamic link library name (react and polyfill)
    filename: '[name].dll.js'.path: distPath,
    // Store the global variable name of the dynamic link library, for example, _dll_react
    // _dll_ is added to prevent global variable collisions
    library: '_dll_[name]',},plugins: [
    / / access DllPlugin
    new DllPlugin({
      // The global variable name of the dynamically linked library needs to be the same as that in output.library
      // The value of this field is the value of the name field in the manifest.json file (_dll_react).
      name: '_dll_[name]'.context: process.cwd(),
      // The name of the manifest.json file that describes the output of the dynamic link library
      path: path.join(__dirname, 'dll'.'[name].manifest.json'),})]};Copy the code
// package.json added DLL build command
"scripts": {
    "dll": "webpack --config webpack-dll.config.js",}Copy the code

(2) When dev builds, tell Webpack which dynamic link libraries are used

/ / webpack. Config. Js file

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

plugins: [
    // Use dynamic link libraries (react and Polyfill)
    new DllReferencePlugin({
      context: process.cwd(),
      manifest: path.join(rootDir, 'dll'.'react.manifest.json'),}),new DllReferencePlugin({
      context: process.cwd(),
      manifest: path.join(rootDir, 'dll'.'polyfill.manifest.json'),}),... ]Copy the code

(3) Import the file in the HTML template

Since I’m only doing local build acceleration here, I’ll introduce it as DEV

<script src="./dll/polyfill.dll.js? _dev"></script>
<script src="./dll/react.dll.js? _dev"></script>
Copy the code

Go to this DLL and set up. React.dll. Js and react.manifast. Js are react.manifast. Take a look at his two files

  • react.dll.jsThis is essentially a collection of code for the referenced module
  • react.manifast.jsSpecify which modules and module path are included
// react.dll. Js files are as follows.
var _dll_react = (function(modules) {
  / /... The webpackBootstrap function code is omitted here} ([function(module.exports, __webpack_require__) {
    // The code corresponding to module 0
  },
  function(module.exports, __webpack_require__) {
    // The code corresponding to the module whose ID is 1
  },
  / /... The code for the remaining modules is omitted here
]));


// react.manifast.js The contents of the react.manifast.js file are as follows:
{
  // Describes the globally exposed variable name of the dynamic link library file
  "name": "_dll_react"."content": {
    "./node_modules/process/browser.js": {
      "id": 0."meta": {}},/ /... Some modules are omitted here
    "./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": {
      "id": 42."meta": {}},... }Copy the code

1.2 Testing Tools

Common tools: speed-measure-webpack-plugin

Usage: Use it to wrap the Webpack configuration

2. Build products

2.1 Reduce the size of the construction product and improve the reuse rate

The main types of optimizations described here are as follows:

  • Optimization. SplitChunks subcontracting
  • Babel configuration@babel/plugin-transform-runtime
  • tree-shaking

Specific use:

1. Optimization. SplitChunks subcontract

Subcontract business code and third-party dependent libraries to reduce the size of index.js;

Remove the common modules of multi-page applications and package them separately. Common code only needs to be downloaded once and then cached, avoiding repeated downloads.

 optimization: {
    minimize: false.moduleIds: 'named'.splitChunks: {
      chunks: 'all'.minSize: 30000.maxSize: 0.minChunks: 1.maxAsyncRequests: 6.maxInitialRequests: 6.automaticNameDelimiter: '~'.name: true.cacheGroups: {
        polyfill: {
          test: /[\\/]node_modules[\\/](core-js|@babel|regenerator-runtime)/,
          name: 'polyfill'.priority: 70.minChunks: 1.reuseExistingChunk: true
        },
        lib: {
            test: /[\\/]node_modules[\\/]/,
            name: 'lib'.chunks: 'initial'.priority: 3.minChunks: 1,},... }}}Copy the code

2. Babel configuration

Extract all required helper functions for the page into a package to avoid double injection

"plugins": [
    "@babel/plugin-transform-runtime". ]Copy the code

3. tree-shaking

If you use ES6’s import syntax, unused code is automatically removed in a production environment.

(1) Specific configuration

const TerserPlugin = require('terser-webpack-plugin');

const config = {
 // Tree-shaking only works in production mode
 mode: 'production'.optimization: {
  // Webpack will identify code that it believes is not being used and mark it in the initial packaging step.
  usedExports: true.minimizer: [
   // Remove dead code compressor
   newTerserPlugin({... }}})];Copy the code

(2) Which kind of code will be removed by shake? Here are some examples

// no tree-shaking
import Stuff from './stuff';
doSomething(Stuff);

// tree-shaking
import Stuff from './stuff';
doSomething();

// tree-shaking
import './stuff';
doSomething();

// no tree-shaking
import 'my-lib';
doSomething();

// Import no tree-shaking
import _ from 'lodash';

// Import tree-shaking
import { debounce } from 'lodash';

Import the concrete module tree-shaking directly
import debounce from 'lodash/lib/debounce';
Copy the code

(3) What is code with side effects? Once introduced, it has a significant impact on the application. (A good example is a global stylesheet, or JS file that sets global configuration.)

(4) We do not want the code with side effects to be shaken. We can configure it as follows

// All files have side effects, all are not tree-shaking
{
 "sideEffects": true
}
// No file has side effects, all can tree-shaking
{
 "sideEffects": false
}
// Only these files have side effects, all other files are tree-shaking, but these files are preserved
{
 "sideEffects": [
  "./src/file1.js"."./src/file2.js"]}Copy the code

(5) Note that Babel configuration requires modules: false, ignore import/export code compilation

const config = {
 presets: [['@babel/preset-env',
   {
     // CommonJS code cannot be shaken by tree-shaking
     // So Babel keeps our existing ES2015 import/export statements and does not compile them
    modules: false}}]].Copy the code

2.2 Testing Tools

Common tools: webpack-bundle-Analyzer

Usage: Use it to wrap the Webpack configuration

3. Product inspection

ES check

When a production environment is built, the build is checked for the presence of ES6 syntax. Some throw an error and prompt you to compile Babel, which prevents the build from being substandard.

Specific usage examples:

// package.json command to add es-check
"dist:basic": "rimraf public && cross-env NODE_ENV=production webpack --config webpack-dist.config.js && es-check es5 ./public/**/*.js"
Copy the code

Second, webpack5 construction optimization

1. Speed optimization

1.1 Compilation Cache

Compilation cache is to cache the results after the first compilation and reuse the cache during subsequent compilation to speed up compilation.

Webpack5 enables compilation caching by default. The cache is in memory by default and you can customize it.

module.exports = {
	cache: {
        // Set the cache type to file system
        type: "filesystem".// The cache location (default is node_modules/.cache/webpack)
        cacheDirectory: path.resolve(__dirname, '.temp_cache'), 
     
        // Specify code dependencies during the build process. Webpack will use these items and the hashes of all dependencies to invalidate the file system cache.
        buildDependencies: {
     
            // The current build cache is invalidated when the contents of the configuration file or the module files on which the configuration file depends change.
            config: [__filename], 

            // webpack.config, loader, and all modules required from your configuration are automatically added. If there are other things that build dependencies, you can add them here
      },
      
      // Specify the version of the cache. Set this version to invalidate the cache when the configuration cache needs to be updated.
	  version: '1.0'}}Copy the code

Some parameter annotations

  • cache: trueiscache: { type: 'memory' }The alias
  • type: ‘filesystem’ | ‘memory’.

If ‘memory’ is set to cache in memory and no other information can be configured, ‘filesystem’ is set to configure more information. The default development mode uses ‘memory’ and production mode is false.

  • version: When neither the configuration file nor the code has changed, but the external dependencies of the build (such as environment variables) have changed, the expected build artifact code may also be different. You can then use the Version configuration to prevent the same cache from being mixed with different external dependencies. For example, you can pass the cache: {version: process.env.node_env} so that different environments do not share the cache when switching.

1.2 Long-term Caching Long-term Caching

Persistent caching refers to making full use of the browser cache to minimize the invalidation of the file cache due to changes in the hash value of the build file due to module changes.

(Since moduleId and chunkId are determined, the hash value of the built file will also be determined.)

1.2.1 the introduction

  1. What are chunk and module?
  • Module: Every source JS and CSS file that can be imported and exported is a module.
  • Chunk: module A separate file block generated by webpack dependency analysis and packaging. For example, files in entry, common code extracted from SplitChunks
  • Bundle: Chunk becomes bundle after being compiled/compressed and packaged. Bundle files are directly referenced by HTML files.

  1. Webpack provides the following three hashes. What do they mean? What are the pros and cons?
  • Hash All bundles use the same hash. (disadvantages) The hash after rebuild will be updated without modifying the file.)
  • Chunkhash An entry/ and the chunk file referenced by the entry all use the same hash. (Disadvantages) This hash will not change after the contents of the chunk file are modified.)
  • Contenthash Changes the hash of each file as it is modified. (disadvantages) If chunk in an entry is deleted, the hash of entry and chunk as well as many files will change, which is not conducive to long-term caching.

For example, just one CSS file referenced by JSX has changed the hash of many bundles.

1.2.2 WebPack4 implements persistent caching

The following configuration is required to achieve long-term caching:

plugins: [
- new webpack.NamedModulesPlugin(),
+ new webpack.HashedModuleIdsPlugin(),
Copy the code

Or configuration

optimization.moduleIds = 'hashed’ 
optimization.chunkIds = 'named'
Copy the code

Configuration description:

  • Use NamedModulesPlugin to fix The Module ID in development and HashedModuleIdsPlugin to fix the Module ID in production (because the build result file is smaller)
  • Use NamedChunksPlugin to solidify the chunk ids of chunks in Runtime and separated when using dynamic loading

NamedChunksPlugin only works on normal Webpack modules, asynchronous modules (asynchronous modules can annotate import with chunkName, for example: import(/ webpackChunkName: “Lodash”/” lodash “).then(), the external module will not work.

1.2.3 Webpack5 defaults to Deterministic for persistent caching

Webpack5 uses a new algorithm. The following configuration is enabled by default in production mode to achieve long-term caching and reduce the size of file packaging:

optimization.chunkIds: "deterministic"
optimization.moduleIds: "deterministic"
mangleExports: “deterministic"
Copy the code

PS. The specific algorithm needs further study ~

2. Package build size optimization

2.1 Node Polyfill script is removed

Webpack 4 comes with polyfills for most core Node.js modules, which are automatically applied as soon as any core modules are used on the front end, resulting in large polyfills, but some polyfills are unnecessary. Now webpackage 5 will not automatically add Polyfills to node.js modules, you need to manually add the appropriate Polyfills.

Note the following when migrating to WebPack5:

  • Try to use front-end compatible modules whenever possible.
  • Polyfills can be added manually for the Node.js core module. The error message will prompt the implementation.
  • Package author: Use the browser field in package.json to make packages front-end compatible. Provide alternative implementations/dependencies for browsers.

2.2 the tree – shaking

1. Nested tree-shaking can track access to nested attributes of export, analyze export and import dependencies of modules, and remove unused modules

// inner.js
export const a = 1;
export const b = 2;

// module.js
export * as inner from './inner';
// or import * as inner from './inner'; export { inner };

// user.js
import * as module from './module';
console.log(module.inner.a); In this example, export B can be removed in production mode.
Copy the code

Optimization. InnerGraph Analyzes dependencies between module exports and imports, enabled by default in production mode.

import { something } from './something';
function usingSomething() {
  return something;
}
export function test() {
  return usingSomething();
}
// Use something only when using the test export.
Copy the code

The following symbols can be analyzed:

  • Function declaration
  • Class declaration
  • Export default or variable declaration with function expression, class expression, sequence expression, /#PURE/ expressions, local variables, import bindings

3. The “sideEffects” flag in package.json allows modules to be manually marked as side-effect-free, thus removing them when they are not in use. Webpack 5 can also automatically mark modules as side-effect-free based on static analysis of source code.

More Webpack5 content recommended reading:

  • Webpack 5.0 is officially released
  • Webpack 5 – Guide to Persistent Caching