深入 webpack stats 输出:用 `stats: "normal"` 读懂标准编译报告(附逐行解析与预设对比)
导读
stats 是 webpack 用来控制编译结果信息输出的配置项,决定你在终端里看到多少构建详情。在 webpack 官方仓库的 examples/stats-normal/README.md 中,给出了一个把 stats 设置为 "normal" 的最小可运行示例,用于演示"标准"级别的编译报告长什么样。本文将以此示例为核心,完整复现其配置与产物、逐行解读其输出的三行日志,并深入 lib/stats/DefaultStatsPresetPlugin.js 源码,讲清 normal 预设与 detailed、minimal、summary、none 等其他预设之间的差异,帮助你日后能依据构建场景选择合适的报告粒度,甚至在预设基础上微调出属于自己的输出。
一、示例速览:normal 输出的标准形态
先看示例目录 examples/stats-normal/ 的整体构成(对应源码见 examples/stats-normal):
- example.js:被打包的入口模块,内容只有一个
console.log("Hello World!"); - webpack.config.js:定义了输出路径与
stats: "normal"; - README.md:官方示例说明,记录了配置、产物
dist/output.js与"Production mode"下的终端输出。
当以生产模式执行打包后,webpack 在终端打印的标准报告只有三行,全部内容如下:
asset output.js 28 bytes [emitted] [minimized] (name: main)
./example.js 29 bytes [built] [code generated]
webpack X.X.X compiled successfully
这份三行输出正是 normal 级别的"标准形态":既不是 none 那样什么都不打,也不是 detailed 那样铺满几百行内部日志。它聚焦于两个对用户最有价值的事实:产出了什么资源(asset)、哪些模块参与了构建(module),并以一行编译结论收尾。README 中对这份输出的评价是 "You see that everything is working nicely together"——即一切模块、依赖与构建流程正常协同工作的直观体现。
二、完整配置与可运行步骤
webpack.config.js 全文只有十几行,是一个不含任何 loader、plugin 的"最小 stats 实验台":
"use strict";
const path = require("path");
/** @type {import("webpack").Configuration} */
const config = {
output: {
path: path.join(__dirname, "dist"),
filename: "output.js"
},
stats: "normal"
};
module.exports = config;
对配置逐项解读:
output.path:产物输出目录,指向示例目录下的dist/(经path.join生成绝对路径);output.filename:产物文件名,固定为output.js,因此 README 的日志里 asset 名就是output.js;stats: "normal":核心配置。stats接受预设名字符串(如"normal")、布尔值或完整的对象式选项(详见下文第五节)。
入口文件 example.js 同样极为简单:
console.log("Hello World!");
本地复现方法:在示例目录执行 webpack 打包即可(例如 npx webpack --config webpack.config.js)。由于文档记录的是 "Production mode" 下的输出,为了让日志与下文解析完全一致,可在配置中显式加入 mode: "production"(默认的压缩与 [minimized] 标注正是生产模式默认开启 minimizer 的结果)。整个构建不包含任何依赖,只有一个模块,因此报告信息量被压缩到了最精简的状态——这恰好方便我们逐词对照着读懂每一行。
三、产物形态:normal 下被打包的 output.js
README 同时贴出了 dist/output.js 的产物结构,这对理解 normal 报告里"29 字节 vs 28 字节"的差别很有帮助。产物整体是一个自执行的 IIFE(webpack bootstrap 运行时):
/******/ (() => { // webpackBootstrap
/*!********************!*\
!*** ./example.js ***!
\********************/
/*! unknown exports (runtime-defined) */
/*! runtime requirements: */
console.log("Hello World!");
/******/ })()
;
三个值得注意的细节:
- 模块路径横幅:产物中
!*** ./example.js ***!是模块的可读标识,对应构建日志中./example.js这一模块名; unknown exports (runtime-defined):由于入口模块没有显式的 ESMexport,webpack 无法静态声明其导出,于是标注为"运行时定义"的未知导出;runtime requirements:为空:示例代码无异步依赖、无 HMR、无公共 chunk,因此运行时不携带任何额外 runtime 需求。
example.js 原始代码为 29 字节,经过生产模式压缩后(含运行时注释)整体输出 28 字节,日志里 ./example.js 29 bytes 与 asset output.js 28 bytes 的数字即来源于此。
四、逐行解读 normal 报告
第 1 行:asset 行
asset output.js 28 bytes [emitted] [minimized] (name: main)
各字段含义:
asset output.js:本次构建产出的资源文件名;28 bytes:产物最终体积;[emitted]:该资源在本轮编译中真正被写盘发出(而非来自缓存/上次构建);[minimized]:产物经过了压缩处理——这是生产模式默认开启代码压缩的标志;(name: main):该资源所属 chunk 的标识名,默认入口 chunk 名为main。
第 2 行:module 行
./example.js 29 bytes [built] [code generated]
./example.js:以仓库/项目根目录为基准的模块路径;29 bytes:模块源码体积;[built]:该模块被成功解析并纳入模块图;[code generated]:模块完成了代码生成阶段,被写入了最终 bundle。
对比同目录的 detailed 输出(见 examples/stats-detailed/README.md)可以发现:normal 不显示模块的 [depth](依赖深度)、内部 id、chunk 归属 {792}、以及"哪些导出被使用/为何未做模块合并(ModuleConcatenation bailout)"等深层信息——这些都属于 detailed 及以上粒度才披露的调试情报。
第 3 行:编译结论
webpack X.X.X compiled successfully
webpack X.X.X 中 X.X.X 是构建时所用 webpack 的实际版本号(文档以占位符形式呈现,避免示例输出随版本漂移)。compiled successfully 表示零错误零警告。若构建存在问题,这一行会变为 compiled with warnings 或 compiled with errors,并在其上方列出具体条目。
五、源码级视角:normal 预设与 stats 输出管线
stats 在编译器中的落地位置
在 lib/WebpackOptionsApply.js 中,webpack 向 compiler 注册了三个与 stats 相关的插件:
new DefaultStatsFactoryPlugin().apply(compiler);
new DefaultStatsPresetPlugin().apply(compiler);
new DefaultStatsPrinterPlugin().apply(compiler);
职责分工大致是:DefaultStatsPresetPlugin 负责把 stats 的字符串预设展开为细粒度选项集合;DefaultStatsFactoryPlugin 负责从 Compilation 中采集各维度数据(assets、modules、chunks、errors……)构成 stats 对象;DefaultStatsPrinterPlugin 负责把 stats 对象按人类可读的格式打印成终端文本。
"normal" 的特殊地位
在 lib/stats/DefaultStatsPresetPlugin.js 中,命名预设的类型定义很有意思:
/** @typedef {{ [Key in Exclude<StatsValue, boolean | StatsOptions | "normal">]: StatsOptions }} NamedPresets */
const NAMED_PRESETS = { ... };
从源码结构可以看出,"normal" 被显式排除在 NAMED_PRESETS(命名预设集合)之外,与布尔值、对象字面量并列单列处理。这佐证了 "normal" 在 webpack 中的语义——它不是某个需要"特判开启"的开关,而是默认的输出基准:当你不写 stats 选项时,得到的正是 normal 级别报告。因此示例中 stats: "normal" 相当于"明确声明使用默认输出粒度",是最贴近默认体验、也是大多数项目最常用的取值。
normal 之外的预设各自加了什么
同一文件里可以看到其余预设的展开定义(lib/stats/DefaultStatsPresetPlugin.js)。以 detailed 为例,它额外开启了一大组字段:
detailed: {
hash: true,
builtAt: true,
relatedAssets: true,
entrypoints: true,
chunkGroups: true,
ids: true,
chunks: true,
chunkRelations: true,
...
publicPath: true,
logging: true,
runtimeModules: true,
...
}
而 minimal、summary、none 则走向另一极端,通过 all: false 关闭大多数分类,只保留计数或结论(详见下一节的输出对比)。这套"预设 = 一组字段开关集合"的设计,正是第五节中"对象微调"能成立的根本原因:预设最终都会归一化为字段粒度,任何字段都可以被单独改写。
六、normal 与其他预设的实战输出对比
在 examples/ 目录下,webpack 为不同预设各准备了一个结构完全相同的示例,唯一区别是 stats 取值。把它们的输出并排比较,就能直观理解各预设的定位:
| 预设 | Production 模式下实际输出 | 定位 | 示例文档 |
|---|---|---|---|
none |
(无任何输出) | 完全静默,适合 CI 中把日志交给外部工具 | stats-none |
summary |
webpack X.X.X compiled successfully |
只保留结论 | stats-summary |
minimal |
1 asset / 1 module / webpack X.X.X compiled successfully |
只报数量 | stats-minimal |
normal |
asset 行 + module 行 + 结论 | 默认标准报告 | stats-normal |
detailed |
逐 chunk、逐模块展开,含 LOG 内部日志 | 深度调试 | stats-detailed |
其中 minimal 的源码定义(modules: true 但 modulesSpace: 0、assets: true 但 assetsSpace: 0)揭示了它的"只统计、不展开"本质;而 summary 只保留 version 与 errors/warnings 计数。值得注意的是,normal 并未出现在命名预设表里,它作为默认基准被单独处理(见第五节),这解释了为什么 normal 的输出既不刻意精简、也不做额外展开。
从 verbose、detailed 到 none 的完整梯度还包含 errors-only(all: false + 仅显示错误与错误计数)与 errors-warnings(额外加入警告),它们都由源码中 NAMED_PRESETS 的定义直接支撑。
七、在 normal 基础上做对象级微调
stats 的实际能力远不止于选择预设字符串:它接受预设字符串、布尔值或完整的对象。如果你认可 normal 作为基准、又希望补充少数几个字段,可以写成对象形式——webpack 会先应用对应预设的默认字段集合,再把你显式给出的字段覆盖进去。例如想要在标准报告上额外看到构建耗时与资源哈希:
/** @type {import("webpack").Configuration} */
const config = {
mode: "production",
entry: "./example.js",
stats: {
preset: "normal", // 以 normal 为基准
timings: true, // 追加:显示各阶段耗时
hash: true // 追加:显示编译哈希
}
};
module.exports = config;
对象式 stats 可用的字段与前面预设展开出的字段一一对应(如 hash、builtAt、timings、assets、modules、chunks、entrypoints、logging、errorDetails、moduleTrace 等),粒度精确到单开关。微调时建议遵守两条纪律:日常开发保持 normal 或 minimal,避免大段噪音淹没关键错误;排查性能/体积/分包问题时临时切到 detailed 或 verbose,借助其 chunk 关系、usedExports、optimizationBailout、内置 LOG from webpack.* 日志定位问题,问题修复后再切回轻量预设。
八、小结
stats: "normal" 提供了对"构建是否健康、产出了什么、谁参与构建"三件事的最优平衡表达:一行 asset、一行 module、一行编译结论。借助 webpack 官方仓库中这套刻意保持最小化的示例(stats-normal)及其姊妹示例(stats-detailed、stats-minimal、stats-summary、stats-none),可以以最低成本掌握 stats 的预设语义;而 lib/stats/DefaultStatsPresetPlugin.js 中 NAMED_PRESETS 的字段集合则揭示了每一档预设背后的开关矩阵,为后续按需做对象级微调提供了可查证的源码依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00