解读Webpack 4 Split Chunks插件

264 阅读4分钟

最近,我们将我们的一个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插件只影响按需分块,它根据以下条件来分块:

  1. 一个新的块应该是共享的,或者包含的模块应该来自node_modules文件夹。
  2. 新的块应该大于30KB。
  3. 当按需加载块时,最大的并行请求数应低于或等于5。
  4. 初始页面加载时的最大并行请求数应低于或等于3。

在我们的案例中,大尺寸库的独立块不会被创建。

这背后的原因是什么呢?

它满足了第一和第二个条件,因为它被用在4个块中,而且它的大小(550KB)大于30KB,所以得出结论,它应该被放在一个新的块中。但它不满足第三个条件,因为每次动态导入时都已经创建了5个chunks,这是异步请求的最大限制。我们观察到,前4个块包括所有的模块,这些模块分别在7、6、5、5个异步块中共享,最后一个是它自己的块。一个最大数量的异步块所依赖的模块被赋予了优先权,由于一个库只有4个异步块需要,包含它的块就不会被创建。

当我们运行yarn build 来构建我们的资产时,一个名为vendorsasync.chunk.1async.chunk.2async.chunk.3async.chunk.4的块在输出中没有被发现。

解决方案

我们可以对这个功能有更多的控制。我们可以通过以下任一方式或组合来改变默认配置。

  1. 增加maxAsyncRequests会产生更多的chunk。大量的请求会降低性能,但在HTTP/2中这并不是一个问题,因为请求和响应的复用。因此,在HTTP/2的情况下,这种配置应该是首选。

    现在让我们来看看这个变化后的Webpack配置文件:

        // Filename: webpack.config.js
    
        const webpack = require('webpack');
        module.exports = {
            //...
           optimization: {
              splitChunks: {
                 chunks: 'all',
                 maxAsyncRequests: 20
              }
           }
        };
    
  2. 增加minSize也能得到理想的结果。一些在我们的应用程序中使用频率较高且大小小于minSize的模块不会被包含在单独的chunks中,因为它们都违反了第二个条件,比如在minSize为100KB的情况下,大于100KB的模块会被考虑,从而为创建包含大尺寸模块的chunks提供了更多可能性。

    现在让我们来看看这个变化后的Webpack配置文件:

       // Filename: webpack.config.js
    
       const webpack = require('webpack');
       module.exports = {
           //...
          optimization: {
             splitChunks: {
                chunks: 'all',
                minSize: 100000
             }
          }
       };
    

实验

步骤

  1. 我们挑选了任意两个异步块,它们之间共享一个大尺寸的第三方库(550KB)。我们把这两个块称为async.chunk.1和async.chunk.2,并假设这两个块的名称和对应的路由名称相同。
  2. 首先加载了async.chunk.1路由,并计算了加载的总内容大小。
  3. 然后从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,它将有效地工作。