SplitChunksPlugin(webpack 内置插件——将共享代码块单独打包)

起初,基于 webpack 构建的图形关系体系,chunks 通过父子关系进行链接。过去我们通过 CommonsChunkPlugin 来避免重复的依赖,但是却达不到更进一步的优化。

webpack4.x 中,CommonsChunkPlugin 已经被 optimization.splitChunks 取代,以达到更好的优化体验。

Defaults(默认)

开箱即用 SplitChunksPlugin 应该能够满足大部分用户的需求。

默认情况下,plugin 只影响按需加载的 chunk,因为改变初始化的 chunk(个人理解为非引入模块(module)的部分)将会改变 html 中 script 标签包含的脚本内容。( 这个部分个人的理解是 splitChunkPlugin 只会去对那些引入的模块进行拆分复用,而不会去操作其他的代码

webpack 将根据以下的条件将 chunk 自动分割并打包出来:(默认规则)

  • 被分割出来的 chunk 必须是共享的某个 module,或者说是来自 node_modules 文件夹中的某个包;
  • 被分割出来的 chunk 在压缩前(webpack 打包前)必须大于等于 30kb,配置中的单位是字节;
  • 按需加载的 chunk 最终被拆分成的文件数量<=5;
  • 入口点最终被拆分成的文件数量<=3;

为了尽可能满足最后两个条件,推荐保证被分割出来的 chunk 尽量大些。

Configuration(配置)

为了方便开发者进行个性化的配置,plugin 提供了一系列的配置。

插件的默认配置提供了 web 性能最佳实践,但是对于不同的项目,这可能不是最佳策略。如果你要进行个性化配置,则应评估所做更改的影响,以确保确实有好处。

1.optimization.splitChunks

此对象为 plugin 的配置对象,默认的配置如下:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      automaticNameMaxLength: 30,
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
}

2.splitChunks.automaticNameDelimiter

type: String

默认情况下,webpack 将使用 chunk 的来源和名称来生成 bundle 的名称(比如 vendors~main.js)。此选项使你可以指定用于生成名称的定界符(就是 vendors\~main.js 中的~符号)。

3.splitChunks.automaticNameMaxLength

type: Number, 默认值为 109

通过这个配置,你可以指定通过 plugin 生成的 bundle 文件名的最大长度。

4.splitChunks.chunks

type: function (chunk)、string

这个配置指定了哪些 chunk 将会被优化。

它可以赋值为一个字符串,这个字符串可以是 all、async 和 initial。

  • initial:直接通过 import 或者 require 导入的模块将会被 split;
  • 如: import a from './a.js' 或 const a = require('./a.js')
  • async:动态按需导入的模块将会被分割,以 import 为例,如下所示:
import(/* webpackChunkName: "a" */ './a.js').then(a => {
  console.log(a)
})
  • all:包含上述两种情况;

或者,如果你想个性化控制,你也可以选择赋值为一个 function。返回值将指示是否包括每个块。

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks (chunk) {
        // exclude `my-excluded-chunk`  排除my-excluded-chunk
        return chunk.name !== 'my-excluded-chunk';
};

您可以将此配置与 HtmlWebpackPlugin 结合使用。它将为您注入所有生成的 vendor chunk。

注:当入口文件中的 module 是动态按需导入的,一定会被分割并打包出来。

对于这个配置更加详细的描述,请阅读我的另一篇文章: blog.csdn.net/YaoDeBiAn

5.splitChunks.maxAsyncRequests

type: Number

用来表示按需加载的模块其能拆分的最大数量;

更加详细的描述可以查阅我的另一篇文章: blog.csdn.net/YaoDeBiAn

6.splitChunks.maxInitialRequests

type: Number

该配置表示入口点能被拆分的最大数量。

更加详细的描述可以查阅我的另一篇文章: blog.csdn.net/YaoDeBiAn

7.splitChunks.minChunks

type: Number

split 前单个非按需导入的 module 的并行数最低下限。

更加详细的描述可以查阅我的另一篇文章: blog.csdn.net/YaoDeBiAn

8.splitChunks.minSize

type: Numbe

所引入的包最小要达到某个大小才能被拆分。

9.splitChunks.maxSize

type: Number

使用 maxSize(全局配置:optimization.splitChunks.maxSize,单个缓存组配置:optimization.splitChunks.cacheGroups[x].maxSize,备用缓存组配置:optimization.splitChunks.fallbackCacheGroup.maxSize)告诉 webpack 尝试将大于 maxSize 字节的块拆分为较小的部分(大于等于 minSize,小于等于 maxSize)。该算法是确定性的,对模块的更改只会产生局部影响。这样,在使用长期缓存时就可以使用它并且不需要记录。maxSize 小于 minSize。

如果不受 maxSize 影响,拆分的 chunk 已经有一个名字 name。而当我们应用了 maxSize 时,基于原来 chunk 拆分出来的 bundle,它的名称将基于 name 进行派生,原理是 webpack 会基于 splitChunks.hidePathInfo 生成一个 key(基于模块名或者模块的 hash 派生),这个 key 会被添加进 name 中,这个 key 个人猜测就是之前案例中的~100271bb,它或许是对应的 bundle 生成的 hash 值的前几位数字或字符。

