合适的使用场景
webpack cache 的效果是用磁盘空间换 compile 速度.
所以在我看来, webpack cache 更合适在本地 dev 的场景使用, 因为本地 dev 触发 compile 比 ci 服务器频繁得多, 并且改动更小, 可以命中更多缓存, 也能大幅提升开发体验.
webpack cache 实践经验
我的实践经验是基于公司的一个老项目.
效果是单个子项目的dev速度从49秒减少到了7秒.
未开启持久化存储的编译速度
开启持久化存储的编译速度
原理
那么,为什么开启持久化缓存之后构建性能会有如此巨大的提升呢?一言蔽之,Webpack5 会将首次构建出的 Module、Chunk、ModuleGraph 等对象序列化后保存到硬盘中,后面再运行的时候就可以跳过一些耗时的编译动作,直接复用缓存信息。
webpack cache 实现流程
1.在 compile 流程中读取与保存 cache
在compilation里有三个变量: _modulesCache, _assetsCache, _codeGenerationCache. 分别在对应的时间点读取和写入 cache:
- _modulesCache 读取: 在 addModule 的时候读取. module 的 build 在读取之后, 如果命中 cache, 那么
needBuild就会是false, 跳过这个 module 的 build, 来节省时间. (build 做的事是运行 loader 和 parse 并分析 ast ) - _modulesCache 写入: 在 module 的 build 完成之后, 把 build 后的 module 结果按照 module 的 id 存储起来.
- _codeGenerationCache: 读取和写入分别在
module.codeGeneration()的前后. - _assetsCache: 在最终生成 assets 的阶段, 在获取 manifest 以后读取 cache, 如果命中, 则不逐个调用
fileManifest.render()来产生 assets 了. 如果不命中, 则调用 render 后写入 cache.
构建流程
Webpack 的构建过程大致上可划分为三个阶段:
初始化,主要是根据配置信息设置内置的各类插件
Make - 构建阶段,从 entry 模块开始,执行:
- 读入文件内容
- 调用 Loader 转译文件内容
- 调用 acorn 生成 AST 结构
- 分析 AST,确定模块依赖列表
- 遍历模块依赖列表,对每一个依赖模块重新执行上述流程,直到生成完整的模块依赖图 —— ModuleGraph 对象
Seal - 生成阶段,过程:
- 代码转译,如 import 转换为 require 调用
- 分析运行时依赖
- 遍历模块依赖图,对每一个模块执行:
- 合并模块代码与运行时代码,生成 chunk
- 执行产物优化操作,如 Tree-shaking
- 将最终结果写出到产物文件
过程中存在许多 CPU 密集型操作,例如调用 Loader 链加载文件时,遇到 babel-loader、eslint-loader、ts-loader 等工具时可能需要重复生成 AST;分析模块依赖信息时则需要遍历 AST,执行大量运算;Seal 阶段也同样存在大量 AST 遍历,以及代码转换、优化操作,等等。
实现缓存
在引入持久化缓存之前,Webpack 在每次运行时都需要对所有模块完整执行上述构建流程,假设业务项目中有 1000 个文件,则每次执行 npx webpack 命令时都需要从 0 开始执行 1000 次构建、生成逻辑。
而 Webpack5 的持久化缓存功能则尝试将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。缓存执行时机如下图:
如图,Webpack 在首次构建完毕后将 Module、Chunk、ModuleGraph 三类对象的状态序列化并记录到缓存文件中;在下次构建开始时,尝试读入并恢复这些对象的状态,从而跳过执行 Loader 链、解析 AST、解析依赖等耗时操作,提升编译性能。
用法详解
理解缓存的核心原理后,我们再回过头来看看 cache 提供的配置项列表,下面摘录几个比较常用的配置项:
官方文档:webpack.js.org/configurati…
- cache.type:缓存类型,支持 'memory' | 'filesystem',需要设置 filesystem 才能开启持久缓存
- cache.cacheDirectory:缓存文件存放的路径,默认为 node_modules/.cache/webpack
- cache.buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为项目配置文件,如:
- module.exports = {
- cache: {
- buildDependencies: {
- config: [path.join(__dirname, 'webpack.dll_config.js')],
- },
- },
- };
- cache.managedPaths:受控目录,Webpack 构建时会跳过新旧代码哈希值与时间戳的对比,直接使用缓存副本,默认值为 ['./node_modules']
- cache.profile:是否输出缓存处理过程的详细日志,默认为 false
- cache.maxAge:缓存失效时间,默认值为 5184000000 (时间戳2个月)
使用时通常关注上述配置项即可,其它如 idleTimeout、idleTimeoutAfterLargeChanges 等项均与 Webpack 内部实现算法有关,与缓存效果关系不大,无需关注。