首页
/ 深入 webpack stats 输出:用 `stats: "normal"` 读懂标准编译报告(附逐行解析与预设对比)

深入 webpack stats 输出:用 `stats: "normal"` 读懂标准编译报告(附逐行解析与预设对比)

2026-09-07 13:12:03作者:何将鹤

导读

stats 是 webpack 用来控制编译结果信息输出的配置项,决定你在终端里看到多少构建详情。在 webpack 官方仓库的 examples/stats-normal/README.md 中,给出了一个把 stats 设置为 "normal" 的最小可运行示例,用于演示"标准"级别的编译报告长什么样。本文将以此示例为核心,完整复现其配置与产物、逐行解读其输出的三行日志,并深入 lib/stats/DefaultStatsPresetPlugin.js 源码,讲清 normal 预设与 detailedminimalsummarynone 等其他预设之间的差异,帮助你日后能依据构建场景选择合适的报告粒度,甚至在预设基础上微调出属于自己的输出。

一、示例速览: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!");

/******/ })()
;

三个值得注意的细节:

  1. 模块路径横幅:产物中 !*** ./example.js ***! 是模块的可读标识,对应构建日志中 ./example.js 这一模块名;
  2. unknown exports (runtime-defined):由于入口模块没有显式的 ESM export,webpack 无法静态声明其导出,于是标注为"运行时定义"的未知导出;
  3. runtime requirements: 为空:示例代码无异步依赖、无 HMR、无公共 chunk,因此运行时不携带任何额外 runtime 需求。

example.js 原始代码为 29 字节,经过生产模式压缩后(含运行时注释)整体输出 28 字节,日志里 ./example.js 29 bytesasset 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.XX.X.X 是构建时所用 webpack 的实际版本号(文档以占位符形式呈现,避免示例输出随版本漂移)。compiled successfully 表示零错误零警告。若构建存在问题,这一行会变为 compiled with warningscompiled 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,
	...
}

minimalsummarynone 则走向另一极端,通过 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: truemodulesSpace: 0assets: trueassetsSpace: 0)揭示了它的"只统计、不展开"本质;而 summary 只保留 version 与 errors/warnings 计数。值得注意的是,normal 并未出现在命名预设表里,它作为默认基准被单独处理(见第五节),这解释了为什么 normal 的输出既不刻意精简、也不做额外展开。

verbosedetailednone 的完整梯度还包含 errors-onlyall: 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 可用的字段与前面预设展开出的字段一一对应(如 hashbuiltAttimingsassetsmoduleschunksentrypointsloggingerrorDetailsmoduleTrace 等),粒度精确到单开关。微调时建议遵守两条纪律:日常开发保持 normal 或 minimal,避免大段噪音淹没关键错误;排查性能/体积/分包问题时临时切到 detailed 或 verbose,借助其 chunk 关系、usedExportsoptimizationBailout、内置 LOG from webpack.* 日志定位问题,问题修复后再切回轻量预设。

八、小结

stats: "normal" 提供了对"构建是否健康、产出了什么、谁参与构建"三件事的最优平衡表达:一行 asset、一行 module、一行编译结论。借助 webpack 官方仓库中这套刻意保持最小化的示例(stats-normal)及其姊妹示例(stats-detailedstats-minimalstats-summarystats-none),可以以最低成本掌握 stats 的预设语义;而 lib/stats/DefaultStatsPresetPlugin.jsNAMED_PRESETS 的字段集合则揭示了每一档预设背后的开关矩阵,为后续按需做对象级微调提供了可查证的源码依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388