13
头图

Why do you need splitChunks?

Let's take a simple chestnut first. There are 3 entry files in the wepack setting: a.js , b.js and c.js m1.js and each entry file is imported synchronously- m1.js , do not set splitChunks, configure the webpack-bundle-analyzer plugin to view the content of the output file, the packaging output is like this:

It can be seen intuitively from the analysis diagram that all three output bundle files contain the m1.js file, which indicates that there are duplicate module codes. The purpose of splitChunks is to separate the repetitive module code into separate files and load them asynchronously to save the size of the output file. There are many configuration items for splitChunks, and some descriptions in the official documents are not very clear. Here are some key configuration properties and scenario explanations to help you understand and understand how to configure splitChunks. For easy understanding and simple demonstration, the initial settings for webpack and splitChunks are as follows:

 const path = require('path');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'development',
  entry: {
    a: './src/a.js',
    b: './src/b.js',
    c: './src/c.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'async',
      
      // 生成 chunk 的最小体积(以 bytes 为单位)。
      // 因为演示的模块比较小,需要设置这个。
      minSize: 0,
    },
  },
  plugins: [new BundleAnalyzerPlugin()],
};

chunks

splitChunks.chunks的作用是指示采用什么样的方式来优化分离chunks,常用的有三种常用的取值: asyncinitial all , async are the default values. Next, let's look at the differences between these three settings.

async

chunks: 'async' means to only choose to separate chunks by import() asynchronously loaded modules.举个例子,还是三个入口文件a.jsb.js c.js ,有两个模块文件m1.js m2.js , the contents of the three entry files are as follows:

 // a.js
import('./utils/m1');
import './utils/m2';

console.log('some code in a.js');

// b.js
import('./utils/m1');
import './utils/m2';

console.log('some code in a.js');

// c.js
import('./utils/m1');
import './utils/m2';

console.log('some code in c.js');

These three entry files are imported asynchronously for m1.js , and are imported synchronously for m2.js . The package output is as follows:

For asynchronous imports, splitChunks separates chunks into separate files for reuse, while for synchronous imports the same modules are not processed, which is the default behavior of chunks: 'async' .

initial

Change the chunks to initial , and then look at the output:

The synchronous import will also be separated out, and the effect is very good. This is the difference between initial and async : synchronously imported modules will also be selected for separation.

all

We add a module file m3.js and make the following changes to the entry file:

 // a.js
import('./utils/m1');
import './utils/m2';
import './utils/m3'; // 新加的。

console.log('some code in a.js');

// b.js
import('./utils/m1');
import './utils/m2';
import('./utils/m3'); // 新加的。

console.log('some code in a.js');

// c.js
import('./utils/m1');
import './utils/m2';

console.log('some code in c.js');

The difference is that a.js is a synchronous import m3.js , and b.js is an asynchronous import. Keeping chunks set to initial , the output is as follows:

As you can see m3.js the chunks that are output separately are imported asynchronously in b, and those imported synchronously in a are not separated. That is, under the initial setting, even if the same module is imported, synchronous import and asynchronous import cannot be reused.

Set chunks to all and export Kangkang:

Whether importing synchronously or asynchronously, m3.js are separated and reused. So all on the basis of initial , the module reuse under different import methods is optimized.

There is a problem here . It is found that when the mode of webpack is set to production , in the above example a.js synchronously imported m3.js is not separated and reused. It is normal when set to development . I don't know the reason, please explain if anyone knows.

We see that async , initial and all are similar to the progressive module multiplexing and separation optimization, so if the output of volume optimization is considered, then set The chunks are all .

cacheGroups

Through cacheGroups , you can customize the chunk output grouping. The setting test filters the modules, and the modules that meet the conditions are assigned to the same group. splitChunks has the following groupings by default:

 module.exports = {
  // ...
  optimization: {
    splitChunks: {
      // ...
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

意思就是存在两个默认的自定义分组, ---5044ee73f9246dbb996fa175e64002e7 defaultVendors defaultdefaultVendors是将---1a898f95fbae924b557ca53684e8982b---下面的模块node_modules组. Let's change the configuration and set all the modules under node_modules to be separated and output to the vendors.bundle.js file:

 const path = require('path');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'development',
  entry: {
    a: './src/a.js',
    b: './src/b.js',
    c: './src/c.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 0,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          name: 'vendors',
        },
      },
    },
  },
  plugins: [new BundleAnalyzerPlugin()],
};

The content of the entry file is as follows:

 // a.js
import React from 'react';
import ReactDOM from 'react-dom';

console.log('some code in a.js');

// b.js
import React from 'react';

console.log('some code in a.js');

// c.js
import ReactDOM from 'react-dom';

console.log('some code in c.js');

The output is as follows:

Therefore, according to the actual needs, we can use cacheGroups to divide some common business modules into different groups to optimize the splitting of the output.

