Webpack 5 的持久化缓存(Persistent Caching)原理是什么?
Webpack 5 的持久化缓存(Persistent Caching)是其最具革命性的特性之一。它允许 Webpack 将构建过程中的编译结果和模块图结构(Module Graph)序列化后存储到磁盘(File System)上,在下一次构建时直接复用,从而大幅度提升二次构建(尤其是冷启动)的速度。
在 Webpack 4 及更早版本中,我们需要依赖第三方插件(如 hard-source-webpack-plugin)或特定的 loader 缓存(如 cache-loader、babel-loader?cacheDirectory)来实现类似功能。而 Webpack 5 将其提升为了原生内置核心能力。
以下是 Webpack 5 持久化缓存的核心工作原理:
1. 核心缓存对象:缓存的不仅是代码,而是“图(Graph)”
传统 loader 缓存只保存“单文件转换后的代码”,而 Webpack 在构建时最耗时的部分之一是依赖图的构建(Module Graph)和 Chunk 图的生成(Chunk Graph)。
Webpack 5 的持久化缓存会序列化保存以下内容:
- 模块对象(Module):包括解析后的 AST、依赖关系、转译后的代码。
- 依赖图(Module Graph)和分块图(Chunk Graph):整体项目的拓扑结构。
- 资源信息(Assets):最终输出的文件信息。
原理:再次构建时,如果缓存有效,Webpack 可以跳过递归解析 AST 和分析依赖的过程,直接将磁盘上的图结构反序列化回内存,瞬间完成依赖图的恢复。
2. 快照机制(Snapshot Algorithm):高效检测变更
为了判断磁盘上的缓存是否仍然有效,Webpack 5 引入了文件系统快照(Snapshot)机制,而不是简单地对所有文件做 Hash(那太慢了)。
快照包含两种判定模式:
- 时间戳(Timestamp /
mtime):比较文件的最后修改时间。 - 文件内容 Hash:如果时间戳变了,再进一步对比文件内容的 Hash 值(避免仅仅
touch了一下文件就导致缓存失效)。
优化的快照策略(Managed Paths)
项目依赖的 node_modules 文件极其庞大,如果对每个文件都做快照比对,性能会非常差。Webpack 5 对此做了分级处理:
managedPaths(托管路径):默认包含node_modules。Webpack 认为这些包只有在安装/更新时才会变,因此只检测package.json的版本号或外层目录的时间戳,跳过内部几万个文件的逐个比对。immutablePaths(不可变路径):比如包含 Hash 的路径或绝对不会改变的路径,直接跳过所有检查。- 源码路径:进行细化校验(时间戳 + Hash)。
3. 高效的序列化与反序列化(Serialization)
为了将内存中的 JavaScript 对象(特别是庞大且复杂的 ModuleGraph)快速写入磁盘并读取,Webpack 5 并没有使用原生的 JSON.stringify/parse(性能差且无法处理循环引用、函数、Buffer 等)。
Webpack 5 实现了一套自定义的高性能二进制序列化器(Serializer):
- 支持处理复杂的数据结构(如循环依赖、
Map、Set、Buffer等)。 - 采用了类似 Protocol Buffers 的紧凑二进制格式,写入和读取速度极快,磁盘占用小。
4. 缓存失效(Cache Invalidation)的触发条件
Webpack 5 在生成缓存时,会计算一个缓存键(Cache Key),同时记录影响构建的全局依赖。以下任何一项发生变化,缓存都会自动失效并重新构建:
- 源码变更:快照检测到某些文件的时间戳/Hash 不一致。
- 配置变更:
webpack.config.js被修改。Webpack 默认会将配置文件及其依赖加入到buildDependencies中。 - 依赖包变更:
node_modules中安装了新包或更新了版本(通过检查package.json/lockfile)。 - 环境/CLI 变更:Node.js 版本改变、Webpack 版本改变、传递给 CLI 的环境变量改变。
5. 持久化缓存的工作流程图解
[构建开始]
│
▼
1. 计算 Cache Key (包含 Webpack 版本、配置、环境变量等)
│
├─► [无缓存/Key 不匹配] ──► 全量重新构建 ──► 写入新缓存到磁盘 ──► [结束]
│
▼ [命中有效缓存目录]
2. 检查 Snapshot (对比文件时间戳/Hash/node_modules)
│
├─► [快照失效] ──────────► 增量构建 (仅编译变动模块) ──► 更新磁盘缓存 ──► [结束]
│
▼ [快照全部有效 (完全命中)]
3. 读取磁盘二进制缓存,反序列化恢复 ModuleGraph / ChunkGraph
│
▼
4. 跳过 Parse / Transform / Seal 阶段,直接生成 Output
│
▼
[构建完成] (速度提升数倍至数十倍)
6. 配置示例
在 Webpack 5 中开启持久化缓存非常简单:
// webpack.config.js
module.exports = {
// ...
cache: {
type: 'filesystem', // 使用文件系统持久化缓存
buildDependencies: {
// 当配置文件发生变化时,使缓存失效
config: [__filename],
},
name: 'development-cache', // 缓存名称,不同环境建议分开
},
};
缓存文件默认存储在 node_modules/.cache/webpack 目录下。
总结
Webpack 5 持久化缓存的核心原理可以概括为:
“基于自定义序列化技术将 ModuleGraph 等内存图结构存盘,结合基于分级的 Snapshot 快照算法精准检测变更,实现冷启动时的极致秒开。” 这一机制彻底解决了大中型前端项目构建缓慢的痛点。