广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Webpack 5 的 Efficiency Metrics 效率指标到底能带来什么?

Webpack 5 发布后,除了持久化缓存、模块联邦等重磅功能,统计输出(Stats)层面的增强也值得关注。官方在默认配置和可配置项中都加入了不少与构建效率直接相关的字段,也就是我们常说的 Efficiency Metrics。这些指标并非某个单独的插件或面板,而是一组内嵌在 stats 对象里的数据集合,目的是让构建性能不再是黑盒。你可以拿到每一次编译的精确耗时、缓存命中情况、模块分组统计以及资源大小变化,进而判断哪一步优化真正起了作用。

Webpack 5 的 Efficiency Metrics 效率指标到底能带来什么?

过去我们排查构建慢的问题,往往依赖主观感受或者粗略的 time 命令。比如“改了某个文件重新构建花了 20 秒”,但具体是模块解析慢、代码生成慢还是插件拖累,很难说清楚。Efficiency Metrics 把这些问题拆解成一个个可测量的小项,结合 CI 平台记录的历史数据,就能画出构建耗时的趋势图,及时发现性能退化。尤其在大型单页应用或组件库中,一次构建动辄上千个模块,没有精细指标几乎无法优化。

Efficiency Metrics 解决了什么问题?

传统的构建日志通常只输出 entrypoint 大小、chunk 数量、错误警告数量,信息粒度较粗。当构建时间从 10 秒涨到 30 秒时,开发者只能凭直觉去猜测是依赖升级还是新引入的 loader 导致。Efficiency Metrics 则提供了模块级别的缓存状态分类、精确到毫秒的每个阶段耗时、以及 chunk 之间的依赖关系变化。这样一来,性能分析从“拍脑袋”变成了有数据支撑的系统性排查。

举个典型场景:一个使用了数百个 npm 包的项目,开启持久化缓存后构建依然没有明显提速。通过查看 cachedModules 指标,可以统计出有多少模块真正命中了缓存,有多少因为配置变更、版本不一致或其他原因重新构建。如果缓存命中率低于预期,就可以进一步检查 cache.buildDependencies 或 snapshot 配置。这些数据在 Webpack 5 之前很难直接获取,而现在是 stats 输出的一部分。

另外,Efficiency Metrics 还能帮助团队建立构建性能基线。在 CI 中配置一个阈值,比如单次冷启动构建不能超过 120 秒,热更新构建不能超过 5 秒,通过提取 stats 里的 time 字段和模块缓存命中率,一旦超过就发出告警。这种量化手段比单纯依赖开发者口头反馈要可靠得多。

如何开启与解读 Efficiency Metrics

Webpack 5 的 stats 配置项非常灵活,可以通过 preset 快速切换,也可以手动指定每个字段。默认的 preset: 'normal' 已经包含了一些基础时间信息,但效率相关的深度指标需要额外开启。下面这段配置展示了如何启用缓存状态分组和构建时间输出:

module.exports = {
  // 其他配置省略
  stats: {
    preset: 'normal',
    builtAt: true,                        // 输出构建开始和结束时间
    cachedModules: true,                  // 显示缓存的模块数量
    cachedAssets: true,                   // 显示缓存的资源数量
    groupModulesByCacheStatus: true,      // 按缓存状态分组模块
    moduleTrace: true,                    // 显示模块依赖追踪信息
    errorsCount: true,
    warningsCount: true,
    timings: true,                        // 显示每个阶段的耗时
  },
};

这段配置里最值得关注的是 groupModulesByCacheStatus。它会将模块按照“已缓存”“未缓存”“已失效”等状态分组,输出类似下面的统计结果,让你一眼看出有多少模块因为缓存失效而重新构建。如果未缓存模块占比很高,说明持久化缓存配置可能有问题,或者项目结构导致大量模块的版本 hash 频繁变化。

除了配置文件,你还可以在 Node.js 脚本中通过 stats.toJson() 按需提取指标。比如在 CI 构建完成后,把 time、cachedModules、assets 等字段写入一个 JSON 文件,再使用可视化工具展示。这样即使不启动 dev server,也能在构建日志之外保留完整的效率数据。需要注意的是,toJson() 带参数时最好显式设置 all: false,否则会包含大量无用字段,增加内存开销。

