Webpack 5 的持久化缓存把构建过程中生成的大量中间数据从内存搬到了磁盘,这让冷启动增量构建成为可能。Data Retention 数据保留,就是描述这些缓存文件应该保存哪些内容、保留多长时间、以及什么条件下判定为过期的一套策略。它不是单一的某个配置项,而是由 cache 字段下多个参数共同决定的行为集合。理解这套机制后,开发者可以根据项目依赖变化频率、网络环境和部署方式,主动调整缓存保留边界,避免无效缓存带来的构建污染。

一、Webpack 5 持久化缓存与 Data Retention 的定位
Webpack 5 之前,缓存主要依赖内存级别的模块缓存。每次进程退出后,这些数据就会丢失,下一次启动构建仍然要从头解析依赖图。Webpack 5 将缓存类型扩展到了 filesystem,允许将模块依赖图、已解析的 loader 结果、生成的 chunk 信息以及 source 等中间产物序列化到本地文件中。这样即使是全新的进程,只要缓存目录没有被删除,就能恢复上次构建的大部分计算成果。
Data Retention 在整个文件系统缓存体系中,承担的是生命周期管理的角色。它需要回答几个关键问题:缓存数据应该由哪些文件来标记依赖关系?目录下堆积的旧缓存何时清理?当配置文件发生变化时,缓存是否应该整体失效?这些问题如果处理不当,会出现构建速度很快但产物陈旧的假象,或者每次改动都触发全量构建,失去持久化缓存的意义。
下面这份配置展示了最基础的持久化缓存启用方式,也是讨论数据保留策略的起点。
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
maxAge: 7 * 24 * 60 * 60 * 1000,
idleTimeout: 60 * 1000,
},
};其中 type 指定使用文件系统缓存;buildDependencies 将当前配置文件加入缓存依赖,一旦配置内容变化,相关缓存会自动失效;maxAge 控制缓存条目的最长保留时间;idleTimeout 则是在构建空闲一段时间后释放内存缓存。
二、影响数据保留时间与失效的配置项
maxAge 是数据保留最直观的控制参数,单位是毫秒。它表示一个缓存条目从写入开始,最多能在缓存目录中保存多久。超过这个时间后,Webpack 会认为该条目可能已经过期,并在下一次构建时忽略它,重新执行解析和编译流程。对于依赖更新频繁的项目,短周期可以降低读取到旧依赖的风险;而对于依赖锁死、构建环境稳定的大型项目,延长 maxAge 能显著提高二次构建的命中率。
idleTimeout 则和构建过程的生命周期有关。Webpack 在内存中会保留一部分活跃模块及其依赖,避免频繁读取磁盘。idleTimeout 指定了构建结束后,这些内存缓存可以保留多久。如果短时间内连续运行 webpack,比如监听模式下的热更新,较长的 idleTimeout 能减少重复读盘;但长时间占用内存也会增加进程的常驻开销,因此在 CI 容器中通常会设置较小的值。
buildDependencies 是数据保留策略中最容易被忽略但又非常关键的一环。它并不直接控制保留时长,而是通过声明缓存依赖来源,决定什么时候让缓存整体失效。除了配置文件,还应该将 package.json、锁文件以及所有会影响构建逻辑的文件加入进去。例如 Babel 配置文件、PostCSS 配置、环境变量文件等。否则一旦这些文件更新,Webpack 仍可能复用旧的缓存结果,产生难以排查的构建异常。
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
cache: {
type: 'filesystem',
name: `my-app-${isProduction ? 'prod' : 'dev'}`,
maxAge: isProduction ? 30 * 24 * 60 * 60 * 1000 : 2 * 24 * 60 * 60 * 1000,
idleTimeout: 60 * 1000,
buildDependencies: {
config: [__filename],
package: ['package.json', 'yarn.lock'],
},
},
};
};上面的配置使用 name 将生产环境和开发环境的缓存目录分开,避免同一套缓存被不同模式的构建互相污染。生产缓存的 maxAge 保留时间更长,因为生产构建通常依赖稳定;开发缓存则可以更短,从而更快反映出依赖或配置的变化。
三、路径约束与缓存数据保留边界
Webpack 在缓存模块依赖时,会记录文件路径和文件内容哈希。对于 node_modules 这类第三方依赖目录,它们通常是稳定的,不会像业务源码那样频繁修改。如果 Webpack 每次构建都要重新扫描这些目录,会浪费大量时间。managedPaths 配置允许指定受管理的路径,Webpack 会假设这些路径下的文件不会频繁变化,从而降低校验成本。
immutablePaths 则更进一步,表示路径下的文件完全不可变。Webpack 不会对这些路径做任何变更检测,直接信任缓存中的模块信息。这对于 pnpm 等采用内容寻址存储的 node_modules 布局尤其有用,因为每个包版本都有独立的目录,内容天然不可变。
const path = require('path');
module.exports = {
cache: {
type: 'filesystem',
store: 'pack',
compression: 'gzip',
managedPaths: [path.resolve(__dirname, 'node_modules')],
immutablePaths: [/node_modules[\\/]\.pnpm/],
},
};store 参数控制缓存文件的存储格式。默认值是 auto,在多数情况下使用 JSON 序列化。pack 格式会把多个缓存条目合并成一个单一打包文件,可以减少大量小文件对文件系统造成的压力,尤其适合 Windows 或网络文件系统。pack 的写入速度略慢于默认格式,但数据保留的完整性和读取性能通常更好。
路径约束的意义在于划定数据保留的信任边界。对于稳定的第三方依赖,可以放心地让缓存长期保留;对于业务源码,则需要通过哈希和构建依赖及时触发更新。这种分层策略能够在不牺牲正确性的前提下,最大限度减少重复计算。
四、工程化实践:缓存保留策略如何落地
在真实项目中,数据保留策略需要结合开发、测试、生产三个阶段分别设计。开发环境追求快速反馈,缓存保留时间可以短一些,并且应该把 package.json、锁文件和各类配置文件都加入 buildDependencies,保证依赖或工具链更新后立即失效。测试环境尤其是 CI 环境,通常每次运行都使用干净的目录,此时缓存需要依赖外部缓存服务来持久化,比如把 node_modules/.cache 目录上传到对象存储。
生产环境通常只执行一次构建,缓存带来的收益体现在增量发布场景。如果团队采用持续部署,每次构建都基于上一次构建的缓存,那么 maxAge 可以设置得较长,比如 30 天。同时要确保 buildDependencies 包含 lockfile,否则依赖升级后缓存没有失效,生产产物可能仍然引用旧版本,引发线上故障。
下面是一份适用于 CI 和生产环境的完整缓存配置,它结合了时间保留、路径管理和依赖追踪。
const path = require('path');
module.exports = {
mode: 'production',
cache: {
type: 'filesystem',
name: 'production-cache',
store: 'pack',
compression: 'gzip',
maxAge: 30 * 24 * 60 * 60 * 1000,
idleTimeout: 10 * 1000,
buildDependencies: {
config: [
__filename,
path.resolve(__dirname, 'babel.config.js'),
path.resolve(__dirname, '.browserslistrc'),
],
package: ['package.json', 'pnpm-lock.yaml'],
},
managedPaths: [path.resolve(__dirname, 'node_modules')],
immutablePaths: [/node_modules[\\/]\.pnpm/],
},
};需要注意的是,缓存保留时间并不是越长越好。过长的 maxAge 可能导致缓存目录无限膨胀,占用大量磁盘空间。Webpack 并不会在每次构建时主动清理所有过期条目,而是在写入新缓存时触发部分清理。因此对于长期运行的开发机或 CI 代理,定期清理缓存目录仍然是有必要的。可以在脚本中设置一个磁盘容量阈值,超过后删除 node_modules/.cache 中超过一定时间的文件。
Data Retention 数据保留机制让 Webpack 5 的缓存从简单的内存复用升级为可持久化、可配置的构建资产。合理设置保留时间、路径边界和依赖追踪,可以让大型前端项目的构建速度获得数量级提升,同时保持产物的一致性。如果只开启默认的 filesystem 缓存而不关注这些参数,项目的构建效率往往无法达到最优状态。

