Webpack website, there is a sentence, I don’t agree with:

Out of the box SplitChunksPlugin is very user-friendly for most users.

Arguably the most important part of performance tuning is knowing how to subcontract. So, let’s talk about subcontracting in WebPack.

First we need to understand a concept.

What is the chunks

A common interview question is what is a module, chunk, or bundle. This is very confusing for beginners. Let’s start with a simple understanding of WebPack:

Webpack: Build your assets. After the resources on the left are passed through Webpack, they can output resources that can be recognized by the Web browser! Because Node builds a world where all non-JS resources are handled under the rules of the JS world:

CSS: essentially a string under style. After webPack processing CSS text, if you want to insert it directly as a style tag, then use the styleloader. If you want to take it out, then use the MiniCssExtractPlugin to process it. Less: You need to convert it to THE CSS. Therefore, use the less-loader to convert it to the CSS and repeat the preceding operations. Typescript: Not directly recognized by the browser, via TS-loader. To javascript that browsers can recognize.

All of this is to do one thing:

In the webpack diagram above, we can distinguish between Modules and bundles. The resource on the left is the moudle and the resource on the right is the bundle.

A TS file, image, less, pug, etc., is a module, and the packaged product is called a bundle.

So what is chunk?

For the bundle product, in some cases, we think it’s too big. In order to optimize performance, for example, to open the first screen quickly, to use caching, etc., we need to split the bundle in the following way, and for the split thing, we call it chunk.

Let’s try packing!

The default configuration

Now let’s create SRC /index.js and SRC /a.js

index.js a.js
import lodash from "lodash"; ,import { a } from "./a"; ,console.log(lodash, a); export const a = "i am aaaaaa"; ,console.log(a); .

Directory structure:

The result of webpack.config.js configuration and packaging is:

webpack.config.js
{, mode: "production",, entry: {, main: "./src/index.js",, },, output: {, path: path.resolve(__dirname, "dist"),, filename: "[name].js", , clean: true,, },,} |

By default, WebPack is not subcontracted and is all bundled together.

Some configuration fields

The main subcontracting of Webpack is the optimization.splitChunks attribute, so let’s try using different configuration fields to see what happens.

optimization.splitChunks.chunks

Chunks has three provided values: async, initial, and all

async

This value is the default bundle value, meaning that our first package is actually async. This value means that for dynamically loaded modules, the default configuration packages the module separately. Use the following syntax for dynamic loading (there are other ways to write it) :

import('lodash')
Copy the code

Modify index.js and run the build command. To make the file more intuitive, we set the value of optimity.chunkids to named

index.js
import { a } from "./a"; Import ('lodash').then(lodash => {, const res = lodash.default.add(3,4), console.log(a, res); ,})

You can see that Lodash is separated into a separate package.

Here’s what WebPack says about the default configuration:

Webpack automatically splits chunks based on the following conditions: New chunks can be shared, or modules can come from the node_modules folder. New chunks can be larger than 20KB (before min+gz). The maximum number of concurrent requests is less than or equal to 30 when loading the initialization page. When trying to satisfy the last two conditions, it is best to use the larger chunks.

When the experiment was carried out, it was found that the inaccuracies, such as the two entrances to the lodash, the Lodash was not drawn out

index.js other.js
import lodash from "lodash"; ,import { a } from "./a"; ,console.log(lodash, a); import lodash from "lodash"; ,,console.log("lodash", lodash);

Configuration and Packaging

configuration packaging
{, entry: {, main: "./src/index.js",, other: "./src/other.js",, },, optimization: {, chunkIds: "named",, },,} |
By default, only dynamically loaded modules are drawn out. In general, packages that are not immediately needed can be considered dynamically loaded, such as export excel packages, Echarts, monaco-Editor, etc.
#### initial

When chunk is initial or alll, webPack packaging follows the following configuration (named default) :