解读这些指标时,要结合具体项目特征。例如 time 字段返回的是毫秒数,冷启动构建通常比热更新高很多,不能直接对比。而 cachedAssets 和 cachedModules 只反映本次构建与上次的差异,如果两次构建之间修改了大量入口文件,缓存命中率自然会下降。所以应该在一段时间内持续记录,观察趋势而不是单次数值。

自定义 Efficiency Metrics 与实战优化

内置指标虽然全面,但有时我们只关心某几个关键数字,或者希望把它们接入现有的监控系统。此时可以利用 compilation.getStats().toJson() 在自定义插件中提取并上报。下面这段代码展示了如何在 Webpack 插件里拿到模块缓存分组和构建耗时,并输出为简洁的 JSON:

class EfficiencyReporter {
  apply(compiler) {
    compiler.hooks.done.tap('EfficiencyReporter', (stats) => {
      const data = stats.toJson({
        all: false,
        builtAt: true,
        time: true,
        cachedModules: true,
        cachedAssets: true,
        groupModulesByCacheStatus: true,
        errors: true,
        warnings: true,
      });
      console.log(JSON.stringify({
        buildTimeMs: data.time,
        cachedModuleCount: data.cachedModules,
        uncachedModuleCount: data.uncachedModules,
        cachedAssetCount: data.cachedAssets,
        errors: data.errorsCount,
        warnings: data.warningsCount,
      }, null, 2));
    });
  }
}
module.exports = {
  plugins: [new EfficiencyReporter()],
  stats: 'errors-only', // 避免默认输出干扰
};

这个插件在每次构建完成后输出精简的效率数据,非常适合投递到 CI 日志或者通过 HTTP 上报到监控平台。实际项目中,我们可以根据这些数据自动计算缓存命中率,比如 cachedModules / (cachedModules + uncachedModules)。如果命中率低于 80%,就触发一条 Slack 通知,提醒团队检查缓存配置或依赖更新策略。

一个真实的优化案例来自一个中后台管理系统。该项目有 1400 多个模块,初始冷启动构建耗时 86 秒,热更新也要 12 秒。开启 Efficiency Metrics 后发现未缓存模块高达 60%,进一步排查发现是 cache.buildDependencies 中没有包含 package-lock.json,导致每次依赖树变化都会使全部模块缓存失效。修复后,冷启动降到 38 秒,热更新降到 3 秒。这个改进如果没有指标数据支撑,很难快速锁定根因。

另外,还可以利用 stats.groupModulesByLayer 和 groupModulesByType 分析不同 loader 处理的模块分布。例如发现某个 loader 处理的模块数量异常多,且大部分没有被缓存,就可以考虑对该 loader 启用缓存(如 babel-loader 的 cacheDirectory),或者通过 exclude 减少处理范围。这些分组能力让效率分析从总体粒度下钻到具体模块和 loader,极大提升了定位问题的效率。

Efficiency Metrics 的局限与思考

尽管 Efficiency Metrics 非常强大,但它并不是万能钥匙。首先,这些指标需要在每次构建后记录并分析,如果只是偶尔看一眼,很难发现趋势变化。其次,指标本身也可能带来一定的性能开销,尤其是开启 moduleTrace 或详细分组后,stats 生成时间会略有增加。在超大项目中,频繁输出完整 stats 甚至会拖慢构建几秒钟,所以生产环境建议只保留关键字段,并按需采样。

另一个局限是不同机器、不同操作系统上的构建耗时天然存在差异,直接用数值对比可能失真。团队内部应当统一基准环境,比如在 CI 中使用固定的容器镜像和硬件配置,并在相同入口和依赖版本下比较。同时,缓存命中率受构建上下文影响很大,例如多页面应用在启动时可能只编译部分页面,导致缓存指标波动明显,需要结合具体场景设定合理的阈值。

最后要认识到,Efficiency Metrics 只是工具,优化构建效率的根本还是要从配置、依赖管理和项目结构入手。比如合理拆包、减少不必要的 loader、利用持久化缓存、优先使用 esbuild 或 swc 等更快的转译器。把指标当作方向盘,而不是油门,才能让 Webpack 5 的构建性能持续保持在健康水平。对于团队而言,建立一套基于 Efficiency Metrics 的持续监控机制,远比偶尔手动优化一次更有价值。

广告位 · 文章底部(后台-模块管理替换此区域)