在 Webpack 的打包结果中,经常会出现同一个模块被复制到多个 chunk 里的情况。比如两个入口文件都引入了同一个工具函数,或者多个动态导入的模块共享了某个依赖,如果处理不当,浏览器就可能重复下载相同的代码。optimization.mergeDuplicateChunks 正是 Webpack 内部用来解决这类重复问题的配置项。它会在 chunk 生成之后、最终资产输出之前,分析模块与 chunk 之间的关联,将那些被多个 chunk 同时包含的模块合并到一个可共享的 chunk 中。理解这个选项的工作机制,有助于我们更好地设计代码分割策略,避免构建产物出现难以排查的体积膨胀。

mergeDuplicateChunks 的作用机制与默认行为
Webpack 在完成模块编译和依赖解析后,会进入 seal 阶段,在这个阶段里它会根据入口配置和动态导入生成一个个 chunk。默认情况下,每个入口会对应一个初始 chunk,而动态导入的模块则会形成独立的异步 chunk。如果同一个模块被多个 chunk 引用,Webpack 并不会自动把它们合并,除非有相应的优化逻辑介入。
optimization.mergeDuplicateChunks 就是其中的一个优化项,它的默认值是 true。它的工作方式可以简单理解为:在 chunk 图构建完成后,遍历所有 chunk,找出那些被多个 chunk 包含的相同模块,然后把这些模块从原 chunk 中移除,并放入一个新的共享 chunk 中。这个共享 chunk 会被所有引用它的 chunk 通过 chunk 关系连接起来。这样做不仅能减少重复代码,还能让浏览器在首次加载时多请求一个文件,但总体下载体积会下降。
值得注意的是,mergeDuplicateChunks 与 splitChunks 是两个独立的机制。splitChunks 是根据开发者配置的规则主动提取公共模块,用来生成独立的缓存组;而 mergeDuplicateChunks 则是在 splitChunks 执行之后仍然存在重复模块时,进行的自动清理。即使你在配置中把 splitChunks 设置为 false,mergeDuplicateChunks 依然会生效。下面的配置示例演示了这种独立性:
// webpack.config.js
module.exports = {
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
mergeDuplicateChunks: true,
splitChunks: false
}
};
在这个例子中,如果 pageA.js 和 pageB.js 都引入了同一个模块 utils.js,那么开启 mergeDuplicateChunks 后,Webpack 会生成一个额外的共享 chunk,而 pageA.bundle.js 和 pageB.bundle.js 中的 utils 代码会被移除。如果将该选项设为 false,则 utils 代码会分别出现在两个 bundle 中,造成重复。
如何验证 mergeDuplicateChunks 的合并效果
要确认 mergeDuplicateChunks 是否真正对构建产物产生了影响,最直接的方法是比较开启和关闭该选项时的输出文件。可以准备一个最小项目,包含两个入口文件,并且两个入口都导入同一个模块。先设置 mergeDuplicateChunks: false 执行一次构建,观察 dist 目录下的文件数量和各文件内容;再改为 true 重新构建,对比产物变化。
例如,在关闭该选项时,dist 目录可能只包含 pageA.bundle.js 和 pageB.bundle.js 两个文件,而 utils 的代码被完整地复制到了这两个文件中。开启之后,dist 目录中会多出一个类似 pageA~pageB.bundle.js 的文件,也就是 Webpack 自动生成的共享 chunk。原来的两个 bundle 文件体积会明显变小,而新增的共享 chunk 则包含了 utils 模块的代码。
除了人工观察文件,还可以通过 Webpack 的 stats 数据来做更精确的分析。在构建命令中加入 --json > stats.json,然后解析 stats.json 中的 chunks 和 modules 字段,就能看到每个 chunk 包含了哪些模块,以及模块在哪些 chunk 中重复出现。下面的命令行示例可以生成 stats 文件:
npx webpack --config webpack.config.js --json > stats.json
在 stats.json 中,可以搜索模块 id 或者模块路径,查看它出现在哪些 chunks 数组里。如果同一个模块出现在两个以上的 chunk 中,说明 mergeDuplicateChunks 没有生效或者该模块被排除在合并范围之外。这种方式适合在大型项目中排查重复模块的来源。
mergeDuplicateChunks 与 splitChunks、runtimeChunk 的协作关系
很多情况下,开发者已经配置了 splitChunks 来提取公共依赖。splitChunks 会根据 cacheGroups 中的规则,把满足条件的模块打包到独立的 chunk 中,并生成一个稳定的缓存组名称。如果 splitChunks 已经覆盖了所有公共模块,那么 mergeDuplicateChunks 能够处理的重复模块就会减少,但它仍然可能在其他地方发挥作用。例如动态导入的模块之间共享的依赖,如果这些依赖没有被 splitChunks 的 cacheGroups 规则匹配到,mergeDuplicateChunks 就可以负责清理这些重复。
与 runtimeChunk 的关系也值得注意。runtimeChunk 主要负责将 Webpack 的运行时代码提取到单独的文件中,它本身不会影响 mergeDuplicateChunks 的合并逻辑。但如果运行时被单独提取后,多个 chunk 对运行时的引用就不会造成重复,这反而减少了 mergeDuplicateChunks 需要处理的对象。因此在实际项目中,同时开启 splitChunks、runtimeChunk 和 mergeDuplicateChunks 通常能获得更干净的输出结构。
不过也需要认识到,mergeDuplicateChunks 生成的共享 chunk 名称是自动生成的,可能包含波浪线或数字,这会对长期缓存和文件指纹造成一定影响。如果项目对 chunk 命名有严格要求,可以通过 splitChunks.name 或 output.chunkFilename 进行统一管理。另外,在微前端或模块联邦场景下,有时希望某个模块在多个子应用中保持独立,避免共享 chunk 加载失败导致整体不可用,这时可以考虑关闭 mergeDuplicateChunks,让每个子应用都带有完整的模块代码。
常见误区与实战建议
一个常见的误区是把 mergeDuplicateChunks 等同于代码分割。实际上,它的目标是去重,而不是创建按需加载的边界。它不会分析模块的异步加载时机,也不会根据缓存策略生成更细粒度的拆分。如果想要实现真正的按需加载和缓存复用,仍然需要依赖 splitChunks 的 cacheGroups 或动态导入语法。
另一个误区是认为关闭 mergeDuplicateChunks 就能减少 HTTP 请求数量。确实,关闭该选项后不会生成额外的共享 chunk,所以初始请求的文件数量可能变少,但代价是每个 chunk 都包含了重复的模块代码,整体传输体积会上升。对于网络带宽受限的用户来说,这反而可能降低加载性能。因此除非有明确的架构需求,否则建议保留默认开启状态。
在排查构建体积问题时,可以结合 Webpack Bundle Analyzer 等工具可视化 chunk 之间的模块分布。如果发现同一个模块出现在多个 chunk 中,先检查 splitChunks 的配置是否合理,再确认 mergeDuplicateChunks 是否被意外关闭。下面是一个简单的对比表格,展示不同配置组合下的典型结果:
| 配置组合 | 输出结果 | 适用场景 |
|---|---|---|
| splitChunks: false, mergeDuplicateChunks: true | 自动生成共享 chunk,重复模块只出现一次 | 不想手动配置 splitChunks,但希望自动去重 |
| splitChunks: true, mergeDuplicateChunks: true | splitChunks 先提取公共模块,mergeDuplicateChunks 清理剩余重复 | 大多数常规项目,推荐组合 |
| splitChunks: false, mergeDuplicateChunks: false | 重复模块被复制到所有引用它的 chunk 中 | 微前端隔离、特殊缓存需求 |
总之,optimization.mergeDuplicateChunks 是 Webpack 内部一个轻量但有效的去重工具。理解它的触发时机和与 splitChunks 的配合方式,可以帮助我们在面对复杂的构建产物时做出更合理的优化决策。日常开发中保持默认开启,遇到特定隔离需求时再有针对性地关闭,是较为稳妥的做法。
WebpackmergeDuplicateChunks重复Chunk修改时间:2026-08-20 20:33:26