module.exports = {
  / /...
  optimization: {
    splitChunks: {
      chunks: 'initial'.minSize: 20000.minRemainingSize: 0.minChunks: 1.maxAsyncRequests: 30.maxInitialRequests: 30.enforceSizeThreshold: 50000.cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10.reuseExistingChunk: true,},default: {
          minChunks: 2.priority: -20.reuseExistingChunk: true,},},},},},};Copy the code

Although your configuration might look like this:

{, entry: {, main: "./src/index.js",, other: "./src/other.js",, },, optimization: {, chunkIds: "named",, splitChunks: {, chunks: "initial",, },, },,}

The default configuration of the WebPack package is default only when chunks are not async

New chunks can be shared, or modules can come from the node_modules folder. New chunks are larger than 20KB (the size before min+gz). When loading chunks on demand, the maximum number of concurrent requests is less than or equal to 30. The maximum number of concurrent requests is less than or equal to 30

all

When chunks is set to all, the value is basically the same as the value of initial.

We will refer to lodash dynamically in other.js:

import('lodash').then(lodash= > {
    const res = lodash.default.add(3.4)
    console.log(res);
})
Copy the code

Then use chunks for all and initial, respectively, and see what happens:

all initial

As you can see, for two entry files that refer to Lodash, initial will be packaged in two copies if one is a normal import and one is a dynamic import, and for all, there will be only one copy, so in general, all will be better than INITIAL.

Other default configurations for optimization.splitChunks

{
      minSize: 20000.minRemainingSize: 0.minChunks: 1.maxAsyncRequests: 30.maxInitialRequests: 30.enforceSizeThreshold: 50000,}Copy the code

MinSize: This configuration means that chunks to be subcontracted will not be unpackaged if they are less than 20K in size. MinChunks: A chunks is referenced multiple times. If the number of references is less than a certain value, the chunks will not be unbundled. . If one of the above conditions is met, it will be subcontracted. EnforceSizeThreshold: If the size of a chunks exceeds 50K, the upper limit will not take effect.

optimization.splitChunks. cacheGroups

CacheGroups have two default caching policies, that is, the default configuration for chunks as all and initail:

cacheGroups: {
        defaultVendors: {
          test: /[\ \ /]node_modules[\ \ /]/.priority: - 10.reuseExistingChunk: true,},default: {
          minChunks: 2.priority: - 20.reuseExistingChunk: true,}}Copy the code

The defaultVendors package all the files imported into node_modules from the source code into one big chunk. Default is the unpacking operation of the same module introduced by multiple entries more than twice. It should be noted that the single-page application we usually operate has only one entry file by default. If there is the following code:

// index.js
import lodash from "lodash";
import { a } from "./a";
import { b } from "./b";
console.log(lodash, a, b);

// a.js
export const a = "i am aaaaaa";
console.log(a);
import "./c";

// b.js
export const b = "i am bbbbbbbbb";
console.log(b);
import "./c";

// c.js
console.log("ccccccccc");
Copy the code

C. JS is quoted by A. JS and B. JS. In this case, a, b, and c are all part of the index.js entry. Although c.js is referenced twice, c.js is not divided into a separate package. If you want to package C. js separately, consider dynamic loading.

Usually, we only need the default configuration for most of the requirements. Sometimes we may want to pull out the React related code separately, which requires the following configuration.

react: {
  name: "ReactAbout",
  test: /react/,
  priority: 1,},Copy the code

Effect of packaging

// other.js

import('lodash').then(lodash= > {const res = lodash.default.add(3, 4) the console. The log (res); })import('. /style/a.css')
import('. /style/b.css')
import('. /style/c.css')
Copy the code
{
    test: /\.css$/,
    use: ["style-loader"."css-loader"],}Copy the code

css

CSS is also part of performance optimization. One way is to use a style-loader to insert CSS into a document in the form of a style tag, which cannot be subcontracted normally. But it can be subcontracted by dynamic introduction.

There is also a MiniCssExtractPlugin for subcontracting CSS. By default, the plug-in pulls out the CSS for each entry individually, or you can configure cacheGroups to package the CSS for multiple entries if the criteria are met.

css: {
  name: "css".test: /\.css$/.minChunks: 1.enforce: true,}Copy the code