webpack 缓存
一、为什么要缓存
在 webpack 基础篇我们知道 webpack 工作流程大概有四部分: 输入 -> 模块处理(loader) -> plugin -> 输出
其中 loader 阶段详细分为以下步骤:
- 调用 Loader 转译文件内容
- 调用 acorn 生成 AST 结构
- 分析 AST,确定模块依赖列表
- 遍历模块依赖列表,对每一个依赖模块重新执行上述流程,直到生成完整的模块依赖图
plugin 阶段详细分为以下步骤:
- 遍历模块依赖图,对每一个模块执行
1.1 代码转译,如 import 转换为 require 调用
1.2 分析运行时依赖 - 合并模块代码与运行时代码,生成 chunk
- 执行产物优化操作,如 Tree-shaking
- 将最终结果写出到产物文件
这些过程中需要进行大量的计算和转换,例如遇到 babel-loader、eslint-loader、ts-loader 等工具时可能需要重复生成 AST;分析模块依赖时则需要遍历 AST,执行大量运算;plugin 阶段也同样存在大量 AST 遍历,以及代码转换、优化操作,等等。
而 Webpack5 的持久化缓存功能则将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。
二、webapck5 缓存
在 webpack5 中提供了内置缓存方案。通过使用 cache 这个对象来实现缓存生成的 webpack 模块和 chunk,来改善构建速度。
module.exports = {
...,
cache: {
type: 'filesystem', //默认为memory,也就是存储到内存
},
}
cache 还提供了若干用于配置缓存效果、缓存周期的配置项,包括:
- cache.type:缓存类型,支持 'memory' | 'filesystem',需要设置为 filesystem 才能开启持久缓存。而 menory 则是缓存在内存中(当cache设置为true时,同效)
- cache.cacheDirectory:缓存文件路径,默认为 node_modules/.cache/webpack
- cache.buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件
module.exports = {
//...,
cache: {
type: 'filesystem', //默认为memory,也就是存储到内存
buildDependencies: {
config: [__filename], /* 将你的 config 添加为 buildDependency,以便在改变 config 时获得缓存无效*/
},
//...,
},
}
TIP
cache 的属性 type 会在开发模式下被默认设置成 memory,而且在生产模式中被禁用,所以如果想要在生产打包时使用缓存需要显式的设置。
snapshot
此外,和持久化缓存另外一个相关的配置是:snapshot。snapshot 相关的配置决定了缓存内存生成 snapshot 时所采用的策略(timestamps | hash | timestamps + hash),而这个策略最终会影响到缓存是否失效,即 webpack 是否决定来使用缓存。
- hash:比较内容哈希来确定无效(比 timestamp 更昂贵,但更改的频率较低)。
- timestamp:比较 timestamps 来确定无效。
如何选择那种模式?官方是这样回答的:
- { hash: true }:对 CI 缓存很有帮助,使用新的 checkout,不需要保存时间戳,并且使用哈希。
- { timestamp: true }:对应本地开发缓存很用帮助。
- { timestamp: true, hash: true }:对于以上提到的两者都很有帮助。首先比较时间戳,这代价很低,因为 webpack 不需要读取文件来计算它们的哈希值。仅当时间戳相同时才会比较内容哈希,这对初始构建的性能影响很小。
const path = require('path')
module.exports = {
// ...
snapshot: {
// 针对包管理器维护存放的路径,如果相关依赖命中了这些路径,那么他们在创建 snapshot 的过程当中不会将 timestamps、 hash 作为 snapshot 的创建方法,而是 package 的 name + version
// 一般为了性能方面的考虑,
managedPaths: [path.resolve(__dirname, '../node_modules')],
immutablePaths: [],
// 对于 buildDependencies snapshot 的创建方式
buildDependencies: {
// hash: true
timestamp: true
},
// 针对 module build 创建 snapshot 的方式
module: {
// hash: true
timestamp: true
},
// 在 resolve request 的时候创建 snapshot 的方式
resolve: {
// hash: true
timestamp: true
},
// 在 resolve buildDependencies 的时候创建 snapshot 的方式
resolveBuildDependencies: {
// hash: true
timestamp: true
}
}
}
三、webpack4 缓存
webpack4 缓存只能借助第三方组件实现类型效果,包括:
- 使用 cache-loader
- 使用 hard-source-webpack-plugin
- 使用 Loader(如 babel-loader、eslint-loader))自带的缓存能力
例如 cache-loader 能够将 Loader 处理结果保存到硬盘,下次运行时若文件内容没有发生变化则直接返回缓存结果,用法:
- 安装依赖
pnpm install -D cache
- 修改配置,注意必须将 cache-loader 放在 loader 数组首位,例如:
module.exports = {
// ...
module: {
rules: [{
test: /\.js$/,
use: ['cache-loader', 'babel-loader', 'eslint-loader']
}]
},
// ...
};
三、长久缓存
长久缓存指的是游览器对我们前端文件的缓存。当我们请求的文件名没有变得时候,游览器就会直接使用缓存的文件,否则重新请求加载。
optimization.moduleIds = 'deterministic'
optimization.chunkIds = 'deterministic'
此配置在生产模式下是默认开启的,它的作用是以确定的方式为 module 和 chunk 分配 3-5 位数字 id,相比于 v4 版本的选项 hashed,它会导致更小的文件 bundles。 由于 moduleId 和 chunkId 确定了,构建的文件的 hash 值也会确定,有利于浏览器长效缓存。同时此配置有利于减少文件打包大小。
在开发模式下,建议使用:
optimization.moduleIds = 'named'
optimization.chunkIds = 'named'