Webpack 5 之后,不少团队在升级时注意到一个变化:曾经频繁变化的 chunk 哈希值开始趋于稳定,即使新增或删除某个局部模块,vendor 文件也不一定跟着改。这个现象背后,正是 Webpack 5 对模块标识和运行时处理方式的改进。有人把这一系列改进称为 Compassionate Universe,可以理解为一套让构建输出变得更具确定性和可预测性的策略集合。

模块ID漂移与确定性模块ID
Webpack 4 默认使用递增的数字作为模块ID。这种分配方式在单次构建中没有任何问题,但项目只要发生一点改动,比如在入口文件里多引入一个模块,后续所有模块的ID都可能整体平移。模块ID一变,引用这些模块的代码就会变化,最终导致 chunk 哈希大量改变。对浏览器缓存来说,这意味着用户需要重新下载未发生实际逻辑变化的文件,缓存命中率大打折扣。
Webpack 5 引入了 optimization.moduleIds 的 deterministic 选项。该模式下,模块ID不再依赖模块在依赖图中的排列顺序,而是基于模块路径和内容生成一个短字符串。只要模块没有被删除或重命名,它的ID在多次构建中就保持一致。这对于大型项目尤其重要,因为开发分支的微小改动不会连累公共依赖的缓存。
module.exports = {
optimization: {
moduleIds: 'deterministic',
},
};
开启后就完成了第一步。需要注意的是,如果项目从 Webpack 4 升级而来,旧的缓存文件仍然使用数字ID,迁移后第一次构建的产物会变,这是预期行为。之后只要保持配置不变,模块ID就会稳定下来。
运行时拆分与长期缓存
除了模块ID,Webpack 在每次构建时生成的运行时代码也是哈希变化的来源之一。运行时代码负责模块加载、懒加载和热更新等逻辑,默认情况下会打包进每个 chunk。只要运行时逻辑发生变化,所有 chunk 的哈希都会跟着变。
Webpack 5 推荐把运行时单独拆成一个文件,通过 optimization.runtimeChunk 设置为 single 来实现。这样运行时代码只存在于一个独立 chunk 中,业务代码和第三方库不会因为运行时的小改动而整体失效。配合 output.filename 和 output.chunkFilename 使用 contenthash,可以让文件名只反映文件自身内容的变化。
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].js',
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
这段配置把运行时、业务代码和第三方库分开。当业务代码更新时,vendors chunk 和 runtime chunk 的哈希保持不变,用户只需要重新下载发生变化的业务文件。长期缓存的收益在频繁发布的场景下非常明显。
资源模块的慈悲化处理
Webpack 5 内置了 Asset Modules,用来替代过去常用的 file-loader、url-loader 和 raw-loader。这一变化让静态资源的处理方式更加统一,也减少了因 loader 版本冲突带来的构建错误。开发者不再需要为了一个图片资源安装三个不同的 loader,再配置一堆 options。
使用 type: 'asset' 可以自动在导出文件和数据 URL 之间切换,根据资源大小决定内联还是单独输出。默认小于 8KB 的资源会被内联为 base64 数据,超过则生成文件并返回 URL。这种策略对图标、小图片等资源非常友好,可以减少 HTTP 请求数量,同时避免大文件被塞进 JS 导致体积膨胀。
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
},
{
test: /\.svg$/,
type: 'asset/source',
},
],
},
};
上面的规则处理了常见位图资源和 SVG 文件。对于 SVG,使用 asset/source 可以获取原始文本内容,方便在组件中直接操作 SVG 结构。这种资源处理方式比旧方案更符合直觉,也避免了 loader 链过长导致的调试困难。
依赖解析的容错与回退
Webpack 5 在模块解析上也做了一些更友好的调整。比如 resolve.fallback 可以显式指定 Node.js 核心模块在浏览器环境中的替代方案。以前很多项目会遇到引入 Node 内置模块时报错的问题,现在可以通过配置 polyfill 或空模块来优雅处理。
另外,Webpack 5 对未定义导出、循环依赖等问题的警告信息也更加清晰,会明确指出是哪个模块、哪个导出出现了问题。这种详细度对大型项目的维护非常有帮助,减少了排查时间。结合 cache 配置启用文件系统缓存,还能进一步提升增量构建速度。
module.exports = {
resolve: {
fallback: {
path: false,
fs: false,
},
},
cache: {
type: 'filesystem',
},
};
以上配置关闭了 Node.js 的 path 和 fs 模块在浏览器端的解析,同时开启文件系统缓存。二次构建时,Webpack 会读取磁盘上的缓存,跳过大量重复计算,这在开发模式下尤其明显。
与旧版方案的实际对比
为了更直观地理解 Compassionate Universe 带来的变化,可以做一个简单实验。使用 Webpack 4 默认配置构建一个包含 lodash 和业务代码的项目,记录 vendors 文件的哈希。然后在业务入口中新增一个无关模块,再次构建,你会发现 vendors 的哈希发生了变化。切换到 Webpack 5 并开启确定性模块ID、运行时拆分和 contenthash 后,同样操作下 vendors 的哈希保持不变。
这个差异的本质在于,旧版 Webpack 的模块ID和运行时分散在业务代码中,任何上游改动都会向下传播。Webpack 5 通过解耦这些依赖,让每个文件的哈希只反映它自己的内容。长期来看,这种稳定性带来的缓存收益会随着项目规模增长而放大。
Compassionate Universe 并不是 Webpack 官方文档里某个单独章节的名字,它更像是对 Webpack 5 中一系列确定性策略的概括。要真正用好它,需要同时关注模块ID、运行时拆分、资源模块和缓存配置。这些选项组合起来,才能让构建产物在多次发布之间保持足够的稳定,让浏览器缓存真正发挥作用。
Webpack 5Compassionate Universe确定性模块ID修改时间:2026-09-17 16:32:30