导读:本期聚焦于画家创作的《如何利用 Webpack stats.timings 分析构建各阶段耗时并优化打包速度?》,敬请观看详情。Webpack 构建慢的根源往往藏在各个阶段的耗时分布里,而 stats.timings 正是打开这扇门的钥匙。本文从 stats 对象的结构入手,讲解 time 字段的含义与各阶段耗时的对应关系,演示如何在 Node API 中读取并格式化这些数据,再结合一个可视化脚本将各阶段耗时输出为清晰的清单。文中还对比了 stats.timings 与 speed-measure-webpack-plugin 等工具的差异,分析 compilation 钩子里自定义打点统计的思路,并针对耗时集中的模块解析、loader 转换、代码压缩等环节给出可落地的优化建议,帮助你快速定位构建瓶颈。

Webpack 构建一次动辄几分钟,很多人第一反应是加缓存或者换机器,但更合理的做法是先搞清楚时间到底花在了哪里。Webpack 在每次编译结束后会生成一个 stats 对象,其中包含一个 timings 字段,记录了整个编译过程的关键耗时信息。读懂并利用好这个字段,是做构建性能分析的第一步。本文将围绕 stats.timings 的结构、读取方式以及如何借助它定位瓶颈展开讲解。

如何利用 Webpack stats.timings 分析构建各阶段耗时并优化打包速度?

stats.timings 到底记录了什么

当一次编译完成后,Webpack 会把编译过程中的时间数据汇总到 stats 对象的 timings 属性里。在 Webpack 4 及更早版本中,这个字段的结构比较扁平,主要包括 time(总耗时,单位毫秒)、buildTime 等键。而在 Webpack 5 中,timings 的信息更加结构化,它是一个以时间戳形式记录的对象,内部包含 startTimebuildTime 等字段,分别代表编译开始的时间戳和实际构建消耗的毫秒数。

需要注意的一点是,stats.timings 给出的是一个宏观层面的时间汇总,它告诉你这次构建总共花了多久,但不会直接告诉你每个 loader 花了多少毫秒、每个插件占用了多少时间。它更像一个总账,而不是明细账。不过这个总账非常有价值:通过在修改配置前后对比 buildTime 的变化,你可以量化每一次优化到底带来了多少收益,避免凭感觉做判断。

拿到 stats 对象的方式通常有两种。如果使用命令行,可以在 webpack.config.js 中配置 stats: 'verbose' 或在输出中包含 timing 信息;如果是通过 Node API 调用 webpack,那么回调函数的第二个参数就是完整的 stats 对象,直接访问 stats.toJson({ timings: true }) 即可拿到序列化后的 timings 数据。

用 Node API 读取并展示各阶段耗时

命令行输出的信息有限,更灵活的方式是写一个脚本来构建并解析 stats。下面是一个完整的示例,它调用 webpack 进行编译,然后从 stats 中提取 timing 相关数据并格式化输出。

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

const compiler = webpack(config);

compiler.run((err, stats) => {
  if (err) {
    console.error(err);
    process.exit(1);
  }

  // 将 stats 序列化为 JSON,显式开启 timings
  const json = stats.toJson({ timings: true, assets: true, modules: false });

  console.log('===== 构建耗时统计 =====');
  console.log(`总耗时 buildTime: ${json.time} ms`);

  if (json.timings) {
    Object.keys(json.timings).forEach(key => {
      const value = json.timings[key];
      // startTime 是时间戳,buildTime 是毫秒数,分类展示
      if (key === 'startTime') {
        console.log(`开始时间: ${new Date(value).toLocaleString()}`);
      } else {
        console.log(`${key}: ${value} ms`);
      }
    });
  }

  compiler.close(() => {
    console.log('编译器已关闭');
  });
});

这段代码的关键点在于 stats.toJson 的参数。timings: true 确保 timing 数据被包含在输出中,同时可以通过关闭 modules 等选项减少序列化开销。输出的 buildTime 就是这次完整编译消耗的毫秒数,把它记录到文件里,多次构建后就能形成一条趋势数据,方便观察构建速度的长期变化。

如果你希望对多个配置(比如开发环境和生产环境)分别统计,可以把上面的逻辑封装成函数,遍历配置数组依次执行,并把结果写入 JSON 文件,再配合简单的图表工具做趋势展示。这种低成本的做法在很多团队里比引入完整的构建监控平台更实用。