maxSize 该选项旨在与 HTTP / 2 和长期缓存一起使用。它增加了请求数量以实现更好的缓存。它还可以用于减小文件大小,以加快重建速度。

maxSize 具有比 maxInitialRequest/maxAsyncRequests 更高的优先权。实际优先级排序为:maxInitialRequest/maxAsyncRequests < maxSize < minSize。

对于 minSize 和 maxSize 可以查阅我的另一篇文章: blog.csdn.net/YaoDeBiAn

10.splitChunks.name

type: boolean = true、 function (module, chunks, cacheGroupKey) => string、string

该配置的值有三种选择,可以是一个布尔值(true 和 false),也可以是一个函数(形式如 function (module, chunks, cacheGroupKey) => string),又或许是一个单纯的 String 类型。

当然该配置也可以在缓存组中单独配置,如:splitChunks.cacheGroups.{缓存组的名称}.name。

该配置控制被拆分出来的 chunk 名称。

如果配置为一个布尔值,比如默认下该配置为 true,对于生成的 chunk 的名称,将会基于打包过程中 chunks 和缓存组名称自动生成。

你可以通过给该配置配置一个字符串或者函数来自定义定制打包后 chunk 的名称。如果配置的字符串是静态的或者配置的函数返回的是一个静态的字符串,将会使得被另外单独拆分的所有 chunk 都被打包到一个单独的文件中,这会导致页面首次加载增加,减慢页面的加载。

如果给该配置赋值为一个函数,你会发现参数中的 chunk.name 和 chunk.hash 对于定制打包后生成的 name 非常方便。(这里所说的 chunk 是参数 chunks 参数的某一项,chunks 是所有 chunk 的集合)

如果 splitChunks.name 匹配到一个入口点名称,打包后生成的 bundle 中该入口点将会被删除。

我们推荐在生产环境下将 splitChunks.name 配置成 false,这将保证不会不必要地更改名称。

main.js

import _ from 'lodash';
console.log(_.join(['Hello', 'webpack'], ' '));

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          // cacheGroupKey here is `commons` as the key of the cacheGroup
          name(module, chunks, cacheGroupKey) {
            const moduleFileName = module.identifier().split('/').reduceRight(item => item);
            const allChunksNames = chunks.map((item) => item.name).join('~');
            return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
          chunks: 'all'
}

运行上述的 splitChunks 配置,webpack 将根据 cacheGroup 名和 chunk 名输出一个拆分文件:(以 commons-main-lodash.js.e7519d2bb8777058fa27.js 散列形式作为真实世界输出的示例)。

在为不同的拆分块分配相同的名称时,这些原本应该被单独打包的 module 都将放在一个共享的块中,尽管不建议这样做,因为这可能会导致一次性下载的大小增加。

11.splitChunks.cacheGroups

type: Object

缓存组可以继承和/或覆盖 splitChunks.*;中的任何选项。但是 test、priority 以及 reuseExistingChunk 只能在高速缓存组级别配置。要禁用任何默认缓存组,请将它们设置为 false。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false
}

12.splitChunks.cacheGroups.{cacheGroup}.priority

type: Number

一个模块可以属于多个缓存组。优化将首选具有较高的缓存组 priority。默认组的优先级为负,以允许自定义组获得更高的优先级(自定义缓存组的默认优先级为 0)。

13.splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk

type: Boolean

表示是否使用已有的 chunk,true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的,即几个 chunk 复用被拆分出去的一个 module。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          reuseExistingChunk: true
}

14.splitChunks.cacheGroups.{cacheGroup}.test

type: function (module, chunk) => boolean、RegExp、string

正则过滤 modules,默认为所有的 modules,可匹配模块路径或 chunk 名字,当匹配到某个 chunk 的名字时,这个 chunk 里面引入的所有 module 都会选中。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test(module, chunks) {
            //...
            return module.type === 'javascript/auto';
}

15.splitChunks.cacheGroups.{cacheGroup}.filename

type: String

这个属性会覆盖 output.filename 这个属性,前提是 chunks 设为 initial。同时,这个属性也能全局设置,比如 splitChunks.filename,但是如果 chunks 设为非 initial,webpack 将会报错,所以官方建议这个属性不全局设置。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          filename: '[name].bundle.js'
}

16.splitChunks.cacheGroups.{cacheGroup}.enforce

type: boolean = false

当设为 true 时,webpack 会忽略 splitChunks.minSize、splitChunks.minChunks、splitChunks.maxAsyncRequests、splitChunks.maxInitialRequests 这几个配置项,并且只要某个缓存组设置了 enforce 为 true,匹配的模块就会忽略前面提到的那几个属性,即使有其他的缓存组匹配同样的模块,也没有设置 enforce,同时优先级比设置了 enforce 的高,enforce: true 仍然有效。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {