Webpack 5 发布之后,不少团队把老项目迁移过来,第一感受往往是构建变快了、产物变小了。除了持久化缓存这个显眼的更新,还有一个更底层的改进经常被忽略,那就是模块解析与剔除逻辑的重构。社区里有人用 Effective Universe,也就是有效宇宙这个词来形容它:一次构建真正需要打进产物的模块,构成了一个有限的、可计算的集合,其余的一切都应该被排除在外。理解这个概念,对做好 Tree Shaking、拆包和缓存优化都有直接帮助。

什么是 Effective Universe 有效宇宙
从字面看,Effective Universe 听起来有点玄,但它描述的东西很具体。Webpack 在构建时会从入口(entry)出发,沿着 import 和 require 语句一路递归解析,最终形成一张完整的模块依赖图。这张图理论上可以无限延伸,npm 依赖一个套一个,任何组件库都可能引入成百上千个传递依赖。但真正会出现在最终 bundle 里的,只是其中被实际执行、被实际读取的那一部分模块,这个子集就是有效宇宙。
在 Webpack 4 时代,判定一个模块是否有效的粒度比较粗,主要靠 export 标记和简单的静态分析,副作用(side effects)处理也不够精细。Webpack 5 重写了这套机制,引入了基于 JavaScript 语义的模块图分析能力,能够追踪到某个导出是否被读取、某个变量是否被引用,再配合 package.json 中的 sideEffects 字段,把判定精度提升到了表达式级别。
举个直观的例子:一个工具库导出了一百个函数,你的代码只用了其中一个。如果库作者正确声明了模块结构,Webpack 5 就能把其余九十九个函数相关的模块从有效宇宙中剔除,最终产物里只剩你用到的那一小段逻辑。这种剔除在 Webpack 4 中往往只能做到文件级别,而 Webpack 5 可以做到更细的导出级别,也就是社区常说的嵌套 Tree Shaking。
有效宇宙是如何被计算出来的
计算过程大致分为三个阶段。第一阶段是解析,Webpack 从入口出发,调用 NormalModuleFactory 逐条解析依赖请求,生成原始模块图。第二阶段是标记,编译器会分析每个模块的导出使用情况,结合模块的 module.usedExports 信息,判断哪些导出是死的。第三阶段是生成,只有在有效集合内的代码才会被合成到产物中,死代码在压缩阶段被彻底移除。
副作用声明是影响判定准确性的关键。可以在 package.json 中这样配置:
{
"name": "my-ui-lib",
"version": "1.0.0",
"sideEffects": false,
"main": "dist/index.js",
"module": "dist/index.esm.js"
}sideEffects 设为 false 是在向打包器承诺:这个包的所有模块都没有副作用,只要没被使用就可以放心删除。如果包里确实存在有副作用的文件,比如全局样式或者 polyfill,就要换成数组形式,把保留路径列出来:
{
"sideEffects": [
"*.css",
"./src/polyfills.js"
]
}另外,Webpack 5 对部分求值能力做了增强。对于一些能在构建期算出结果的常量表达式,比如 process.env.NODE_ENV === 'production' 分支,它会提前求值并裁掉不可达分支,进一步收窄有效宇宙的范围。这也是为什么建议在业务代码里使用 import 静态导入而非运行时拼接路径,前者可以被完整追踪,后者会让模块脱离静态分析,被迫整体纳入有效集合。
与持久化缓存和模块联邦的关系
有效宇宙的边界一旦稳定下来,缓存的价值就凸显了。Webpack 5 的文件系统缓存(FileSystemCache)会把模块的解析结果、转换产物序列化到磁盘,下次构建时只重新计算发生变化的子图。因为缓存是按模块粒度做的,所以依赖图越大,收益越明显。典型配置如下:
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
// 配置文件变化时让缓存失效
config: [__filename]
},
cacheDirectory: './node_modules/.cache/webpack'
}
};模块联邦(Module Federation)则把有效宇宙的概念扩展到了多个应用之间。宿主应用远程引用另一个暴露出来的模块时,被共享的依赖不会重复打包,而是通过运行时的共享作用域按需加载。换句话说,整个微前端体系的有效宇宙是由多个工程共同贡献、动态协商出来的,这在 Webpack 4 的单工程视角下是无法表达的。
常见踩坑点与排查手段
实际项目中,有效宇宙的边界经常被意外撑大。最常见的问题是引入了带副作用的库却没有声明,或者代码里存在动态 require,比如 require(someVariable),这会让 Webpack 退化到猜测模式,把整个目录打包进去。babel 转译 CommonJS 产物也会破坏 Tree Shaking 的前提,尽量走 ESM 通道可以避免这个问题。
排查时可以用 stats 分析产物的模块构成:
module.exports = {
stats: {
modules: true,
reasons: true,
usedExports: true,
providedExports: true
}
};再配合 webpack-bundle-analyzer 或者官方的 webpack --json 输出,就能清楚看到每个模块为何被保留、哪些导出被判定为未使用。如果发现某个明显没用的模块仍在产物里,先检查它的 sideEffects 声明,再看是否存在动态导入、顶层副作用代码或被 module.noParse 排除的情况。
总结一下,Effective Universe 不是某个具体的配置项,而是理解 Webpack 5 构建行为的一把钥匙。它把入口可达性、副作用声明、导出级 Tree Shaking、持久化缓存和模块联邦串成了一个整体。写代码时坚持 ESM、管好副作用声明、避免动态路径导入,就能让有效宇宙保持最小,构建速度和产物体积自然都会受益。
Webpack 5Effective Universe模块联邦修改时间:2026-09-08 07:08:44