Webpack 4 —— 神秘的 SplitChunks 插件

原文链接 Webpack 4 — Mysterious SplitChunks Plugin

Webpack-4 正式版夸赞了它更快的构建速度(大约 98%)和更小的 chunk 体积。

然而 Webpack 的作者在开发者社区丢了一颗炸弹,他发布了一则关于负责跨多入口代码拆分的插件的公告。文档中 CommonsChunkPlugin 部分写道:

https://webpack.js.org/plugins/commons-chunk-plugin/

以下是我的一次简单的尝试,通过举一个常见的例子来理解并帮助你使用 SplitChunksPlugin chunks 的配置选项。


作为一个早期的爱好者我曾试图理解代码拆分背后的魔法。文档说 “splitChunks.chunks” 选项有三个可选值:“initial”、“async” 和 “all”。我有点困惑,也更加激发了我的好奇心!

我深入挖掘 Github historyWebpackOptions Schema 的文档找到了这些内容:

“三个可选值:initial、async 和 all”,配置优化时只能选择 初始块、按需块或所有块 —— Github History

“选择用于确定共享模块的块(默认 “async”、“initial” 或 “all” 需要将这些块添加至 HTML)” —— WebpackOptions Schema


举个例子,有两个导入相同 node_modules 的入口文件 a.js 和 b.js,将其中一些模块动态导入来检查代码拆分的行为。

我们用 webpack-bundle-analyzer 这个插件来帮助我们理解模块是如何被拆分的。

a.js :

只有 lodash 动态导入

1
2
3
4
5
6
7
8
import "react";
import("lodash"); // dynamic import
import "jquery";

console.log('I am a');
var a = 10;

export default a;

b.js :

React 和 lodash 动态导入

1
2
3
4
5
6
7
8
import("react"); // dynamic import
import("lodash"); // dynamic import
import "jquery";

console.log('I am b');
var b = 10;

export default b;

我选这个配置的主要原因是来理解当存在公共库的时候 Webpack 的配置是如何表现的,

  1. React - 一个入口动态导入另一个非动态导入
  2. lodash - 两个入口都动态导入
  3. jquery - 两个入口都非动态导入

我们保持这些文件不变,改变 Webpack 配置中 chunks 的值。

1. chunks: “async” —— 优化动态模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var entry = {
a: './public/js/a.js',
b: './public/js/b.js'
};

var output = {
filename: '[name].bundle.js'
};

var optimization = {
splitChunks: {
cacheGroups: {
node_vendors: {
test: /[\\/]node_modules[\\/]/,
chuns: 'async',
priority: 1
}
}
}
};

module.exports.config = {
entry: entry,
output: output,
optimization: optimization
};

webpack.config.js

BundleAnalyzer 截图

chunks: 'async' 告诉 webpack,

​ “嘿,webpack!我只关心动态导入的模块的优化,别管非动态模块。”

现在我们一步步看看发生了什么:

  • Webpack 把 reactb.js 里拎出来放到一个新文件里,a.js 里的不动。优化只发生在动态模块上,import("react") 这句声明会导致拆分文件,而 import "react" 不会。
  • Webpack 把 lodasha.js 里拎出来放到一个新文件里,这个新文件也被 b.js 引用。
  • 尽管 a.jsb.js 都引用了 jquery 但是并不会对它进行优化。为啥?(提示:‘Async’)

2. chunks: “initial” —— 优化同步模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var entry = {
a: './public/js/a.js',
b: './public/js/b.js'
};

var output = {
filename: '[name].bundle.js'
};

var optimization = {
splitChunks: {
cacheGroups: {
node_vendors: {
test: /[\\/]node_modules[\\/]/,
chuns: 'initial',
priority: 1
}
}
}
};

module.exports.config = {
entry: entry,
output: output,
optimization: optimization
};

webpack.config.js

BundleAnalyzer 截图

chunks: 'initial' 告诉 webpack,

​ “嘿,webpack!我不关心动态导入的模块,你可以把它们每个都拆成单独的文件。然而我希望把所有非动态导入的模块打在一个包里,尽管我准备好跟其他文件共享并且分块这些非动态导入的模块,如果这些文件也需要非动态导入的模块的话。”

我们再一步步看看发生了什么:

  • a.js 里的 react 将被移到 node_vendors~a.bundle.jsb.js 里的 react 将被移到它单独的包 0.bundle.js 里。
  • a.jsb.js 里的 lodash 将被移到 1.bundle.js 里。为啥?这个 lodash 是个动态导入的模块。
  • jquery 作为一个非动态导入的公共模块将在 node_vendors~a~b.bundle.js 中被 a.jsb.js 共享。

3. chunks: “all” —— 优化同步和异步模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var entry = {
a: './public/js/a.js',
b: './public/js/b.js'
};

var output = {
filename: '[name].bundle.js'
};

var optimization = {
splitChunks: {
cacheGroups: {
node_vendors: {
test: /[\\/]node_modules[\\/]/,
chuns: 'all',
priority: 1
}
}
}
};

module.exports.config = {
entry: entry,
output: output,
optimization: optimization
};

webpack.config.js

BundleAnalyzer 截图

chunks: 'all' 告诉 webpack,

​ “嘿,webpack!我不管这个模块是动态还是非动态导入的,把它们全部优化掉。但是要确保……呐,你够聪明去干这活儿!”

我们继续一步步看看发生了什么:

  • reacta.js 中非动态导入,在 b.js 中动态导入。所以它转到一个单独的文件 0.bundle.js 中被两个文件引用。
  • lodash 在两个文件中都是动态导入的,所以显然得到一个单独的文件 1.bundle.js
  • jquery 在两个文件中都是非动态导入的,所有它转到公共的共享模块 node_vendors~a~b.bundle.js,被两个文件引用。

最后


# 译注

​ 看到最后发现还是没讲为什么这么干,但是大致可以猜测是个什么样的规则,可能还是得去官方源码中找答案…… TODO!