Skip to content

webpack 缓存

一、为什么要缓存

在 webpack 基础篇我们知道 webpack 工作流程大概有四部分: 输入 -> 模块处理(loader) -> plugin -> 输出

其中 loader 阶段详细分为以下步骤:

  1. 调用 Loader 转译文件内容
  2. 调用 acorn 生成 AST 结构
  3. 分析 AST,确定模块依赖列表
  4. 遍历模块依赖列表,对每一个依赖模块重新执行上述流程,直到生成完整的模块依赖图

plugin 阶段详细分为以下步骤:

  1. 遍历模块依赖图,对每一个模块执行
    1.1 代码转译,如 import 转换为 require 调用
    1.2 分析运行时依赖
  2. 合并模块代码与运行时代码,生成 chunk
  3. 执行产物优化操作,如 Tree-shaking
  4. 将最终结果写出到产物文件

这些过程中需要进行大量的计算和转换,例如遇到 babel-loader、eslint-loader、ts-loader 等工具时可能需要重复生成 AST;分析模块依赖时则需要遍历 AST,执行大量运算;plugin 阶段也同样存在大量 AST 遍历,以及代码转换、优化操作,等等。

而 Webpack5 的持久化缓存功能则将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。

二、webapck5 缓存

在 webpack5 中提供了内置缓存方案。通过使用 cache 这个对象来实现缓存生成的 webpack 模块和 chunk,来改善构建速度。

js
module.exports = {
    ...,
    cache: {
        type: 'filesystem',   //默认为memory,也就是存储到内存
    },
}

cache 还提供了若干用于配置缓存效果、缓存周期的配置项,包括:

  • cache.type:缓存类型,支持 'memory' | 'filesystem',需要设置为 filesystem 才能开启持久缓存。而 menory 则是缓存在内存中(当cache设置为true时,同效)
  • cache.cacheDirectory:缓存文件路径,默认为 node_modules/.cache/webpack
  • cache.buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件
js
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 不需要读取文件来计算它们的哈希值。仅当时间戳相同时才会比较内容哈希,这对初始构建的性能影响很小。
js
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 能够将 Loader 处理结果保存到硬盘,下次运行时若文件内容没有发生变化则直接返回缓存结果,用法:

  1. 安装依赖
pnpm install -D cache
  1. 修改配置,注意必须将 cache-loader 放在 loader 数组首位,例如:
js
module.exports = {
    // ...
    module: {
        rules: [{
            test: /\.js$/,
            use: ['cache-loader', 'babel-loader', 'eslint-loader']
        }]
    },
    // ...
};

三、长久缓存

长久缓存指的是游览器对我们前端文件的缓存。当我们请求的文件名没有变得时候,游览器就会直接使用缓存的文件,否则重新请求加载。

js
optimization.moduleIds = 'deterministic'
optimization.chunkIds = 'deterministic'

此配置在生产模式下是默认开启的,它的作用是以确定的方式为 module 和 chunk 分配 3-5 位数字 id,相比于 v4 版本的选项 hashed,它会导致更小的文件 bundles。 由于 moduleId 和 chunkId 确定了,构建的文件的 hash 值也会确定,有利于浏览器长效缓存。同时此配置有利于减少文件打包大小。

在开发模式下,建议使用:

js
optimization.moduleIds = 'named'
optimization.chunkIds = 'named'