For example, we now have two requirements for the output:

  1. The modules under node_modules are all separated and output to the vendors.bundle.js file.
  2. utils/ There are a series of tool module files in the directory, and they are all typed into a utils.bundle.js file when they are packaged.

Adjust the settings in webpack as follows:

 const path = require('path');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'development',
  entry: {
    a: './src/a.js',
    b: './src/b.js',
    c: './src/c.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 0,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          name: 'vendors',
        },
        default: {
          test: /[\\/]utils[\\/]/,
          priority: -20,
          reuseExistingChunk: true,
          name: 'utils',
        },
      },
    },
  },
  plugins: [new BundleAnalyzerPlugin()],
};

The entry file is adjusted as follows:

 // a.js
import React from 'react';
import ReactDOM from 'react-dom';
import('./utils/m1');
import './utils/m2';

console.log('some code in a.js');

// b.js
import React from 'react';
import './utils/m2';
import './utils/m3';

console.log('some code in a.js');

// c.js
import ReactDOM from 'react-dom';
import './utils/m3';

console.log('some code in c.js');

The output is as follows:

maxInitialRequests and maxAsyncRequests

maxInitialRequests

maxInitialRequests Indicates the maximum number of parallel requests for the entry. The rules are as follows:

  • The entry file itself counts as a request.
  • import() Asynchronous loading does not count.
  • If there are multiple modules that satisfy the splitting rules at the same time, but according to the current value of maxInitialRequests now only allow one more split, select chunks with larger capacity.

For example, the webpack settings are as follows:

 const path = require('path');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'development',
  entry: {
    a: './src/a.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 0,
      maxInitialRequests: 2,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          name: 'vendors',
        },
        default: {
          test: /[\\/]utils[\\/]/,
          priority: -20,
          reuseExistingChunk: true,
          name: 'utils',
        },
      },
    },
  },
  plugins: [new BundleAnalyzerPlugin()],
};

The content of the entry file is as follows:

 // a.js
import React from 'react';
import './utils/m1';

console.log('some code in a.js');

The package output is as follows:

The splitting process according to maxInitialRequests = 2 is as follows:

  • a.bundle.js counts as one file.
  • Both vendors.bundle.js and utils.bundle.js can be split, but there is one bit left, so choose to split out vendors.bundle.js.

Set the value of maxInitialRequests to 3, the result is as follows:

Let's consider another scenario, the entry is still the a.js file, and the content of a.js is changed as follows:

 // a.js
import './b';

console.log('some code in a.js');

// b.js
import React from 'react';
import './utils/m1';

console.log('some code in b.js');

Adjusted to a.js synchronously imported b.js , b.js and then imported other modules. In this case maxInitialRequests does it work?可以这样理解, maxInitialRequests是描述的入口并行请求数, b.js会打包进a.bundle.js ,没有异步请求; b.js two import modules inside will be split according to the settings of cacheGroups , then the number of parallel requests at the entrance will be counted.

For example, when maxInitialRequests is set to 2, the packaging output results are as follows:

When set to 3, the packed output is as follows:

maxAsyncRequests

maxAsyncRequests means to limit the maximum number of concurrent requests in asynchronous requests. The rules are as follows:

  • import() itself counts as a request.
  • If there are multiple modules that meet the splitting rules at the same time, but according to the current value of maxAsyncRequests now only allow one more split, choose chunks with larger capacity.

Or as a chestnut, the webpack configuration is as follows:

 const path = require('path');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'development',
  entry: {
    a: './src/a.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 0,
      maxAsyncRequests: 2,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          name: 'vendors',
        },
        default: {
          test: /[\\/]utils[\\/]/,
          priority: -20,
          reuseExistingChunk: true,
          name: 'utils',
        },
      },
    },
  },
  plugins: [new BundleAnalyzerPlugin()],
};

The entry and related files are as follows:

 // a.js
import ('./b');

console.log('some code in a.js');

// b.js
import React from 'react';
import './utils/m1';

console.log('some code in b.js');

At this time, it is asynchronously imported b.js . Under the setting of maxAsyncRequests = 2 , the packaged output results are as follows:

According to the rules:

  • import('.b') counts as one request.
  • Split by chunks size vendors.bundle.js .

Finally the content of import './utils/m1' is left in b.bundle.js . If you put maxAsyncRequests = 3 the output is as follows:

In this way, the b.js imported in m1.js is also split. In practice, we can adjust maxInitialRequests and maxAsyncRequests according to our needs. Personally, the default settings are enough.

Summarize

The setup of splitChunks is quite complicated. Through the above rules and examples, I believe that everyone has understood the use of several key attributes in unpacking. I personally think that they are also the ones that are confusing in the official documentation. For the remaining other attributes, you can find the answer through the official documentation.
My JS Blog: Whisper JavaScript


deepfunc
776 声望634 粉丝