2、Webpack缓存机制:Webpack持久化缓存原理、cache配置项详解、缓存版本控制

聊到Webpack的缓存,我得先坦白一件事。

早些年我做项目,每次改一行代码都要等十几秒重新构建。那时候团队十几个人,每天光等构建的时间加起来,够看两集电视剧了。后来我深入研究了一下Webpack的缓存机制,才明白——原来大部分重复工作,根本没必要做第二遍

持久化缓存:到底在缓存什么?

说白了,Webpack的持久化缓存就是把编译结果存到磁盘上。下次启动时,如果文件没变,就直接读缓存,跳过编译。

你想想看,一个项目少说几百个模块。每次构建都重新解析、转换、打包,太浪费了。缓存的核心思路就一句话:变了的重新编译,没变的直接复用

Webpack 5之前,缓存主要靠内存,或者靠第三方loader的缓存。Webpack 5直接内置了持久化缓存,这才算真正解决了问题。

核心原理:Webpack会为每个模块生成一个「内容哈希」。构建时对比哈希值,如果一致,说明模块没变,直接从缓存读取编译结果。

这里有个细节——缓存的不只是编译后的代码。还包括模块的依赖关系图、解析结果、loader处理后的中间产物。说白了,整个构建过程中的中间状态,能缓存的都缓存了。

cache配置项详解

配置其实不复杂。我直接给你看最常用的写法:

// webpack.config.js
module.exports = {
  cache: {
    type: 'filesystem',  // 启用文件系统缓存
    cacheDirectory: path.resolve(__dirname, '.temp_cache'), // 缓存存放目录
    buildDependencies: {
      config: [__filename] // 配置文件变化时,缓存失效
    },
    version: '1.0', // 缓存版本号
    name: 'production-cache', // 缓存名称,用于区分不同环境
    store: 'pack', // 缓存存储方式
    compression: 'gzip', // 缓存压缩方式
    maxAge: 5184000000 // 缓存有效期,默认60天
  }
};

我来拆开讲讲每个配置项的实际用途。

type: 'filesystem'

这是Webpack 5的核心配置。以前只有memory模式,构建完就没了。filesystem模式会把缓存写到磁盘,下次启动直接读。我个人习惯在开发环境用memory,生产环境用filesystem。不过现在磁盘读写够快,我基本全用filesystem了。

cacheDirectory

缓存放哪。默认是node_modules/.cache/webpack。我建议单独指定一个目录,比如.temp_cache。这样清理缓存时方便,直接删文件夹就行。

小技巧:把缓存目录加到.gitignore里。别问我为什么,问就是曾经有人把缓存提交到仓库,然后整个团队都炸了。

buildDependencies

这个配置很关键。它告诉Webpack:哪些文件的变化会导致整个缓存失效。最常见的就是webpack.config.js本身。你改了配置,之前的缓存肯定不能用了。

buildDependencies: {
  config: [__filename],
  // 还可以加其他依赖
  // tsconfig: [path.resolve(__dirname, 'tsconfig.json')]
}

version

缓存版本号。手动控制缓存失效的利器。比如你升级了某个loader,或者改了babel配置,但Webpack不知道。这时候手动改一下version,所有缓存立即失效。

注意:version不是随便写的。我建议用语义化版本号,比如'1.0'、'1.1'。每次重大配置变更就升一位。我曾经见过有人用时间戳当version,结果每次构建都重新缓存,等于没开缓存。

name

缓存名称。主要用于区分不同构建环境。比如开发环境和生产环境的缓存不能混用。

name: process.env.NODE_ENV === 'production' ? 'prod-cache' : 'dev-cache'

store与compression

store默认是'pack',把缓存打包成单个文件。compression默认是'gzip',压缩缓存文件。这两个一般不用改。除非你有特殊需求,比如想用更快的压缩算法。

maxAge

缓存有效期。默认60天。过了这个时间,缓存自动失效。我一般不改这个,60天够长了。项目迭代快的话,一个月缓存就自动更新了。

缓存版本控制:别让缓存变成坑

缓存是好东西,但用不好就是坑。我来说说缓存版本控制的几个关键点。

什么时候缓存会失效?

  • 文件内容变化:模块内容变了,该模块的缓存失效
  • 配置文件变化:webpack.config.js变了,整个缓存失效
  • 依赖变化:node_modules里的包变了,相关缓存失效
  • 手动版本号变化:version变了,全部缓存失效
  • 超过有效期:maxAge到了,缓存自动失效

我踩过的坑

曾经有一次,我升级了babel-loader,但忘了改version。结果构建出来的代码还是旧的。排查了半天才发现是缓存没失效。从那以后,我养成了一个习惯:每次升级loader或plugin,顺手改一下version

最佳实践:在package.json里加一个脚本,专门用来清理缓存。

// package.json
{
  "scripts": {
    "clean:cache": "rm -rf .temp_cache node_modules/.cache"
  }
}

多环境缓存管理

开发环境和生产环境的缓存一定要分开。不然你在开发环境调试的代码,被生产环境缓存了,上线后还是旧代码。

// 根据环境区分缓存
cache: {
  type: 'filesystem',
  name: process.env.NODE_ENV === 'production' 
    ? 'prod-cache' 
    : 'dev-cache',
  version: process.env.NODE_ENV === 'production' 
    ? '1.0.0' 
    : '1.0.0-dev'
}

实战配置示例

最后给你一个我项目中实际在用的配置。嗯,这个配置经过多次迭代,目前比较稳定。

// webpack.cache.config.js
const path = require('path');

module.exports = {
  cache: {
    type: 'filesystem',
    cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
    buildDependencies: {
      config: [
        __filename,
        path.resolve(__dirname, 'babel.config.js'),
        path.resolve(__dirname, 'tsconfig.json')
      ]
    },
    version: '2.1.0',
    name: `app-${process.env.NODE_ENV}`,
    store: 'pack',
    compression: 'gzip',
    maxAge: 1000 * 60 * 60 * 24 * 30, // 30天
    hashAlgorithm: 'md4' // 哈希算法,默认md4
  }
};

最后说一句:缓存不是银弹。如果你的项目构建本来就很快,开缓存反而多了一步读写磁盘的操作。我建议项目超过100个模块,或者构建时间超过5秒,再考虑开启持久化缓存。

好了,Webpack的缓存机制就聊到这。下一章我们聊聊热更新——那个让你改代码后页面自动刷新的神奇机制。