background

Front-end users know that in order to reduce the package size, they often pack dependent front-end modules independently, such as vue and VUe-Router into a separate package vendor. In addition, there will often be multiple routes of complex pages of each page are a separate package, only when visiting a page, and then to download the page JS package, in order to speed up the home page rendering.

React and Vue both provide excellent tools to help us avoid the tedious configuration work. When we build the code, we automatically break it down for us.

So, many people don’t know what’s going on behind the scenes. As for why so split, exactly how to control the split code, is confused.

Problem of test

Before we start, let’s take a look at a question. If you already know the answer to the question and understand why, you don’t need to read on. If you don’t know the answer or you know the answer, but you don’t know why. Well, I strongly recommend reading this article.

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: { app: "./src/index.js" },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  },
  optimization: {
    splitChunks: {
      chunks: "all"
    }
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
};
Copy the code
// index.js

import "vue"
import(/*webpackChunkName: 'a'* /"./a");
import(/*webpackChunkName: 'b'* /"./b");
Copy the code
// a.js

import "vue-router";
import "./someModule"; // The module size is larger than 30KBCopy the code
// b.js

import "vuex";
import "./someModule"; // The module size is larger than 30KBCopy the code
// somemodule.js // This module is larger than 30KB //...Copy the code

There are three ways to split code

There are three common ways to split code in Webpack:

  • Entry point: UseentryConfigure manual separation of code.
  • Dynamic import: Separation of code through inline function calls to modules.
  • Prevent repetition: usesplitChunksDeduplicate and separate chunks. The first way, very simple, just need toentryConfigure multiple entrances in:
entry: { app: "./index.js", app1: "./index1.js" }
Copy the code

The second way is to automatically separate modules loaded with import() into separate packages in your code:

/ /... import("./a"); / /...Copy the code

The third way is to use the splitChunks plug-in to configure the separation rules, and webPack automatically separates the chunks that meet the rules. Everything is done automatically.

The first two splits are easy to understand. This paper mainly discusses the third way.

SplitChunks code split

splitChunksThe default configuration

SplitChunks: {// Indicates which chunks to split. Possible values are async, INITIAL, and all chunks:"async", // Indicates that the newly separated chunk must be larger than or equal to minSize. The default value is 30000, about 30KB. MinSize: 30000, // Indicates that a module can be divided only if it contains at least one Minchunk. The default value is 1. MinChunks: 1, // indicates the maximum number of parallel requests when loading files on demand. The default value is 5. MaxAsyncRequests: 5, // Indicates the maximum number of parallel requests to load entry files. The default value is 3. MaxInitialRequests: 3, // represents the name connecter of the split chunk. The default value is ~. Such as the chunk to vendors. Js automaticNameDelimiter:'~', // Set the name of chunk. The default istrue. As for thetrueSplitChunks are automatically named based on the key of chunk and cacheGroups. name:true, // Multiple groups can be configured under cacheGroups, each based ontestThe conditions are mettestConditional modules are assigned to that group. Modules can be referenced by multiple groups, but ultimately which group to package into depends on priority. By default, all modules from the node_modules directory are packaged into the vendors group, and modules shared by two or more chunks are packaged into the default group. cacheGroups: { vendors: {test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        // 
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true}}}Copy the code

The configuration is summarized as follows:

  1. Modules are reused in code or fromnode_modulesfolder
  2. Module size greater than or equal to 30kb (before compression)
  3. When chunks are loaded on demand, the maximum number of parallel requests cannot exceed 5
  4. When the initial page loads, the maximum number of parallel requests cannot exceed 3
// index.js

import("./a");

// ...
Copy the code
// a.js

import "vue";

// ...
Copy the code

The code above, with the default configuration, builds as follows:

Cause analysis:

  • index.jsAs an entry file, it is a case of manually configuring the segmentation code at the entry point, so it is packaged separately. (app.js)
  • a.jsthroughimport()For loading, it is dynamic import, so it will type a package independently. (1.js)
  • vuefromnode_modulesDirectory, and larger than 30KB; From thea.jsAfter removal, witha.jsParallel loading, the number of requests for parallel loading is 2, not more than the default 5;vueAfter the split, the number of parallel loaded entry files does not increase and does not exceed the default of 3.vueIt is insplitChunksSplit conditions, separate package (2.js)

Understand chunks

The chunks are used to tell the object of the splitChunks. The options are async, INITIAL, and all. The default value is async, which means that by default only chunks loaded asynchronously are selected for code splitting. We already proved this in the previous example. Here we use two examples to see what happens when chunks are initial and all. First change the chunks value to initial:

chunks: "initial"
Copy the code

The construction results are as follows:

Cause analysis:

When chunks are initial, the scope of splitChunks becomes the initial chunk that is not loaded asynchronously. For example, our index.js is the chunk that exists at the time of initialization. The VUE module is introduced in chunk A. js asynchronously loaded, so it will not be separated.

Chunks will still use initial, and we’ll make a few changes to index.js and a.js:

// index.js
import 'vue'
import('./a')
Copy the code
// a.js
console.log('a')
Copy the code

The construction results are as follows:

Cause analysis:

Vue is introduced directly in index.js, and index.js is the initial chunk, so it is unbundled into vendors~app.js.

Can splitChunks handle both initial chunks and asynchronous chunks? The answer is yes, just change “chunks” to “all” :

chunks: "all"
Copy the code

Make minor changes to index.js and a.js:

// index.js
import 'vue-router'
import('./a')
Copy the code
// a.js
import 'vue'
console.log('a')
Copy the code

The construction results are as follows:

Cause analysis:

If chunks are all, splitChunks can be processed in both initial chunk and asynchronous chunk scenarios, so the VUE -router in index.js is split off into vendors~app.js, The vue in chunk A. js loaded asynchronously is split into 3.js. It is recommended to set chunks to ALL in development.

Understand maxInitialRequests

MaxIntialRequests indicates that after splitChunks are split, the initial number of chunks requested on the page does not exceed the specified value. Initial chunk refers to the JS that need to be downloaded at the beginning of the page rendering, as opposed to asynchronously loaded js after the page is loaded.

Make the following changes to splitChunks and use the default Settings for the rest:

chunks: 'initial',
maxInitialRequests: 1
Copy the code

Make a few changes to index.js:

// index.js
import 'vue'
Copy the code

The construction results are as follows:

Cause analysis:

Because maxInitialRequests was 1, if vue was unbundled from index.js, the newly created chunk would be required as a pre-dependency of the original Chunk index.js when the page was initialized. Then the number of requests at initialization becomes 2, so splitChunks do not split index.js.

Understand maxAsyncRequests

In contrast to maxInitialRequests, maxAsyncRequests indicate that after splitChunks are split, the number of asynchronous chunks that can be loaded in parallel does not exceed the specified value.

Make the following changes to splitChunks and use the default Settings for the rest:

maxAsyncRequests: 1
Copy the code

Make a few changes to index.js:

// index.js
import('./a')
Copy the code
// a.js
import 'vue'
console.log('a')
Copy the code

The construction results are as follows:

Possible cause: maxAsyncRequests are 1 and a.quests are loaded asynchronously with import(). The number of parallel asynchronous requests is 1. If vue is unbundled from A. js, the unbundled package also becomes an asynchronous request chunk. Thus, when the asynchronous request a.js is made, the number of parallel requests is 2. Therefore, splitChunks do not split A. js because the conditions are not met.

Understand minChunks

MinChunks indicate that a module must be shared by at least a specified number of chunks. The default value is 1.

Make the following changes to splitChunks and use the default Settings for the rest:

chunks: 'all',
minChunks: 2
Copy the code

Make a few changes to index.js:

// index.js
import 'vue'
Copy the code

The construction results are as follows:

Cause analysis:

Because minChunks are 2, the vUE will be split only if it is shared by at least 2 chunks.

To consider

What is the result of building the following code?

chunks: 'all',
minChunks: 2
Copy the code
// index.js
import 'vue'
import './a'
Copy the code
// a.js
import 'vue'
console.log('a')
Copy the code

Understanding of cache groups

CacheGroups inherits the values of all attributes in splitChunks, Such as chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, automaticNameDelimiter, and name, we can also reassign values in cacheGroups, Overrides the value of splitChunks. In addition, some attributes are only available in cacheGroups: test, Priority, and reuseExistingChunk.

With cacheGroups, we can define custom chunk groups that are filtered by test criteria and assigned to the same group.

CacheGroups have two default groups, vendors, which make all modules from the node_modules directory; A default contains modules shared by more than two chunks.

In the previous example, you might have noticed how some of the split chunk names are so strange, vendors~app, for example (vendors~app is the default combination of the group’s key in cacheGroups + the source chunk name). Let’s take a look at customizing the names of the chunks to be split.

First find the group that the chunk belongs to, and then modify the vendors group as follows, using the default Settings for other vendors:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    }
}
Copy the code

Make a few changes to index.js:

// index.js
import 'vue'
Copy the code

The construction results are as follows:

Cause analysis:

Vue comes from the node_modules directory and is assigned to the default vendors set, so the default chunk names are used if name is not specified, and here we specify name, so the final chunk is called customName.

Modules can also be assigned to multiple different groups, but ultimately decide which chunk to package to based on priority.

Add a new group:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    },
    customGroup: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName1",
      priority: 0
    }
}
Copy the code

Construction results:

Cause analysis:

Both groups, vendors and customGroup, met the criteria, but the latter was a higher priority, so vUE was eventually packaged into CustomName1.js.

conclusion

At this point, you should have a good understanding of how WebPack splits code. Can you give your answer to the question at the beginning of the article?

Pay attention to our