Webpack 5 的 Truth 并不是一个单独的编译插件,也很难在配置项里直接关掉。它是一个综合结论:当模块对最终产物没有任何可观察影响时,构建器认为它不贡献真值。这个思路和 JavaScript 的 truthiness 有点相似,但判断对象从运行时变量变成了构建期模块与函数。该结论由三部分共同产生,即 ES module 的静态 import/export 结构、sideEffects 规则和 innerGraph 内部依赖图。前两者告诉你模块能否被整体删除,后者告诉你模块内部的某个函数或变量是否真的需要保留。

一、从 Tree Shaking 看真值贡献
在 Webpack 5 的生产构建中,摇树优化仍然依赖 ES module 的静态结构。它只有在确认某个导出没有被任何导入方消费时,才会将该导出从产物中删除。这个删除动作并不是简单看模块有没有导出,而是看这个导出是否对最终 bundle 产生可见影响。若一个函数既没有被调用,也没有在模块顶层执行,就认为它不具备真值贡献。
下面这个例子里,工具模块同时提供了两个函数,但入口只使用了其中一个。
// utils/string.js
export function uppercase(value) {
return value.toUpperCase();
}
export function lowercase(value) {
return value.toLowerCase();
}
// index.js
import { uppercase } from './utils/string';
console.log(uppercase('hello'));
构建后,lowercase 不会出现在产物里。Webpack 5 能做出这个判断,是因为它分析了导入关系,确认 lowercase 从未被消费。Webpack 4 也能处理这种简单场景,但面对再导出、模块内变量赋值和条件分支时,往往因为无法继续追踪而保留冗余代码。Webpack 5 则把判断推到更接近运行时数据流的位置,这也是很多人将这种变化称为 Truth 分析的原因。
二、innerGraph 如何做模块内部真值分析
模块级摇树有一个明显缺陷:它只能判断模块与模块之间的导入导出关系,无法深入模块内部。比如一个模块导出 compute,而 compute 内部只调用 add,完全没有用到 sub,Webpack 4 仍然可能把整个模块顶层代码都保留下来。
// math.js
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
export function compute(x) {
return add(x, 1);
}
Webpack 5 的 innerGraph 会遍历函数体和变量引用,给 add 和 sub 分别标记是否被外部访问。结果 add 因为被 compute 引用而保留,sub 则被移除。这种细粒度处理在 Webpack 4 中很难实现,因为当时的依赖图主要记录模块之间的导入导出关系,不深入模块内部。
innerGraph 默认在生产模式启用,开发环境基本不受影响。它的核心价值是把真值判断从模块级下沉到变量级,让构建结果更接近代码真实的运行时数据流。
三、sideEffects 与真值判断的协同
除了内部依赖图,Webpack 5 还进一步依赖 sideEffects 来决定是否整个跳过模块。这个字段写在 package.json 中,告诉构建器哪些文件是真正无副作用的。副作用可以理解为模块顶层代码是否会在导入时执行某些外部可见操作,比如操作 DOM、写全局对象、注册事件或加载样式。
{
"name": "webpack5-truth-demo",
"sideEffects": [
"*.css",
"./src/polyfill.js"
]
}
如果一个模块被标记为无副作用,并且导入方没有消费它的任何导出,Webpack 会连模块的初始化代码一起删除。这个行为在实际项目中非常有用,比如一个工具库只导入了两个函数,其他大量工具函数不会进入最终产物。
但如果 sideEffects 配错,删除了有副作用的模块,就会出现全局注册丢失、样式丢失或 polyfill 失效等问题。例如某些组件库会在模块顶层执行 register(),这类文件必须写入 sideEffects 白名单,否则会被构建器判定为无真值贡献而误删。
四、从源码到产物的完整验证
接下来用一个最小工程验证上述过程。目录结构如下,所有源码位于 Windows 路径 C:\project\webpack5-demo 下。
webpack5-demo/
src/
index.js
utils/
number.js
string.js
其中 string.js 有两个导出,但入口只导入其中一个。再配合 package.json 的 sideEffects: false,可以观察最终产物中是否还存在未使用的函数。
// src/utils/number.js
export function toNumber(value) {
return Number(value);
}
export function toInteger(value) {
return parseInt(value, 10);
}
// src/utils/string.js
export function uppercase(value) {
return value.toUpperCase();
}
export function lowercase(value) {
return value.toLowerCase();
}
// src/index.js
import { toNumber } from './utils/number';
import { uppercase } from './utils/string';
console.log(toNumber('42'));
console.log(uppercase('webpack'));
构建命令可以在项目根目录执行。
cd C:\project\webpack5-demo npx webpack --mode production
构建完成后去 dist 目录查看产物。通常只会发现 toNumber 和 uppercase 的函数体被打包进去,而 toInteger 与 lowercase 都已经消失。这正是 sideEffects、usedExports 和 innerGraph 三个机制共同完成真值判断的结果。
五、升级 Webpack 5 时的常见误区
从 Webpack 4 升级到 5 时,最需要注意的往往不是写一堆新配置,而是重新审视 package.json 中的 sideEffects 字段。不少老项目并没有维护这个字段,或者直接设为 false,这会导致构建结果与本地运行表现不一致。
具体来说,如果某个模块在顶层进行 DOM 操作、注册全局事件或扩展原型链,它就不应该被标记为无副作用。正确的做法是把这个文件加入 sideEffects 数组白名单,或者在该模块内部显式引入这类副作用文件。
另一个容易混淆的地方是 CSS 文件。Webpack 5 的 Asset Modules 可以处理样式资源,但 CSS 的引入本质上是有副作用的。因此在配置 sideEffects 时,通常要像示例那样把 *.css 保留下来,否则生产环境会漏掉样式。
理解了这些以后再看 Truth 这个概念,就会明白它不是某种新的布尔开关,而是 Webpack 5 对模块真值贡献判断的一次收紧。只要建立清晰的导入导出结构,并维护好 sideEffects 白名单,构建结果的体积和运行行为都能保持稳定。
Webpack 5Tree ShakingsideEffects修改时间:2026-09-26 05:51:02