Webpack 详解之代码分割(code-splitting)

7,661 阅读5分钟

本文所有的内容均基于 webpack@4.42.0 所撰写。阅读本文之前需了解 Webpack 的一些基本概念,如: entrychunkmodule 等。

代码分割是 Webpack 最引人注目的特性之一。这个特性允许开发者将代码分割成不同的包,然后可以按需加载或并行加载这些包。它可以用来实现更小的包,并控制资源加载优先级,如果使用正确,将对加载时间产生重大影响。

分割策略

首先介绍一下 Webpack 默认的代码分割策略。

webpack will automatically split chunks based on these conditions:

  • New chunk can be shared OR modules are from the node_modules folder
  • New chunk would be bigger than 30kb (before min+gz)
  • Maximum number of parallel requests when loading chunks on demand would be lower or equal to 5
  • Maximum number of parallel requests at initial page load would be lower or equal to 3

上面这段话来自 Webpack 的官方文档,说的是 Webpack 会在满足下面的条件时自动进行 chunk 的分割 (ps:如果没有特殊配置的话,只有异步加载的 chunk 会自动进行分割)

  • 新的 chunk 可以被共享或者 chunk 里的 module 来自于 node_modules 文件夹
  • 新的 chunk 文件大小大于 30kb(压缩前)
  • 按需加载的 chunk 内部的最大并行请求数不超过 5
  • 初始化页面的并行请求的最大数量不超过 3

满足上面的四个条件 Webpack 就认为可以进行代码分割,前两个条件应该比较好理解,这里解释下后两个条件的意思吧:

按需加载的 chunk 内部的最大并行请求数不超过 5

简单来说就是加载 chunk 所发送的请求不能超过 5 个。举个例子:

a.js 依赖了 1.js2.js3.js4.js 这四个文件,且这四个文件满足了代码分割的前两个条件 (被共享且大于 30kb)。这个时候就会分割出五个 chunk,分别是:a.chunk.js1.chunk.js2.chunk.js3.chunk.js4.chunk.js。此时加载 chunk~a 的并发请求数就恰好是 5 个,编译后的加载代码大致如下:

// t.e 是加载的 chunk 的方法,t.e 的参数应该是 chunk 的 id,为了容易阅读,我这里用了 chunk 的名称替代。
Promise.all([t.e('1.chunk'),t.e('2.chunk'),t.e('3.chunk'),t.e('4.chunk'),t.e('a.chunk')]).then(() => // do some thing)

如果 a.js 又依赖了 5.js,,这个时候并不会分割出 5.chunk,因为如果分割出 5.chunk,那么加载 a.chunk 的请求就是 6 个了, 5.js 的内容会被合并到 a.chunk 中。

初始化页面的并行请求的最大数量不超过 3

这里的初始化页面可以理解为加载 entry,换句话说就是加载 entry的并行请求的最大数量不超过 3 个,现在就比较好理解了,和上一个条件基本一致,只不过一个是针对 entry 的,一个是针对普通的 chunk 的。这个配置是为了对代码分割的 chunk 数量进行一定的限制,避免分割出太多chunk导致请求数量过多的情况。

要注意的是:

  • 这两个限制并不包括 js 以外的请求比如css
  • 如果只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来。

分割配置

上面介绍的都是 Webpack 代码分割的默认策略,这些策略是 Webpack 团队认为的最佳实践。 如果你在项目有特殊的需求,比如认为默认的 30kb 太大了,你想超过 10kb 就进行分割。Webpack 也提供了我们一些配置去修改分割策略,这就是 Webpack 中的 optimization.splitChunks 配置,下面就介绍几个比较相对重要的配置,完整的配置可以查看文档

splitChunks.chunks

function (chunk) | string

chunks 配置的是要针对哪些 chunk 进行代码分割,之前有提到过,默认只会分割异步的加载的 chunkchunks 的值为 string 时,有效的值为all, asyncinitial

module.exports = {
  //...
  optimization: {
    splitChunks: {
      // 分割所有类型的 chunk
      chunks: 'all'
    }
  }
};
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks (chunk) {
        // 除了 `my-excluded-chunk` 都进行分割
        return chunk.name !== 'my-excluded-chunk';
      }
    }
  }
};

splitChunks.minChunks

number

minChunks 指的是在代码分割之前 module 至少被几个 chunk 共享。默认值为1

splitChunks.minSize

number

minSize 对应的就是分割策略中的第二个条件中的 30kb 的限制,开发者可以根据项目的实际情况灵活调节它的大小。

splitChunks.maxSize

number

这个配置是设置分割出来的 chunk 的最大文件大小的,默认值为 0,也就是没有上限。设置了该属性,Webpack 就会尽可能地把 chunk 的大小限制在maxSize 的值之内,不过一个完整的module 的代码没法分割成几段,所以该超过还是会超过的。

你可以通过设置这个属性来分割出更多、更小的 chunk,不过随之而来的就是更多的加载 chunk 的请求。

splitChunks.maxAsyncRequests

number

maxAsyncRequests 对应的是分割策略中的第三条的配置,默认值为 5

splitChunks.maxInitialRequests

number

maxInitialRequests 对应的是分割策略中的第四条的配置,默认值为 3

splitChunks.cacheGroups

cacheGroupssplitChunks 中最核心的配置之一,它的配置项包括 splitChunks.* 下的所有配置,且还多了testpriorityreuseExistingChunk 三个配置项。

cacheGroups 中的配置会覆盖 splitChunks 中的配置,而且有优先级之分(通过 priority 设置优先级),cacheGroups 主要的用途是针对不同的 chunk 设置不同的分割策略,这可以大大提升代码分割的灵活性,笔者在这里就不详细展开说明使用方法了,感兴趣的可以参考官方文档。