Build the column series directory entry

Jiao Chuankai, Platform support group of Front-end Technology Department of Micro Medical. Come on Sir, compatible with IE?

One, foreword

In the default configuration, we know that WebPack packs all the code into a chunk. For example, if you have a large single-page application, you might want to split each route into a chunk so that we can load it on demand.

Code separation is one of the most compelling features of WebPack. This feature enables you to separate code into different bundles and load these files on demand or in parallel. Code separation can be used to obtain smaller bundles and control resource load priorities, which, when used properly, can greatly affect load times.

Second, about code segmentation

Next, we’ll examine the packaging differences caused by different code separations. First, our project assumes that there are two simple files 👇

index.js

import { mul } from './test'
import $ from 'jquery'

console.log($)
console.log(mul(2.3))

Copy the code

test.js

import $ from 'jquery'

console.log($)

function mul(a, b) {
    return a * b
}

export { mul }

Copy the code

You can see that both of them now rely on the jquery library, and will rely on each other as well.When we package with the default configuration, the result is 👇, which packs everything into a main bundle (324kb) So how can we isolate the other modules from this bundle in the most direct way?

1. Multiple entry

Entries in the WebPack configuration can be set to multiple entries, which means we can use index and test files as entries:

// entry: './ SRC /index.js', the original single entry
/** now use each as an entry */
entry: {index:'./src/index.js'.test:'./src/test.js'
},
output: {
  filename: '[name].[hash:8].js'.path: path.resolve(__dirname, './dist'),},Copy the code

Let’s take a look at the results of this packaging:Two files indeed! But why both files320+kb? What about breaking up and getting smaller bundles? This is due to the introduction of jquery in both portals and webPack’s packaging analysis from both portals packages dependent modules separately into 👇 each time

Yes, there are some pitfalls and inconveniences associated with this configuration:

  • If the entry chunks contain duplicate modules, those duplicate modules are introduced into each bundle.
  • This approach is not flexible and does not dynamically separate the code from the core application logic.

Is there a way to package and separate interdependent modules without having to manually configure entry points? There has to be.

2. SplitChunksPlugin

SplitChunks is an out-of-box plug-in that comes with WebPack4 to separate chunks that meet the rules or customize the chunks. It replaces the CommonsChunkPlugin used in Webpackage 4 to resolve repeated dependencies.

Let’s add some configuration to our WebPack configuration:

entry: './src/index.js'.// Here we change the receipt entry
/** + */
optimization: {
  splitChunks: {
    chunks: 'all',}},Copy the code

The result after packaging is shown below:You can obviously see that in addition to the main bundle that is packaged according to the entry, there is an additional bundle namedvendors-node_modules_jquery_dist_jquery_js.xxxxx.jsObviously, we have extracted the common jquery module.

Next, let’s explore SplitChunksPlugin. Let’s look at the configuration defaults first:

splitChunks: {
    // Indicates the chunks to be divided. The options are async, INITIAL, and all
    chunks: "async".// Indicates that the newly separated chunk must be greater than or equal to minSize (20000, about 20KB).
    minSize: 20000.// Avoid zero-size modules by ensuring that the minimum chunk size left after splitting exceeds the limit, only if there is a single chunk left
    minRemainingSize: 0.// Indicates that a module must contain at least one chunk of minChunks. The default value is 1.
    minChunks: 1.// represents the maximum number of parallel requests when files are loaded on demand.
    maxAsyncRequests: 30.// indicates the maximum number of parallel requests while loading the entry file.
    maxInitialRequests: 30.// Volume thresholds and other restrictions that enforce detaching (minRemainingSize, maxAsyncRequests, maxInitialRequests) will be ignored
    enforceSizeThreshold: 50000.// Multiple groups can be configured under cacheGroups. Each group is conditional on test. Modules that meet the test criteria 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: {
        defaultVendors: {
            test: /[\\/]node_modules[\\/]/.// A module can belong to multiple cache groups. Optimization gives priority to cache groups with higher priorities.
            priority: -10.// If the current chunk contains modules that have been split from the main bundle, it will be reused
            reuseExistingChunk: true,},default: {
            minChunks: 2.priority: -20.reuseExistingChunk: true}}}Copy the code

By default, SplitChunks will only be split for asynchronously called chunks (“async”), and by default, chunks must be at least 20KB in size. Modules that are too small will not be included.

As a bonus, the default values vary depending on the mode configuration. See 👇 for details:

const { splitChunks } = optimization;
if (splitChunks) {
  A(splitChunks, "defaultSizeTypes".() = > ["javascript"."unknown"]);
  D(splitChunks, "hidePathInfo", production);
  D(splitChunks, "chunks"."async");
  D(splitChunks, "usedExports", optimization.usedExports === true);
  D(splitChunks, "minChunks".1);
  F(splitChunks, "minSize".() = > (production ? 20000 : 10000));
  F(splitChunks, "minRemainingSize".() = > (development ? 0 : undefined));
  F(splitChunks, "enforceSizeThreshold".() = > (production ? 50000 : 30000));
  F(splitChunks, "maxAsyncRequests".() = > (production ? 30 : Infinity));
  F(splitChunks, "maxInitialRequests".() = > (production ? 30 : Infinity));
  D(splitChunks, "automaticNameDelimiter"."-");
  const { cacheGroups } = splitChunks;
  F(cacheGroups, "default".() = > ({
    idHint: "".reuseExistingChunk: true.minChunks: 2.priority: -20
  }));
  F(cacheGroups, "defaultVendors".() = > ({
    idHint: "vendors".reuseExistingChunk: true.test: NODE_MODULES_REGEXP,
    priority: -10
  }));
}
Copy the code