深入 compilation 钩子做精细化打点

stats.timings 的粒度不够细时,可以借助 Webpack 的插件机制在编译生命周期的各个钩子上打时间戳。Webpack 的 Compiler 和 Compilation 对象暴露了大量钩子,比如 make(开始构建模块)、finishMakeseal(开始封装优化阶段)、afterSealemit(输出资源到磁盘)等,正好对应构建的几个大阶段。

class TimingPlugin {
  apply(compiler) {
    const marks = {};

    compiler.hooks.make.tap('TimingPlugin', () => {
      marks.make = Date.now();
    });

    compiler.hooks.compilation.tap('TimingPlugin', (compilation) => {
      compilation.hooks.finishModules.tap('TimingPlugin', () => {
        marks.finishModules = Date.now();
      });

      compilation.hooks.seal.tap('TimingPlugin', () => {
        marks.seal = Date.now();
      });

      compilation.hooks.afterOptimizeChunkAssets.tap('TimingPlugin', () => {
        marks.afterOptimize = Date.now();
      });
    });

    compiler.hooks.emit.tapAsync('TimingPlugin', (compilation, callback) => {
      marks.emit = Date.now();
      callback();
    });

    compiler.hooks.done.tap('TimingPlugin', () => {
      marks.done = Date.now();
      console.log('===== 各阶段耗时 =====');
      const order = ['make', 'finishModules', 'seal', 'afterOptimize', 'emit', 'done'];
      order.forEach((key, i) => {
        if (i === 0) return;
        const prev = order[i - 1];
        const cost = marks[key] - marks[prev];
        console.log(`${prev} -> ${key}: ${cost} ms`);
      });
    });
  }
}

module.exports = TimingPlugin;

把这个插件加入配置后,每次构建结束都会打印出各阶段的分段耗时。一般来说,makefinishModules 这段对应模块的解析和 loader 转换,往往是耗时大头;seal 之后的阶段对应 Tree Shaking、代码分割、压缩等优化流程,生产构建中这块占比会明显升高。有了分段数据,优化方向就清晰多了:模块阶段慢就治理 loader 和 resolve,优化阶段慢就调整 terser 的并行度或换用 esbuild 压缩。

与第三方工具相比,这种自研打点的方式没有额外依赖,也不会干扰构建本身的速度。像 speed-measure-webpack-plugin 这类插件虽然能给出每个 loader 和插件的耗时,但它对 Webpack 5 的兼容性曾长期不稳定,而且测量本身会带来一定的性能损耗,在 CI 环境中开启可能导致数据失真。因此建议的做法是:日常用 stats.timings 记录总耗时,需要排查问题时再临时启用详细的打点插件。

根据耗时分布制定优化策略

定位到瓶颈之后,优化就有了明确靶子。如果耗时集中在模块构建阶段,优先检查几件事:一是排除不必要的 loader 处理范围,比如让 babel-loader 只处理 src 目录并开启 exclude: /node_modules/;二是利用缓存,Webpack 5 内置的持久化缓存(cache: { type: 'filesystem' })通常能把二次构建时间压缩到原来的几分之一;三是检查 resolve 配置,合理设置 extensionsalias,减少模块查找的尝试次数。

如果耗时集中在 seal 之后的优化阶段,重点则不同。压缩是生产构建中最常见的慢点,可以把 terser-webpack-pluginparallel 选项设为 true 利用多核,或者直接改用速度更快的压缩方案。此外,source map 的生成方式对耗时影响极大,eval-cheap-module-source-mapsource-map 在大项目上的差距可能达到数倍,开发环境应尽量选择轻量的映射方式。

最后提醒一点,任何优化都应该用数据验证闭环。改完配置后重新跑一次构建,对比 stats.timings 中的 buildTime,确认收益真实存在再合入代码。构建优化最忌讳的是凭感觉堆砌配置,看似加了很多优化项,实际构建反而变慢了。把 stats.timings 的记录固化到 CI 流程中,让每次流水线都留下构建耗时数据,长期来看还能及时发现依赖膨胀带来的性能退化,防患于未然。

Webpackstats.timings构建耗时分析修改时间:2026-09-15 23:08:59

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260915/57557.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。