「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」
对大部分 node_modules 进行哈希处理并加盖时间戳以生存构建和常规依赖项,其代价非常昂贵,并且还会大大降低 webpack 的执行速度。 为避免这种情况出现,webpack 引入了相关的性能优化,默认情况下会跳过 node_modules
,并使用 package.json
中的 version
和 name
作为数据源。
此优化将用于配置项 cache.managedPaths
中的所有 path。 它默认为 webpack 安装了 node_modules
目录。
启用此优化后,请勿手动编辑 node_modules
。 你可以使用 cache.managedPaths: []
禁用它。
当使用 Yarn PnP 时,将启用另一个优化。 由于缓存内容不可变,yarn 缓存中的所有文件都将完全跳过哈希和时间戳的操作(甚至不会追踪 version
和 name
)。
此操作由配置项 cache.immutablePaths
控制。 启用 Yarn PnP 时,默认为安装了 webpack 的 yarn 缓存。
不要手动编辑 yarn 缓存,因为这根本不可行。
使用持久化缓存
此为启用持久化缓存的典型配置:
cache: {
type: "filesystem",
buildDependencies: {
config: [ __filename ] // 当你 CLI 自动添加它时,你可以忽略它
}
}
复制代码
Watching
持久化缓存可用于单独构建和连续构建(watch)。
当设置 cache.type: "filesystem"
时,webpack 会在内部以分层方式启用文件系统缓存和内存缓存。 从缓存读取时,会先查看内存缓存,如果内存缓存未找到,则降级到文件系统缓存。 写入缓存将同时写入内存缓存和文件系统缓存。
文件系统缓存不会直接将对磁盘写入的请求进行序列化。它将等到编译过程完成且编译器处于空闲状态才会执行。 如此处理的原因是序列化和磁盘写入会占用资源,并且我们不想额外延迟编译过程。
针对单一构建,其工作流为:
- Loading cache
- Building
- Emitting
- Display results (stats)
- Persisting cache (if changed)
- Process exits
针对连续构建(watch),其工作流为:
- Loading cache
- Building
- Emitting
- Display results (stats)
- Attach filesystem watchers
- Wait
cache.idleTimeoutForInitialStore
- Persisting cache (if changed)
- On change:
- Building
- Emitting
- Display results (stats)
- Wait
cache.idleTimeout
- Persisting cache (if changed)
你会发现两个新的配置项 cache.idleTimeout
和 cache.idleTimeoutForInitialStore
,它们控制着持久化缓存之前编译器必须空闲的时长。 cache.idleTimeout
默认为 60s,cache.idleTimeoutForInitialStore
默认为 0s。 由于序列化阻止了事件循环,因此在序列化缓存时不进行缓存检测。 此延迟尝试避免由于快速编辑文件,而在 watch 模式下导致重新编译造成的延迟,同时尝试为下一次冷启动保持持久化缓存的最新状态。 这是一个折中的解决方案,可以设置适合你工作流的值。较小的值会缩短冷启动时间,但会增加延迟重新构建的风险。
错误处理
发生错误要恢复持久化缓存的方式,可以通过删除整个缓存并进行全新的构建,或者通过删除有问题的缓存 entry 并使得该项目保持未缓存状态来进行。
在这种情况下,webpack 的 logger 会发出警告。 欲了解更多,请参阅 infrastructureLogging
的配置项。
CLI 指南
默认情况下,使用 webpack 的 CLI 可能会添加一些构建依赖关系,而 webpack 本身不会。
- 默认情况下,CLI 会将
cache.buildDependencies.defaultConfig
设置为所用的配置文件 - CLI 会将命令行参数附加到
cache.version
- 使用命令行参数时,CLI 可能会在
cache.name
中添加注释。
调试信息
使用如下配置,将输出额外的调试信息:
infrastructureLogging: {
debug: /webpack\.cache/
}
复制代码
内部工作流
- webpack 读取缓存文件。
- 没有缓存文件 -> 没有构建缓存
- 缓存文件中的
version
与cache.version
不匹配 -> 没有构建缓存
- webpack 将解析快照(
resolve snapshot
)与文件系统进行对比- 匹配到 -> 继续后续流程
- 没有匹配到:
- 再次解析所有解析结果(
resolve results
)- 没有匹配到 -> 没有构建缓存
- 匹配到 -> 继续后续流程
- 再次解析所有解析结果(
- webpack 将构建依赖快照(
build dependencies snapshot
)与文件系统进行对比- 没有匹配到 -> 没有构建缓存
- 匹配到 -> 继续后续流程
- 对缓存 entry 进行反序列化(在构建过程中对较大的缓存 entry 进行延迟反序列化)
- 构建运行(有缓存或没有缓存)
- 追踪构建依赖关系
- 追踪
cache.buildDependencies
- 追踪已使用的 loader
- 追踪
- 追踪构建依赖关系
- 新的构建依赖关系已解析完成
- 解析依赖关系已追踪
- 解析结果已追踪
- 创建来自所有新解析依赖项的快照
- 创建来自所有新构建依赖项的快照
- 持久化缓存文件序列化到磁盘
序列化
所有支持序列化的 class 都需要注册一个序列化器:
webpack.util.serialization.register(Constructor, request, name, serializer);
复制代码
Constructor
应为一个 class 或构造器函数。 对于任何需要序列化的对象的 object.constructor
将被用于查找序列化器(serializer)。
request
将被用于加载调用 register
模块。 它应指向当前模块。 它将以这种方式使用:require(request)
。
name
被用于区分具有相同 request
的多个 register
调用。
serializer
是至少拥有 serialize
和 deserialize
两个方法的对象。
当需序列化对象时,请调用 serializer.serialize(object, context)
。 context
是至少拥有一个 write(anything)
方法的对象 此方法将内容写入输出流。 传递的值也会被序列化。
当需要反序列化对象时,请调用 serializer.deserialize(context)
。 context
是至少拥有一个 read(): anything
方法的对象。 此方法会反序列化输入流中的某些内容。 deserialize
必须返回反序列化后的对象。
serialize
和 deserialize
应以相同的顺序读取和写入相同的对象。
// some-module/lib/MyClass.js
class MyClass {
constructor(a, b) {
this.a = a;
this.b = b;
this.c = undefined;
}
}
register(MyClass, "some-module/lib/MyClass", null, {
seralize(obj, { write }) {
write(obj.a);
write(obj.b);
write(obj.c);
}
deserialize({ read }) {
const obj = new MyClass(read(), read());
obj.c = read();
return obj;
}
});
复制代码
基本数据类型和引用数据类型的序列化器都已被注册,即 string,number,Array,Set,Map,RegExp,plain objects,Error。