知海

SplitChunksPlugin 插件

webpackjsorg-main插件参考

SplitChunksPlugin 插件最初在 webpack 内部图结构中,chunk(以及其中导入的模块)之间通过父子关系相连。CommonsChunkPlugin 插件用于避免它们之间的重复依赖,但无法进行更深入的优化。

自 webpack v4 起,CommonsChunkPlugin 已被移除,取而代之的是 optimization.splitChunks 配置项。

默认行为

开箱即用的 SplitChunksPlugin 对大多数用户来说应该表现良好。

默认情况下,它只影响按需加载的 chunk,因为更改初始 chunk 会影响 HTML 文件中为运行项目而需要包含的 script 标签。

webpack 将根据以下条件自动拆分 chunk:

  • 新 chunk 可以被共享,或者模块来自 node_modules 文件夹
  • 新 chunk 的体积将大于 20kb(在压缩和 gzip 之前)
  • 按需加载 chunk 时,并行请求的最大数量将小于或等于 30
  • 页面初始加载时的并行请求最大数量将小于或等于 30

当尝试满足最后两个条件时,会优先考虑生成更大的 chunk。

配置

webpack 为希望对此功能进行更多控制的开发人员提供了一系列选项。

W> 默认配置是为了符合 web 性能最佳实践而选择的,但适合您项目的最佳策略可能会有所不同。如果您更改配置,应该衡量更改的效果,以确保确实能带来实际好处。

optimization.splitChunks

此配置对象代表了 SplitChunksPlugin 的默认行为。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      chunks: "async",
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

W> 在处理文件路径时,webpack 在 Unix 系统中始终使用 /,在 Windows 系统中始终使用 \。这就是为什么在 {cacheGroup}.test 字段中使用 [\\/] 来表示路径分隔符是必要的。{cacheGroup}.test 字段中直接使用 /\ 在跨平台时会导致问题。

W> 自 webpack 5 起,将入口名称传递给 {cacheGroup}.test,以及使用现有 chunk 的名称作为 {cacheGroup}.name,已不再被允许。

splitChunks.automaticNameDelimiter

string = '~'

默认情况下,webpack 会使用 chunk 的来源和名称来生成名称(例如 vendors~main.js)。此选项允许您指定用于生成名称的分隔符。

splitChunks.chunks

string = 'async' function (chunk) RegExp

此选项指示哪些 chunk 将被选中进行优化。当提供字符串时,有效值为 allasyncinitial。提供 all 特别强大,因为它意味着 chunk 即使在异步和非异步 chunk 之间也可以共享。

请注意,它也应用于回退缓存组(splitChunks.fallbackCacheGroup.chunks)。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      // 包含所有类型的 chunk
      chunks: "all",
    },
  },
};

或者,您可以提供一个函数以获得更多控制。返回值将指示是否包含每个 chunk。

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      chunks(chunk) {
        // 排除 `my-excluded-chunk`
        return chunk.name !== "my-excluded-chunk";
      },
    },
  },
};

如果您使用 webpack 5.86.0 或更高版本,还可以传递一个正则表达式:

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      chunks: /foo/,
    },
  },
};

T> 您可以将此配置与 HtmlWebpackPlugin(用于单页应用)或 ChunksWebpackPlugin(用于多页应用)结合使用。它将为您注入所有生成的 vendor chunk。

splitChunks.maxAsyncRequests

number = 30

按需加载时的最大并行请求数。

splitChunks.maxInitialRequests

number = 30

入口点的最大并行请求数。

splitChunks.defaultSizeTypes

[string] = ['javascript', 'unknown']

设置当使用数字表示大小时,所使用的尺寸类型。

splitChunks.minChunks

number = 1

拆分前模块必须被共享的最小次数。

splitChunks.hidePathInfo

boolean

防止在根据 maxSize 拆分的部分创建名称时暴露路径信息。

splitChunks.minSize

number = 20000 { [index: string]: number }

生成 chunk 的最小体积(以字节为单位)。

splitChunks.minSizeReduction

number { [index: string]: number }

生成 chunk 时,主 chunk(bundle)所需的最小体积减少量(以字节为单位)。这意味着,如果拆分成一个 chunk 不能将主 chunk(bundle)的体积减少到指定字节数,那么即使满足 splitChunks.minSize 的值,也不会进行拆分。

T> splitChunks.minSizeReductionsplitChunks.minSize 都需要满足才能生成 chunk。

splitChunks.enforceSizeThreshold

splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold

number = 50000

超过此大小阈值时,将强制执行拆分,并忽略其他限制(minRemainingSize、maxAsyncRequests、maxInitialRequests)。

splitChunks.minRemainingSize

splitChunks.cacheGroups.{cacheGroup}.minRemainingSize

number = 0

splitChunks.minRemainingSize 选项是在 webpack 5 中引入的,通过确保拆分后剩余的 chunk 的最小体积高于限制,来避免产生零体积的模块。

splitChunks.minRemainingSize 的默认值取决于 mode

模式 默认值
"production" splitChunks.minSize 的值
"development" 0
"none" splitChunks.minSize 的值

除极少数需要深度控制的场景外,通常无需手动指定。

W> splitChunks.minRemainingSize 仅在只剩下一个 chunk 时生效。

splitChunks.layer

splitChunks.cacheGroups.{cacheGroup}.layer

RegExp string function

按模块层将模块分配到缓存组。

splitChunks.maxSize

number = 0

使用 maxSize(全局使用 optimization.splitChunks.maxSize,或针对每个缓存组使用 optimization.splitChunks.cacheGroups[x].maxSize,或针对回退缓存组使用 optimization.splitChunks.fallbackCacheGroup.maxSize)告诉 webpack 尝试将大于 maxSize 字节的 chunk 拆分成更小的部分。这些部分的体积将至少为 minSize(与 maxSize 配合使用)。
该算法是确定性的,对模块的更改只会产生局部影响。因此,它适用于长期缓存,且不需要 records。maxSize 只是一个提示,当模块大于 maxSize 或拆分违反 minSize 时,它可能会被突破。

当 chunk 已有名称时,每个部分将根据该名称派生出一个新名称。根据 optimization.splitChunks.hidePathInfo 的值,它将添加一个基于第一个模块名称或其哈希的键。

maxSize 选项旨在与 HTTP/2 和长期缓存配合使用。它会增加请求数量以获得更好的缓存效果。它也可以用来减小文件体积以加快重建速度。

T> maxInitialRequest/maxAsyncRequests 的优先级高于 maxSize。实际优先级为 maxSize < maxInitialRequest/maxAsyncRequests < minSize

T> 设置 maxSize 的值会同时设置 maxAsyncSizemaxInitialSize 的值。

splitChunks.maxAsyncSize

number

maxSize 类似,maxAsyncSize 可以全局应用(splitChunks.maxAsyncSize),应用到缓存组(splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize),或应用到回退缓存组(splitChunks.fallbackCacheGroup.maxAsyncSize)。

maxAsyncSizemaxSize 的区别在于,maxAsyncSize 只会影响按需加载的 chunk。

splitChunks.maxInitialSize

number

maxSize 类似,maxInitialSize 可以全局应用(splitChunks.maxInitialSize),应用到缓存组(splitChunks.cacheGroups.{cacheGroup}.maxInitialSize),或应用到回退缓存组(splitChunks.fallbackCacheGroup.maxInitialSize)。

maxInitialSizemaxSize 的区别在于,maxInitialSize 只会影响初始加载的 chunk。

splitChunks.name

boolean = false function (module, chunks, cacheGroupKey) => string string

也可用于每个缓存组:splitChunks.cacheGroups.{cacheGroup}.name

拆分 chunk 的名称。提供 false 将保留 chunk 的原始名称,这样不会不必要地更改名称。这是生产构建的推荐值。

提供字符串或函数允许您使用自定义名称。指定一个字符串或一个始终返回相同字符串的函数,会将所有公共模块和 vendor 合并到一个单独的 chunk 中。这可能会导致初始下载量增大并减慢页面加载速度。

如果您选择指定一个函数,您可能会发现 chunk.name 属性(其中 chunkchunks 数组中的一个元素)对于为您的 chunk 选择名称特别有用。

如果 splitChunks.name 与某个入口点的名称匹配,则入口点 chunk 和缓存组将合并为一个单独的 chunk。

T> splitChunks.cacheGroups.{cacheGroup}.name 可用于将模块移动到作为源 chunk 父级的 chunk 中。例如,使用 name: "entry-name" 将模块移动到 entry-name chunk 中。您也可以使用按需加载的命名 chunk,但必须注意所选模块仅在此 chunk 下使用。

main.js

js 复制代码
import _ from "lodash";

console.log(_.join(["Hello", "webpack"], " "));

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          // 这里的 cacheGroupKey 是 `commons`,即 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 还会输出一个名为 commons-main-lodash.js.e7519d2bb8777058fa27.js 的组公共 chunk(哈希值仅为真实世界输出的示例)。

W> 当为不同的拆分 chunk 指定相同名称时,所有 vendor 模块都会被放置到一个共享 chunk 中,但不建议这样做,因为它可能导致下载更多代码。

splitChunks.usedExports

splitChunks.cacheGroups{cacheGroup}.usedExports

boolean = true

找出模块中哪些导出被使用,以混淆导出名称、省略未使用的导出并生成更高效的代码。
当它为 true 时:为每个运行时分析使用的导出;当它为 "global" 时:为所有运行时组合全局分析导出。

splitChunks.cacheGroups

缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项;但 testpriorityreuseExistingChunk 只能在缓存组级别配置。要禁用任何默认缓存组,请将它们设置为 false

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false,
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.priority

number = -20

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

splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk

boolean = true

如果当前 chunk 包含已从主 bundle 中拆分出的模块,则将重用该 chunk,而不是生成新的 chunk。这可能会影响 chunk 最终的文件名。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          reuseExistingChunk: true,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.type

function RegExp string

允许按模块类型将模块分配到缓存组。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        json: {
          type: "json",
        },
      },
    },
  },
};

splitChunks.cacheGroups.test

splitChunks.cacheGroups.{cacheGroup}.test

function (module, { chunkGraph, moduleGraph }) => boolean RegExp string

控制哪些模块被此缓存组选中。省略它将选择所有模块。它可以匹配模块的绝对资源路径或 chunk 名称。当匹配到一个 chunk 名称时,该 chunk 内的所有模块都会被选中。

{cacheGroup}.test 提供函数:

webpack.config.js

js 复制代码
import path from "node:path";

export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        svgGroup: {
          test(module) {
            // `module.resource` 包含文件在磁盘上的绝对路径。
            // 请注意使用 `path.sep` 而不是 / 或 \,以确保跨平台兼容性。

            return (
              module.resource &&
              module.resource.endsWith(".svg") &&
              module.resource.includes(`${path.sep}cacheable_svgs${path.sep}`)
            );
          },
        },
        byModuleTypeGroup: {
          test(module) {
            return module.type === "javascript/auto";
          },
        },
      },
    },
  },
};

要查看 modulechunks 对象中可用的信息,您可以在回调中放置 debugger; 语句。然后在调试模式下运行您的 webpack 构建,以便在 Chromium DevTools 中检查参数。

{cacheGroup}.test 提供 RegExp

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          // 注意使用 `[\\/]` 作为路径分隔符以确保跨平台兼容性。
          test: /[\\/]node_modules[\\/]|vendor[\\/]analytics_provider|vendor[\\/]other_lib/,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.filename

string function (pathData, assetInfo) => string

允许在且仅在它是初始 chunk 时覆盖文件名。
output.filename 中所有可用的占位符在这里也都可用。

W> 此选项也可以全局设置在 splitChunks.filename 中,但不推荐这样做,并且如果 splitChunks.chunks 未设置为 'initial',很可能会导致错误。避免全局设置它。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: "[name].bundle.js",
        },
      },
    },
  },
};

作为函数:

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: (pathData) =>
            // 根据您的需求使用 pathData 对象生成文件名字符串
            `${pathData.chunk.name}-bundle.js`,
        },
      },
    },
  },
};

可以通过在文件名前添加路径前缀来创建文件夹结构:'js/vendor/bundle.js'

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: "js/[name]/bundle.js",
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.enforce

boolean = false

告诉 webpack 忽略 splitChunks.minSizesplitChunks.minChunkssplitChunks.maxAsyncRequestssplitChunks.maxInitialRequests 选项,并始终为此缓存组创建 chunk。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          enforce: true,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.idHint

string

设置 chunk id 的提示。它将被添加到 chunk 的文件名中。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          idHint: "vendors",
        },
      },
    },
  },
};

示例

默认行为:示例 1

js 复制代码
// index.js

import("./a"); // 动态导入
js 复制代码
// a.js
import "react";

// ...

结果: 将创建一个包含 react 的单独 chunk。在 import 调用时,该 chunk 与包含 ./a 的原始 chunk 并行加载。

原因:

  • 条件 1:chunk 包含来自 node_modules 的模块
  • 条件 2:react 大于 30kb
  • 条件 3:在 import 调用时并行请求数为 2
  • 条件 4:不影响初始页面加载时的请求

这背后的原因是什么?react 可能不会像您的应用程序代码那样频繁更改。通过将其移入一个单独的 chunk,该 chunk 可以与您的应用代码分开缓存(假设您使用了 chunkhash、records、Cache-Control 或其他长期缓存方法)。

默认行为:示例 2

js 复制代码
// entry.js

// 动态导入
import("./a");
import("./b");
js 复制代码
// a.js
import "./helpers"; // helpers 大小为 40kb

// ...
js 复制代码
// b.js
import "./helpers";
import "./more-helpers"; // more-helpers 大小也为 40kb

// ...

结果: 将创建一个包含 ./helpers 及其所有依赖项的单独 chunk。在 import 调用时,该 chunk 与原始 chunk 并行加载。

原因:

  • 条件 1:chunk 在两次 import 调用之间共享
  • 条件 2:helpers 大于 30kb
  • 条件 3:在 import 调用时并行请求数为 2
  • 条件 4:不影响初始页面加载时的请求

helpers 的内容放入每个 chunk 将导致其代码被下载两次。通过使用单独的 chunk,这只会发生一次。我们付出的代价是额外的请求,这可以被认为是一种权衡。这就是为什么最小体积设置为 30kb 的原因。

拆分 chunk:示例 1

创建一个 commons chunk,其中包含所有入口点之间共享的代码。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: "commons",
          chunks: "initial",
          minChunks: 2,
        },
      },
    },
  },
};

W> 此配置可能会增大您的初始 bundle,建议在模块不是立即可用时使用动态导入。

拆分 chunk:示例 2

创建一个 vendors chunk,其中包含整个应用中来自 node_modules 的所有代码。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
};

W> 这可能会导致生成一个包含所有外部包的大型 chunk。建议只包含您的核心框架和工具库,并动态加载其余依赖。

拆分 chunk:示例 3

创建一个 custom vendor chunk,其中包含由 RegExp 匹配的特定 node_modules 包。

webpack.config.js

js 复制代码
export default {
  // ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: "vendor",
          chunks: "all",
        },
      },
    },
  },
};

T> 这将导致将 reactreact-dom 拆分到一个单独的 chunk 中。如果您不确定某个 chunk 中包含哪些包,可以参阅 Bundle Analysis 部分了解详情。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。