最近,我们将我们的一个web应用迁移到了Webpack 4,通过使用Split Chunks插件,减少了构建时间并降低了块的大小。它能自动识别出应该通过启发式方法进行分割的模块,并分割出大块。这篇博文讲述了我们在理解神秘的Split Chunks插件方面的努力。
问题
我们在默认的Split Chunks配置中面临的问题是,一个550KB的大模块被重复在4个异步块中。因此,我们的目标是专门减少包的大小,并在应用程序中利用更好的代码分割机制。
我们的Webpack配置文件看起来像这样:
// Filename: webpack.config.js
const webpack = require('webpack');
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all'
}
}
};

观察
默认情况下,Split Chunks插件只影响按需分块,它根据以下条件来分块:
- 一个新的块应该是共享的,或者包含的模块应该来自node_modules文件夹。
- 新的块应该大于30KB。
- 当按需加载块时,最大的并行请求数应低于或等于5。
- 初始页面加载时的最大并行请求数应低于或等于3。
在我们的案例中,大尺寸库的独立块不会被创建。
这背后的原因是什么呢?
它满足了第一和第二个条件,因为它被用在4个块中,而且它的大小(550KB)大于30KB,所以得出结论,它应该被放在一个新的块中。但它不满足第三个条件,因为每次动态导入时都已经创建了5个chunks,这是异步请求的最大限制。我们观察到,前4个块包括所有的模块,这些模块分别在7、6、5、5个异步块中共享,最后一个是它自己的块。一个最大数量的异步块所依赖的模块被赋予了优先权,由于一个库只有4个异步块需要,包含它的块就不会被创建。
当我们运行yarn build 来构建我们的资产时,一个名为vendorsasync.chunk.1async.chunk.2async.chunk.3async.chunk.4的块在输出中没有被发现。

解决方案
我们可以对这个功能有更多的控制。我们可以通过以下任一方式或组合来改变默认配置。
-
增加maxAsyncRequests会产生更多的chunk。大量的请求会降低性能,但在HTTP/2中这并不是一个问题,因为请求和响应的复用。因此,在HTTP/2的情况下,这种配置应该是首选。
现在让我们来看看这个变化后的Webpack配置文件:
// Filename: webpack.config.js const webpack = require('webpack'); module.exports = { //... optimization: { splitChunks: { chunks: 'all', maxAsyncRequests: 20 } } }; -
增加minSize也能得到理想的结果。一些在我们的应用程序中使用频率较高且大小小于minSize的模块不会被包含在单独的chunks中,因为它们都违反了第二个条件,比如在minSize为100KB的情况下,大于100KB的模块会被考虑,从而为创建包含大尺寸模块的chunks提供了更多可能性。
现在让我们来看看这个变化后的Webpack配置文件:
// Filename: webpack.config.js const webpack = require('webpack'); module.exports = { //... optimization: { splitChunks: { chunks: 'all', minSize: 100000 } } };
实验
步骤
- 我们挑选了任意两个异步块,它们之间共享一个大尺寸的第三方库(550KB)。我们把这两个块称为async.chunk.1和async.chunk.2,并假设这两个块的名称和对应的路由名称相同。
- 首先加载了async.chunk.1路由,并计算了加载的总内容大小。
- 然后从async.chunk.1路由导航到async.chunk.2路由,再次计算内容大小。
第一个方法的结果(改变maxAsyncRequest属性)
| MaxAsyncRequests | async.chunk.1 | async.chunk.2 |
|----------------------|----------------------------------|----------------------------|
| 5 | 1521.6 KB | 758 KB |
| 10 | 1523.76 KB | 79.1 KB |
| 15 | 1524 KB | 79.1 KB |
| 20 | 1524.3 KB | 79.1 KB |
在这个改变之后,我们的包看起来是这样的。

通过这样的配置,一个名为vendorsasync.chunk.1async.chunk.2async.chunk.3async.chunk.4的独立块被创建,如下图所示。
第二种方法(改变minSize属性)的结果
| MinSize | async.chunk.1 | async.chunk.2 |
|---------------------|----------------------------------|----------------------------|
| 30 KB | 1521.6 KB | 758 KB |
| 50 KB | 1521.6 KB | 188 KB |
| 100 KB | 1521.4 KB | 78.4 KB |
在这种改变之后,我们的捆绑文件看起来是这样的:

在这种情况下,一个大尺寸的库也被提取到一个单独的块中,名为vendorsasync.chunk.1async.chunk.2async.chunk.3async.chunk.4,如下图所示:
注意:Async.chunk.2在最小尺寸为50KB的情况下,其大小为188KB,而在最小尺寸为100KB的情况下,其大小减少到78.4KB。这是因为还有一个大小为146KB的模块在其他四个块中共享,被提取到一个单独的块中,使整个包的大小减少到78.4KB(太棒了!)。
总结
增加minSize和maxAsyncRequests都会减少async.chunk.2块的大小。
第二种方法会导致多个大尺寸的块,每个块都有多个重复的小尺寸模块。 另一方面,第一种方法会导致大量的小块,这些小块没有任何重复的模块。加载多个小块会增加页面的加载时间,但有了HTTP/2,它将有效地工作。