The cacheGroups cache group is the most important for segmentation, and it can use any option from splitChunks.*, but test, Priority, and reuseExistingChunk can only be configured at the cache group level. The Vendors group and a defalut group are already provided in the default configuration, and the Vendors ** group uses test: /[\\/]node_modules[\\/]/ to match all conforming modules in node_modules.

Tip: When Webpack processes file paths, they always contain/on Unix systems and \ on Windows systems. This is why the {cacheGroup}.test field uses [\/] to represent path separators. / or \ in {cacheGroup}.test can cause problems when used across platforms.

Given the configuration above, we can see why we have a file named Vendor-node_modules_jquery_dist_jquery_js.db47cc72.js in the package, called vendor-node_modules_jquery_dist_jquery_js.db47cc72.js. If you want to customize the name, you can also use the splitchunks. name attribute (available in every cacheGroup). This attribute can be used in one of three forms:

  1. boolean = falseSetting it to false will keep the chunk’s name the same, so it won’t change the name unnecessarily. This is the recommended value for a build in production.
  2. function (module, chunks, cacheGroupKey) => stringThe return value must be of type string and inchunksEach of these in the arraychunkThere arechunk.name 和 chunk.hashProperties, for example 👇
name(module, chunks, cacheGroupKey) {
  const moduleFileName = module
  .identifier()
  .split('/')
  .reduceRight((item) = > item);
  const allChunksNames = chunks.map((item) = > item.name).join('~');
  return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
},
Copy the code
  1. stringA function that specifies a string or always returns the same string consolidates all common modules and vendors into one chunk. This mayThis results in larger initial downloads and slower page loading.

Also pay attention to the splitChunks. MaxAsyncRequests and splitChunks. MaxInitialRequests respectively refers to on-demand loaded initial maximum number of parallel requests and page rendering time need the maximum number of concurrent requests

When our project was larger, if we needed to unpack a dependency separately, we could do something like this:

cacheGroups: {
  react: {
    name: 'react'.test: /[\\/]node_modules[\\/](react)/,
      chunks: 'all'.priority: -5,}},Copy the code

Once packaged, the specified package can be split:

For more configurations, see the configuration documents on the official website

(3) dynamic import

Using the import() syntax for dynamic import is also a highly recommended way to split code. Let’s make a quick change to our index.js file and look at the effect of packaging:

// import { mul } from './test'
import $ from 'jquery'

import('./test').then(({ mul }) = > {
    console.log(mul(2.3))})console.log($)
// console.log(mul(2, 3))
Copy the code

As you can see, throughimport()Syntax-imported modules are automatically packaged separately when packaged

Note that this syntax also has a handy “dynamic reference” way of adding appropriate expressions, for example, assuming we need to load the appropriate topic:

const themeType = getUserTheme();
import(`./themes/${themeType}`).then((module) = > {
  // do sth aboout theme
});
Copy the code

In this way, we can “dynamically” load the asynchronous modules we need. There are two main principles for this implementation:

  1. At a minimum, you need to include module-related path information, and packaging can be restricted to a specific directory or set of files.
  2. According to the path information, Webpack will be packed./themesIn theallThe files are packed into a new chunk so they can be used when needed.

4. Magic notes

In the import() syntax above, we can see that the filename generated automatically by packaging is not what we want. How can we control the name of the packaging ourselves? Here we will introduce our Magic Comments:

import(/* webpackChunkName: "my-chunk-name" */'./test')
Copy the code

A document packaged like this:

Magic notes can not only help us change the name of chunk, but also implement functions such as preloading. Here’s an example:

We do this by expecting to load the module functionality we need only when the button is clicked:

// index.js
document.querySelector('#btn').onclick = function () {
  import('./test').then(({ mul }) = > {
    console.log(mul(2.3));
  });
};
Copy the code
//test.js
function mul(a, b) {
  return a * b;
}
console.log('Test is loaded');
export { mul };
Copy the code

As you can see, it does load when we click the buttontest.jsFile resources. However, if this module is a very large module, loading when clicking may lead to a long time loading and other bad user experience. In this case, we can use our/* webpackPrefetch: true */mannerperfecting, to see the effect:

// index,js

document.querySelector('#btn').onclick = function () {
  import(/* webpackPrefetch: true */'./test').then(({ mul }) = > {
    console.log(mul(2.3));
  });
};
Copy the code

And you can see that throughout the process, when the screen initially loads,test.jsThe resource is already preloaded, and when we click the button, it will be loaded from(prefetch cache)To read the content. This is the process of module prefetching. And we also have/* webpackPreload: true */The way to preload.

But prefetch and preload sound similar, and actually their load timing and so on are completely different:

  • Preload Chunk starts loading in parallel when the parent chunk loads. Prefetch Chunk starts loading after the parent chunk finishes loading.
  • Preload Chunk has medium priority and is downloaded immediately. Prefetch Chunk downloads while the browser is idle.
  • Preload chunk is immediately requested in the parent chunk for the present moment. Prefetch Chunk will be used at some point in the future.

Third, the end

In first have engineered packaged thoughts, we will consider multiple file is packaged into a file to reduce the resource request, as the project is more and more complex, optimizing project to do, we find that the longer project load user experience, the more bad, and he can pass code division way too early to reduce page load time request the resource volume.

This article only briefly introduces the common webpack code segmentation methods, but in the actual project performance optimization, there are often more stringent requirements, I hope to let you quickly understand the skills and advantages of starting the code segmentation through the introduction of this article.

reference

How to use splitChunks to fine-control code segmentation

Code Splitting – Webpack