SplitChunksPlugin 插件
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 将被选中进行优化。当提供字符串时,有效值为 all、async 和 initial。提供 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.minSizeReduction 和 splitChunks.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 的值会同时设置 maxAsyncSize 和 maxInitialSize 的值。
splitChunks.maxAsyncSize
number
与 maxSize 类似,maxAsyncSize 可以全局应用(splitChunks.maxAsyncSize),应用到缓存组(splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize),或应用到回退缓存组(splitChunks.fallbackCacheGroup.maxAsyncSize)。
maxAsyncSize 与 maxSize 的区别在于,maxAsyncSize 只会影响按需加载的 chunk。
splitChunks.maxInitialSize
number
与 maxSize 类似,maxInitialSize 可以全局应用(splitChunks.maxInitialSize),应用到缓存组(splitChunks.cacheGroups.{cacheGroup}.maxInitialSize),或应用到回退缓存组(splitChunks.fallbackCacheGroup.maxInitialSize)。
maxInitialSize 与 maxSize 的区别在于,maxInitialSize 只会影响初始加载的 chunk。
splitChunks.name
boolean = false function (module, chunks, cacheGroupKey) => string string
也可用于每个缓存组:splitChunks.cacheGroups.{cacheGroup}.name。
拆分 chunk 的名称。提供 false 将保留 chunk 的原始名称,这样不会不必要地更改名称。这是生产构建的推荐值。
提供字符串或函数允许您使用自定义名称。指定一个字符串或一个始终返回相同字符串的函数,会将所有公共模块和 vendor 合并到一个单独的 chunk 中。这可能会导致初始下载量增大并减慢页面加载速度。
如果您选择指定一个函数,您可能会发现 chunk.name 属性(其中 chunk 是 chunks 数组中的一个元素)对于为您的 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.* 的任何选项;但 test、priority 和 reuseExistingChunk 只能在缓存组级别配置。要禁用任何默认缓存组,请将它们设置为 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";
},
},
},
},
},
};
要查看 module 和 chunks 对象中可用的信息,您可以在回调中放置 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.minSize、splitChunks.minChunks、splitChunks.maxAsyncRequests 和 splitChunks.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> 这将导致将 react 和 react-dom 拆分到一个单独的 chunk 中。如果您不确定某个 chunk 中包含哪些包,可以参阅 Bundle Analysis 部分了解详情。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
