Webpack 5 相比 Webpack 4 有一个非常容易被忽视但影响深远的改动,那就是模块标识与 Chunk 标识的生成策略重构,官方文档中称为 Reconciliation,可以翻译为和解或协调机制。它的核心目标是:让不同机器、不同构建次数下生成的 bundle 产物尽可能一致,从而使持久化缓存可以跨环境复用。如果你遇到过团队协作时有人本地缓存有效、有人全部失效的怪现象,或者二次构建始终命不中缓存,大概率就是没有正确理解这套机制。本文将从原理、配置和实践三个层面把这个问题讲透。

一、为什么需要 Reconciliation:模块 ID 不稳定带来的缓存灾难
先看问题的根源。Webpack 内部会为每个模块分配一个数字类型的 module ID,为每个 Chunk 分配 chunk ID。在 Webpack 4 默认配置下,module ID 是按模块的引用顺序自增的数字。这意味着你新增一个文件、调整一个 import 的位置,后面所有模块的 ID 都会整体偏移。
模块 ID 会直接写入最终产物。一旦 ID 变了,即使模块内容一个字节都没改,包含该模块的 chunk 内容也会变化,进而导致 [contenthash] 改变,浏览器缓存和 CI 环境的持久化缓存同时报废。更麻烦的是,引用顺序还受 node_modules 依赖安装顺序、操作系统文件系统排序差异的影响,这就是两台机器构建产物哈希不一致的典型原因。
Webpack 4 时代大家通常借助 HashedModuleIdsPlugin 来缓解,它把模块路径做一次哈希作为 ID。但路径哈希依赖项目绝对路径或上下文路径的写法,跨机器时路径不同(比如 Windows 的 C:\Users\xxx\project 与 CI 上的 /home/runner/project)依然可能导致 ID 不一致。Webpack 5 把这个问题提到了内核层面统一解决。
二、deterministic 策略:Webpack 5 的默认和解方案
Webpack 5 提供了两个顶级配置项:optimization.moduleIds 和 optimization.chunkIds。生产环境默认值都是 deterministic,这也是 Reconciliation 机制的核心。
deterministic 的策略是:以模块或 Chunk 的相对路径作为输入,经过哈希运算后取一段固定长度的数字作为 ID(默认 3 位数字,即 0 到 999)。因为用的是相对于项目上下文的路径,绝对路径差异被消除;因为不依赖引用顺序,增删模块不会影响其他模块的 ID。这就同时满足了两个条件:跨机器稳定、跨构建稳定。
需要注意 deterministic 与 size、total-size 的区别。size 模式下 ID 按模块体积排序分配,追求产物体积最小;hashed 模式则对路径做完整 md4 哈希,ID 更长,碰撞概率更低,适合超大项目。普通业务项目用默认的 deterministic 即可,超过几万个模块的巨型 monorepo 才需要考虑 hashed。
module.exports = {
optimization: {
// 生产环境默认即为 deterministic,这里显式写出便于理解
moduleIds: 'deterministic',
chunkIds: 'deterministic',
},
output: {
filename: '[name].[contenthash:8].js',
},
};另外要区分两个容易混淆的概念:[contenthash] 与 [fullhash]。fullhash 是整个编译的哈希,任何一个模块变化都会改变所有文件名;contenthash 只基于该 chunk 自身包含的内容计算。Reconciliation 机制让 module ID 稳定下来之后,contenthash 才能真正做到只改了哪个 chunk 就只更新哪个文件,浏览器端的缓存命中率随之大幅提升。
三、配合文件系统缓存:把二次构建压到秒级
Reconciliation 解决的是产物稳定性的前半段,后半段要靠 Webpack 5 新增的持久化缓存配置 cache.filesystemSystem,准确写法是 cache: { type: 'filesystem' }。它会把模块解析结果、依赖图、生成产物等中间状态序列化到磁盘,下次构建时增量恢复,只重新处理真正变化的文件。
这里有一个关键的联动关系:如果模块 ID 不稳定,即使磁盘缓存命中了,最终产物的文件名和内部引用也会变化,等于缓存白做。这就是为什么说 Reconciliation 是持久化缓存的基石,两者必须配套使用才有意义。
module.exports = {
cache: {
type: 'filesystem',
// 缓存目录,建议放在项目内便于版本管理工具忽略
cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
buildDependencies: {
// 配置文件本身变化时让缓存失效
config: [__filename],
},
version: '1.0.0',
},
};实践中还有几个坑要注意。第一,buildDependencies 里务必把 webpack 配置文件、babel 配置等纳入,否则修改 loader 配置后缓存不会失效,会构建出旧规则产物。第二,CI 环境如果要跨流水线复用缓存,需保证 node_modules 的安装方式确定(使用 lock 文件),并且缓存目录随流水线归档保存。第三,如果引入了 DLL 或 externals,要注意这些外部依赖的版本号变化不会体现在模块 ID 中,必要时手动更新 cache.version 来强制刷缓存。
配置得当的情况下,中型项目的二次冷启动构建从一两分钟降到十秒以内是很常见的,热构建甚至只需一两秒,团队每个成员和 CI 机器都能共享这份收益。这正是 Reconciliation 与持久化缓存组合带来的价值:确定性不仅让缓存可用,也让构建结果可预期、可复现。
Webpack 5Reconciliation持久化缓存修改时间:2026-09-04 17:36:36