Skip to content

构建性能--持久化缓存

原理

整个过程中存在许多 CPU 密集型操作,例如调用 Loader 链加载文件时,遇到 babel-loader、eslint-loader、ts-loader 等工具时可能需要重复生成 AST;分析模块依赖时则需要遍历 AST,执行大量运算;Seal 阶段也同样存在大量 AST 遍历,以及代码转换、优化操作,等等。假设业务项目中有 1000 个文件,则每次执行 npx webpack 命令时,都需要从 0 开始执行 1000 次构建、生成逻辑。

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

webpack5

设置 cache.type = 'filesystem' 即可开启:

js
module.exports = {
    //...
    cache: {
        type: 'filesystem'
    },
    //...
}

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

  • cache.type:缓存类型,支持 'memory' | 'filesystem',需要设置为 filesystem 才能开启持久缓存;
  • cache.cacheDirectory:缓存文件路径,默认为 node_modules/.cache/webpack
  • cache.buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件,如:
js
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [
        path.join(__dirname, 'webpack.dll_config.js'),
        path.join(__dirname, '.babelrc')
      ],
    },
  },
};
  • cache.managedPaths:受控目录,Webpack 构建时会跳过新旧代码哈希值与时间戳的对比,直接使用缓存副本,默认值为 ['./node_modules']
  • cache.profile:是否输出缓存处理过程的详细日志,默认为 false
  • cache.maxAge:缓存失效时间,默认值为 5184000000

性能可以提升几十倍

webpack4

cache-loader

cache-loader 能够将 Loader 处理结果保存到硬盘,下次运行时若文件内容没有发生变化则直接返回缓存结果

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

cache-loader 只缓存了 Loader 执行结果,缓存范围与精度不如 Webpack5 内置的缓存功能,所以性能效果相对较低。能提升稳定在 60% ~ 80% 之间

hard-source-webpack-plugin

它并不仅仅缓存了 Loader 运行结果,还保存了 Webpack 构建过程中许多中间数据,包括:模块、模块关系、模块 Resolve 结果、Chunks、Assets 等,效果几乎与 Webpack5 自带的 Cache 对齐。

  1. 安装依赖
shell
npm i -D hard-source-webpack-plugin
  1. 配置
js
const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");

module.exports = {
  // ...
  plugins: [
    new HardSourceWebpackPlugin(),
  ],
}

底层逻辑与 Webpack5 的持久化缓存很相似,但优化效果稍微差一些。性能提升稳定在 62% ~ 88% 之间

组件自带的缓存

例如使用 babel-loader 时,只需设置 cacheDirectory = true 即可开启缓存功能

js
module.exports = {
    module: {
        rules: [{
            test: /\.m?js$/,
            loader: 'babel-loader',
            options: {
                cacheDirectory: true,
            },
        }]
    },
}

DLL

dll 在 webpack4 以下可以使用,从而提升性能,但 webpack4 及以上打包性能足够好,没有必要再使用了。

当成知识点了解即可

作用:事先把常用但又构建时间长的代码提前打包好(例如 react、react-dom),后面再打包的时候就跳过原来的未打包代码,直接使用

  1. 需要新建个构建配置文件,比如是 webpack.dll.config.js
js
module.exports = {
  context: process.cwd(),
  entry: {
    library: [
      'react',
      'react-dom',
      'redux',
      'react-redux'
    ],
    // 如果有多个,直接在增加一个
  },
  output: {
    // 这里打包后的文字是 library.dll.js
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'build/libarary'),
    // 暴露的库的名字
    library: '[name]'
  },
  plugins: [
    // 指定包存放的位置
    new webpack.DllPlugin({
      name: '[name]',
      // 描述动态链接库 mainfest 文件输出时的文件名称
      // path: 'manifest.json'
      path: path.resolve(__dirname, 'build/libarary/[name].json')
    })
  ]
}
  1. pageage.json scripts 中增加命令
json
"scripts": {
  "dll": "webpack --config webpack.dll.js"
}
  1. 执行 npm run dll 分包
  2. webpack.config.js 引入
js
module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      // 刚打包后的json文件地址
      // manifest: require('xxx.json'),
      manifest: require('./build/library/libary.json'),
      // 指定需要用到的 manifest 文件,
      // webpack 会根据这个 manifest 文件的信息,分析出哪些模块无需打包,直接从另外的文件暴露出来的内容中获取
    })
    // 如果引入多个,使用多次此插件
  ]
}
  1. 每次公共部分代码的变化,都需要重新执行分包指令

建议使用 autodll-webpack-dll 简化配置过程