- Improve build performance with Persistent Caching (通过使用持久性缓存来提高构建性能)
Cache基本配置
在 Webpack 4 中,cache 只是单个属性的配置,所对应的赋值为 true 或 false,用来代表是否启用缓存,或者赋值为对象来表示在构建中使用的缓存对象。而在 Webpack 5 中,cache 配置除了原本的 true 和 false 外,还增加了许多子配置项,例如:
- cache.type:缓存类型。值为 'memory'或‘filesystem’,分别代表基于内存的临时缓存,以及基于文件系统的持久化缓存。在选择 filesystem 的情况下,下面介绍的其他属性生效。
- cache.cacheDirectory:缓存目录。默认目录为 node_modules/.cache/webpack。
- cache.name:缓存名称。同时也是 cacheDirectory 中的子目录命名,默认值为 Webpack 的 ${config.name}-${config.mode}。
- cache.cacheLocation:缓存真正的存放地址。默认使用的是上述两个属性的组合:path.resolve(cache.cacheDirectory, cache.name)。该属性在赋值情况下将忽略上面的 cacheDirectory 和 name 属性。
单个模块的缓存失效
Webpack 5 会跟踪每个模块的依赖项:fileDependencies、contextDependencies、missingDependencies。当模块本身或其依赖项发生变更时,Webpack 能找到所有受影响的模块,并重新进行构建处理。
这里需要注意的是,对于 node_modules 中的第三方依赖包中的模块,出于性能考虑,Webpack 不会跟踪具体模块文件的内容和修改时间,而是依据依赖包里package.json 的 name 和 version 字段来判断模块是否发生变更。因此,单纯修改 node_modules 中的模块内容,在构建时不会触发缓存的失效。
全局的缓存失效
当模块代码没有发生变化,但是构建处理过程本身发生变化时(例如升级了 Webpack 版本、修改了配置文件、改变了环境变量等),也可能对构建后的产物代码产生影响。因此在这种情况下不能复用之前缓存的数据,而需要让全局缓存失效,重新构建并生成新的缓存。在 Webpack 5 中共提供了 3 种不同维度的全局缓存失效配置。
1、buildDependencies
第一种配置是cache.buildDependencies,用于指定可能对构建过程产生影响的依赖项。
它的默认选项是{defaultWebpack: ["webpack/lib"]}。这一选项的含义是,当 node_modules 中的 Webpack 或 Webpack 的依赖项(例如 watchpack 等)发生变化时,当前的构建缓存即失效。
上述选项是默认内置的,无须写在项目配置文件中。配置文件中的 buildDenpendencies 还支持增加另一种选项 {config: [__filename]},
它的作用:(1)获取最新配置及其依赖项(2)当配置文件内容或配置文件依赖的模块文件发生变化时,当前的构建缓存即失效。
2、version
第二种配置是 cache.version。当配置文件和代码都没有发生变化,但是构建的外部依赖(如环境变量)发生变化时,预期的构建产物代码也可能不同。这时就可以使用 version 配置来防止在外部依赖不同的情况下混用了相同的缓存。例如,可以传入 cache: {version: process.env.NODE_ENV},达到当不同环境切换时彼此不共用缓存的效果。
3、name
缓存的名称除了作为默认的缓存目录下的子目录名称外,也起到区分缓存数据的作用。例如,可以传入 cache: {name: process.env.NODE_ENV}。这里有两点需要补充说明:
- name 的特殊性:与 version 或 buildDependencies 等配置不同,name 在默认情况下是作为缓存的子目录名称存在的,因此可以利用 name保留多套缓存。在 name 切换时,若已存在同名称的缓存,则可以复用之前的缓存。与之相比,当其他全局配置发生变化时,会直接将之前的缓存失效,即使切换回之前已缓存过的设置,也会当作无缓存处理。
- 当 cacheLocation 配置存在时,将忽略 name 的缓存目录功能,上述多套缓存复用的功能也将失效。
补充:
此外,在 Webpack 4 中,部分插件是默认启用缓存功能的(例如压缩代码的 Terser 插件等),项目在生产环境下构建时,可能无意识地享受缓存带来的效率提升,但是在 Webpack 5 中则不行。无论是否设置 cache 配置,Webpack 5 都将忽略各插件的缓存设置(例如 TerserWebpackPlugin),而由引擎自身提供构建各环节的缓存读写逻辑。
因此,项目在迁移到 Webpack 5 时都需要通过上面介绍的 cache 属性来单独配置缓存。
持久化缓存开启与否对比:
(1)未开启:构建时间38s左右
cache: {
type: 'memory'
},
(2)开启持久化缓存:构建时间14s左右,提升了63%
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
},
name: 'dev_cache'
},
Webpack5 内置缓存方案探索
webpack5会 缓存生成的webpack模块和chunk,来改善构建速度。
webpack5内置的缓存方案 = cache-loader + dll
缩短 Webpack 构建时间以及减小成本的解决方案,他们包括但不限于:
- cache-loader
- DllReferencePlugin
- auto-dll-plugin
- thread-loader
- happypack
- hard-source-webpack-plugin
这里简要进行说明:
1、cache-loader 可以在一些性能开销较大的 loader 之前添加,目的是将结果缓存到磁盘里;
- 在一些性能开销较大的 loader 之前添加此 loader,以将结果缓存到磁盘里。
- 请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。
官方提供的 cache-loader,可将上一个 loader 处理的结果缓存到磁盘上,下一次在走这个流程的时候(pitch)依据一定的规则来使用缓存内容从而跳过后面 loader 的处理。不过
cache-loader
也仅仅能覆盖到经由loader
处理后的文件内容,缓存内容的范围比较受限,此外就是cache-loader
缓存是在构建流程当中进行的,缓存数据的过程也是有一些性能开销的,会影响整个的编译构建速度,所以建议是搭配译耗时较长的 loader 一起使用。Webpack4时之所以要有dll,是因为cache-loader并不能覆盖所有模块,只能对个别被loader处理的模块进行缓存。而那些通用的库是没法被cache-loader处理的,所以只能通过dll的方式来预编译。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
'babel-loader'
],
include: path.resolve('src')
}
]
}
}
2、DLLPlugin 和 DLLReferencePlugin 实现了拆分 bundles,同时节约了反复构建 bundles 的成本,大大提升了构建的速度;
3、thread-loader 和 happypack 实现了单独的 worker 池,用于多进程/多线程运行 loaders;
4、vue-cli 和create-react-app 并没有使用到 dll 技术,而是使用了更好的代替者:hard-source-webpack-plugin。