Webpack 的 stats 对象里有一个字段叫 builtAt,名字看起来像日期,实际拿到的却是一串数字。这串数字是 Unix 时间戳,单位是毫秒,表示一次 compilation 构建完成的时间点。它的主要用途不是直接给用户看,而是给程序做日志记录、耗时统计和构建状态追踪。

stats.builtAt 在 Webpack stats 体系里的位置与取值来源
Webpack 在每次构建结束后会生成一个 stats 对象,这个对象可以序列化成 JSON,也可以格式化成终端字符串。stats 数据里包含模块、资源、错误、警告、耗时等多种信息,builtAt 属于时间类字段。它记录的不是字符串形式的日期,而是从 ECMAScript 纪元开始经过的毫秒数,也就是常见的 Unix timestamp 毫秒格式。
从 Webpack 内部编译流程看,builtAt 对应 compilation 结束阶段记录的时间。它和 compiler.hooks.done 回调触发的时间非常接近,但不完全等同。因为 stats 对象的生成、序列化也会消耗少量时间,所以如果在回调里用 Date.now() 代替,得到的结果会略微偏晚。这个差异在普通项目里可能只有几毫秒,但在构建统计面板或 CI 日志里,如果要求时间边界清晰,还是应该以 builtAt 为准。
默认情况下,Webpack 的 normal stats preset 不一定输出 builtAt 字段。如果只是执行 webpack 命令然后查看控制台输出,很可能看不到它。需要使用 verbose 级别的 preset,或者在 stats 配置中显式开启 builtAt,才能保证它在 JSON 或字符串输出中出现。
在 Webpack 配置中开启 builtAt 的几种方式
最常用的做法是在 webpack.config.js 中设置 stats.builtAt。Webpack 的 stats 配置既可以是字符串 preset,也可以是对象。当使用对象形式时,可以精确控制想要输出的字段。下面是一个开启 builtAt 和 assets 的最小配置:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
stats: {
builtAt: true,
assets: true
}
};
如果你通过 Node API 调用 Webpack,不修改全局配置也可以直接获取这个字段。在 stats.toJson() 方法中传入 builtAt: true,就能从结果对象里读取构建完成时间戳。这种方式很适合接入自动构建脚本,不需要污染 Webpack 主配置。
const webpack = require('webpack');
const config = require('./webpack.config.js');
const compiler = webpack(config);
function handleResult(err, stats) {
if (err) {
console.error(err);
return;
}
const info = stats.toJson({ all: false, builtAt: true, time: true });
console.log('builtAt:', info.builtAt);
console.log('time:', info.time + 'ms');
}
compiler.run(handleResult);
也可以在自定义插件里读取,例如在 compiler.hooks.done 回调中接收 stats 参数,然后调用 toJson 提取。这样做的好处是构建一结束就能立即处理,适合把结果写入日志文件或发送到监控接口。
class BuildTimePlugin {
apply(compiler) {
compiler.hooks.done.tap('BuildTimePlugin', function (stats) {
const info = stats.toJson({ all: false, builtAt: true, time: true });
console.log('build completed at', info.builtAt);
console.log('build duration', info.time + 'ms');
});
}
}
module.exports = BuildTimePlugin;
把 builtAt 转换成本地可读时间并计算构建耗时
builtAt 直接输出时是一串数字,例如 1743420000000。要让它对开发者友好,需要交给 JavaScript 的 Date 对象处理。new Date(builtAt) 会创建一个日期对象,然后可以调用 toISOString() 得到标准 UTC 时间,也可以调用 toLocaleString() 得到本地时区的可读格式。
const builtAt = 1743420000000;
const time = new Date(builtAt);
console.log(time.toISOString());
console.log(time.toLocaleString('zh-CN', { hour12: false }));
如果需要在 CI 日志里同时记录构建开始时间和结束时间,可以结合 stats.time 字段。Webpack 的 stats.time 表示本次构建消耗的总毫秒数,用 builtAt - time 就能近似得到构建开始时刻。这个方法虽然不是精确到纳秒,但对于日志展示、构建趋势分析已经足够稳定。
function logBuildRange(info) {
const endTime = new Date(info.builtAt);
const startTime = new Date(info.builtAt - info.time);
console.log('start:', startTime.toLocaleString());
console.log('end:', endTime.toLocaleString());
console.log('duration:', info.time + 'ms');
}
需要特别注意的是,builtAt 是构建完成时刻,不是当前时间,也不是 stats 输出时刻。多阶段构建、watch 模式、缓存命中时,这个字段的含义仍然不变。只要一次 compilation 结束,它就会被记录一次。因此在 watch 模式下,每次重新构建都会产生新的 builtAt 值。
builtAt 与 Date.now、hooks.done 的区别及常见误区
一个常见的误用是在构建回调里直接用 Date.now() 记录结束时间,然后把它和 builtAt 混在一起比较。实际上 Date.now() 是回调执行时间,统计的是“构建完成之后又执行了多少逻辑”,而 builtAt 是 compilation 完成时刻。前者会包含 stats 生成、回调调度等开销,后者更接近真实构建结束点。
另一个常见问题是时区。很多开发者拿到毫秒时间戳后直接拼到日志里,结果排查问题时还需要手动换算。正确做法是统一使用 ISO 字符串或带时区标记的格式。例如 time.toISOString() 输出的是 UTC 时间,可读性和跨地域协作都更好。如果必须展示本地时间,也要使用 toLocaleString() 而不是手动拼接年月日,因为手工格式化容易在不同 Node 版本或操作系统上出现不一致。
下面通过一个表格对比几种常见的时间来源,帮助区分它们的用途:
| 数据来源 | 含义 | 单位 | 适用场景 |
|---|---|---|---|
| stats.builtAt | compilation 完成时间点 | 毫秒时间戳 | 记录构建结束时刻 |
| stats.time | 本次构建总耗时 | 毫秒 | 性能统计与阈值告警 |
| Date.now() in done | 回调执行时刻 | 毫秒时间戳 | 粗略统计,包含额外开销 |
| compilation.endTime | compilation 结束时间 | 毫秒时间戳 | 需要直接访问 compilation 对象 |
理解了这些差异之后,就可以把 builtAt 放在正确的位置上。比如在构建完成插件里写入 JSON 日志时,建议同时记录 builtAt 和 stats.time,这样后续无论是做性能回归分析,还是排查某次发布是否在预期时间完成,都有清晰的数据依据。不要只记录一个可读日期,因为程序化分析需要的是稳定的时间戳,而不是给人看的字符串。
总体来说,stats.builtAt 是 Webpack 提供给开发者定位构建结束时刻的重要字段。它默认不开启、单位是毫秒、值是一个数字,这些特性决定了它更适合被程序消费。把配置开好、把转换写对、把时间来源分清楚,就能避免大部分构建日志里的时间错乱问题。
Webpack stats.builtAt构建完成时间戳webpack 配置修改时间:2026-10-07 01:33:55