原文连接前端
继 webpack v5-beta0 发布后,官方又发布了持久化缓存指南。node
首先,要注意的是默认状况下不会启用持久化缓存。你能够自行选择启用。webpack
为什么如此? webpack 旨在注重构建安全而非性能。 咱们没有打算默认启用这一功能,主要缘由在于此功能虽然有 95% 概率提高性能,但仍有 5% 的概率中断你的应用程序/工做流/构建。web
这可能听起来很糟,但相信我它并不是如此。 只不过须要开发人员进行额外的操做来配置它。typescript
序列化与反序列化功能具备无需配置的开箱即用体验,但开箱即用的部分可能导致缓存失效。数据库
什么是缓存失效? webpack 须要确认 entry 的缓存什么时候会失效,并在失效时再也不将其用于构建。 所以,当你应用程序修改文件时,就会发生此状况。npm
示例:修改 magic.js
。 webpack 必须让 entry 为 magic.js
的缓存失效。 构建将从新处理该文件,即运行 babel,typescript 诸如此类工具,从新解析文件并运行代码生成。 webpack 可能还会导致 entry 为 bundle.js
的缓存失效。 而后根据原模块从新构建此文件。json
为此,webpack 追踪了每一个模块的 fileDependencies
contextDependencies
以及 missingDependencies
,并建立了文件系统快照。 此快照会与真实文件系统进行比较,当检测到差别时,将触发对应模块的从新构建。缓存
webpack 给 bundle.js
的缓存 entry 设置了一个 etag
,它为全部贡献者的 hash 值。 比较这个 etag
,只有当它与缓存 entry 匹配时才能使用。安全
webpack 4 中的内存缓存也依赖上述这些。 从开发人员角度来讲,这些都可以开箱即用,无需额外配置。 但对于 webpack 5 的持久化缓存来讲,却充满着挑战。
如下操做均会让 webpack 使 entry 缓存失效:
这变得很是棘手。 开箱即用的状况下,webpack 没法处理全部这些状况。 这就是咱们为何选择安全的方式,并将持久化缓存变为可选特性的缘由。 咱们但愿读者能够学习如何启用持久化缓存,觉得你提供正确的提示。 咱们但愿你知道须要使用哪一种配置来处理你自定义的构建脚本。
为了处理构建过程当中的依赖关系,webpack 提供了三个新工具:
此为全新的配置项 cache.buildDependencies
,它能够指定构建过程当中的代码依赖。 为了使它更简易,webpack 负责解析并遵循配置值的依赖。
值类型有两种:文件和目录。 目录类型必须以斜杠(/
)结尾。其余全部内容都解析为文件类型。
对于目录类型来讲,会解析其最近的 package.json
中的 dependencies。 对于文件类型来讲,咱们将查看 node.js 模块缓存以寻找其依赖。
示例:构建一般取决于 webpack 自己的 lib 文件夹: 你能够这样配置:
cache.buildDependencies: {
defaultWebpack: ["webpack/lib/"]
}
复制代码
当 webpack/lib
或 webpack 依赖的库(如,watchpack
,enhanced-resolved
等)发生任何变化时,其缓存将失效。 webpack/lib
已经是默认值,默认状况下无需配置。
另外一个示例:构建依旧取决于你的配置文件。 具体配置以下:
cache.buildDependencies: {
config: [__filename]
}
复制代码
__filename
变量指向 node.js 中的当前文件。
当配置文件或配置文件中经过 require
依赖的任何内容发生更改时,也会使得持久化缓存失效。 当配置文件经过 require()
引用了全部使用过的插件时,它们也会成为构建依赖项。
若是配置文件经过 fs.readFile
读取文件,则将不会成为构建依赖项,由于 webpack 仅遵循 require()
。 你须要手动将此类文件添加到 buildDependencies
中。
构建的某些依赖项不能单纯的依靠对文件的引用,如,从数据库读取的值,环境变量或命令行上传递的值。 对于这些值,咱们给出了新的配置项 cache.version
。
cache.version
类型为 string。传递不一样的字符串将使持久化缓存失效。
示例:你的配置中可能会读取环境变量中的 GIT_REV
并将其与 DefinePlugin
一块儿使用以将其嵌入到 bundle 中。 这使得 GIT_REV
成为你构建的依赖项。 具体配置以下:
cache: {
version: `${process.env.GIT_REV}`
}
复制代码
在某些状况下,依赖关系会在多个不一样的值间切换,而且对于每一个值更改都会使得持久化缓存失效,这显然是浪费资源的。 对于这类值,咱们给出了新的配置项 cache.name
。
cache.name
类型为 string。传递值将建立一个隔离且独立的持久化缓存。
cache.name
被用于对文件名进行持久化缓存。确保仅传递短小且 fs-safe 的名称。
示例:你的配置可使用 --env.target mobile|desktop
参数为移动端或 PC 用户建立不一样的构建。 具体配置以下:
cache: {
name: `${env.target}`
}
复制代码
对大部分 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 自动添加它时,你能够忽略它
}
}
复制代码
持久化缓存可用于单独构建和连续构建(watch)。
当设置 cache.type: "filesystem"
时,webpack 会在内部以分层方式启用文件系统缓存和内存缓存。 从缓存读取时,会先查看内存缓存,若是内存缓存未找到,则降级到文件系统缓存。 写入缓存将同时写入内存缓存和文件系统缓存。
文件系统缓存不会直接将对磁盘写入的请求进行序列化。它将等到编译过程完成且编译器处于空闲状态才会执行。 如此处理的缘由是序列化和磁盘写入会占用资源,而且咱们不想额外延迟编译过程。
针对单一构建,其工做流为:
针对连续构建(watch),其工做流为:
cache.idleTimeoutForInitialStore
cache.idleTimeout
你会发现两个新的配置项 cache.idleTimeout
和 cache.idleTimeoutForInitialStore
,它们控制着持久化缓存以前编译器必须空闲的时长。 cache.idleTimeout
默认为 60s,cache.idleTimeoutForInitialStore
默认为 0s。 因为序列化阻止了事件循环,所以在序列化缓存时不进行缓存检测。 此延迟尝试避免因为快速编辑文件,而在 watch 模式下致使从新编译形成的延迟,同时尝试为下一次冷启动保持持久化缓存的最新状态。 这是一个折中的解决方案,能够设置适合你工做流的值。较小的值会缩短冷启动时间,但会增长延迟从新构建的风险。
发生错误要恢复持久化缓存的方式,能够经过删除整个缓存并进行全新的构建,或者经过删除有问题的缓存 entry 并使得该项目保持未缓存状态来进行。
在这种状况下,webpack 的 logger 会发出警告。 欲了解更多,请参阅 infrastructureLogging
的配置项。
正常使用不须要如下信息。
封装 webpack 的工具能够选择其余默认值。 当不容许使用自定义扩展的 webpack 时,因为能够彻底控制全部构建的依赖项,所以能够默认打开持久化存储。
默认状况下,使用 webpack 的 CLI 可能会添加一些构建依赖关系,而 webpack 自己不会。
cache.buildDependencies.defaultConfig
设置为所用的配置文件cache.version
cache.name
中添加注释。使用以下配置,将输出额外的调试信息:
infrastructureLogging: {
debug: /webpack\.cache/
}
复制代码
version
与 cache.version
不匹配 -> 没有构建缓存resolve snapshot
)与文件系统进行对比
resolve results
)
build dependencies snapshot
)与文件系统进行对比
cache.buildDependencies
全部支持序列化的 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。
如对译文有疑问,欢迎评论。
相关文章会在公众号首发,扫描下方二维码关注咱们,咱们将提供前端相关最新最优含量的资讯。