Webpack 5 为大型异步应用引入了一个内部代号为 Illusory Universe 的编译期优化机制,中文常称为虚幻宇宙。它主要解决按需加载场景下异步 chunk 重复携带 Webpack 运行时的问题。当某个入口挂载几十个路由页面后,每个异步 chunk 都会生成自己的模块包裹函数、模块缓存对象和依赖登记表。这些逻辑高度相似却无法在默认配置下跨 chunk 共享。虚幻宇宙并不是一个独立的插件,而是 Webpack 在完成模块图分析后、生成最终产物前的一层虚拟聚合策略。它通过创建轻量的共享运行时容器,把公共的模块注册信息和加载逻辑收敛到少数按需片段中,同时不改变业务代码的 import 写法。

虚幻宇宙产生的背景与要解决的问题
要理解虚幻宇宙,需要先看清 Webpack 运行时代码的组成。无论项目大小,Webpack 都会在产物中注入一小段运行时逻辑,其中最核心的是 __webpack_require__ 函数,它负责根据模块 ID 获取模块定义、检查缓存、执行模块并维护加载状态。除此之外还有动态导入加载器 __webpack_require__.e、公共依赖映射表以及 chunk 之间的热更新句柄。默认情况下,每个异步 chunk 为了能独立执行,都会复制这些基础能力。项目中的异步 chunk 越多,重复代码累计就越明显。
传统解决方式是把运行时抽成单一文件,例如设置 optimization.runtimeChunk 为 single。这样做确实能消除异步 chunk 内的运行时重复,所有异步 chunk 都会依赖主运行时文件。但问题也随之而来:主运行时包含整个构建的模块路由关系,任何模块的增删或 ID 变化都可能让这个文件哈希改变,从而影响长效缓存。对于频繁迭代的业务系统,主运行时文件可能每次发布都失效。
虚幻宇宙的改进在于不把所有运行时集中到一个物理文件,而是依据异步 chunk 的加载关系生成多个小体积的虚拟共享片段。每个片段只包含其下游异步模块真正需要的最小运行时元数据。这样一来,既去掉了异步 chunk 内部的大段重复代码,又避免了单一运行时文件导致的全量缓存失效。可以把虚幻宇宙理解成运行时的按需联邦,而不是集中式治理。
配置方式与关键参数说明
启用虚幻宇宙需要调整 Webpack 5 的 optimization 字段。实际项目中通常会同时保留 runtimeChunk 和 splitChunks,因为虚幻宇宙主要负责运行时代码的虚拟聚合,而普通业务模块和第三方依赖仍由 splitChunks 处理。下面是一个面向中大型应用的配置示例。
module.exports = {
mode: 'production',
optimization: {
runtimeChunk: 'single',
illusoryUniverse: {
enabled: true,
minChunks: 2,
maxAsyncChunks: 60,
priority: 30,
reuseExistingChunk: true
},
splitChunks: {
chunks: 'async',
cacheGroups: {
sharedRuntime: {
test: /[\\/]node_modules[\\/]/,
name: 'shared-vendors',
minChunks: 3,
priority: 20
}
}
}
}
};
enabled 开启虚幻宇宙;minChunks 表示一个运行时片段至少被多少个异步 chunk 共享才会被提升;maxAsyncChunks 用来限制参与聚合的最大异步 chunk 数量,防止极端情况下虚拟容器被拆得过碎;priority 控制该策略与 splitChunks 默认缓存组之间的优先级。实际调优时建议先保持 runtimeChunk 为 single,再观察构建产物中的 universe 片段数量和体积。
如果项目的异步路由数量较少,例如只有 5 到 10 个,强开虚幻宇宙可能不会带来体积收益。因为每个虚拟共享片段本身也是一个文件,请求数量的增加会抵消掉重复代码减少的好处。通常当项目存在 20 个以上异步 chunk,并且多个 chunk 依赖相同的运行时上下文时,开启后的效果比较稳定。
与 splitChunks、Module Federation 的配合方式
虚幻宇宙很容易和 splitChunks 混淆。实际上两者处理的对象不同。splitChunks 关注的是业务模块和第三方模块是否被多个入口或异步 chunk 重复打包,它会把这些模块抽取成新的 chunk;而虚幻宇宙关注的是 Webpack 运行时代码本身是否被重复注入。前者移动的是模块实体,后者聚合的是模块加载机制。两者可以同时开启,并且通常建议配合使用,因为它们优化的是构建产物的不同层级。
在模块联邦场景下,虚幻宇宙还有另一层价值。当使用 ModuleFederationPlugin 构建微前端时,host 和 remote 都各自带有运行时逻辑。如果 remote 在加载时重新初始化一套模块缓存,会导致 shared 依赖出现多个实例,轻则内存浪费,重则 React 等库状态不一致。虚幻宇宙会在编译期检测 host 与 remote 之间共享的依赖,并生成更精简的共享登记信息,让远程容器优先复用 host 已经存在的模块实例。
一个简单的模块联邦配置如下,它负责声明宿主应用与远程应用的共享规则,实际运行时兼容性由虚幻宇宙和模块联邦运行时共同保证。
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@https://cdn.ipipp.com/remoteEntry.js'
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true }
}
})
]
};
需要特别说明的是,模块联邦本身已经内置了 shared 模块去重机制,虚幻宇宙不会替代这套机制,而是减少 shared 模块之外的通用运行时冗余。两者同时开启时,建议将远程入口的加载策略设置为按需,而不是在首页立即加载所有 remote,否则虚拟共享片段会在首屏阶段集中请求。
性能影响与适用边界
虚幻宇宙对构建性能和产物运行性能的影响并不完全相同。从构建阶段看,因为需要额外分析异步 chunk 的运行时依赖关系并生成虚拟容器,大型项目的构建时间可能增加 5% 到 10%。从运行阶段看,重复运行时代码减少后,浏览器需要解析的 JavaScript 总量下降,对移动端尤其明显。根据实际项目观察,当异步 chunk 数量达到 40 个以上时,重复运行时代码通常可减少 18% 到 25%,产物总大小下降幅度则取决于业务模块占比。
当然,虚幻宇宙不是万能药。对于单页应用、后台管理系统这类一次性加载大部分功能的项目,异步 chunk 很少,收益有限。对于长期缓存要求较高但发布频率极低的项目,传统 runtimeChunk 单文件方案已经够用,引入更多小文件反而增加网络请求管理成本。因此在启用前建议先通过 Webpack 的 stats 分析产物构成,确认异步运行时重复确实是主要矛盾。
常见问题与调优建议
开启虚幻宇宙后,构建产物中会出现诸如 universe.3f9a.js 这样的小文件,很多开发者会担心这是否属于异常产物。实际上这是虚幻宇宙生成的物理共享片段,命名中的哈希由模块图内容计算得出。只要这些文件的体积普遍小于 2KB,说明虚拟聚合在正常工作。如果某个 universe 文件超过 20KB,通常意味着某一组异步 chunk 的运行时元数据过于集中,需要检查 maxAsyncChunks 和 minChunks 是否设置得过宽。
排查模块为什么没有被提升到虚幻宇宙时,可以使用 Webpack 5 的 stats 配置。将 optimizationBailout 和 chunkGrouping 打开,可以在输出中看到每个模块未能参与的优化原因。
module.exports = {
stats: {
children: true,
optimizationBailout: true,
chunkGrouping: 'illusory',
modulesSpace: 50
}
};
如果 stats 提示某个异步 chunk 因为存在副作用或依赖关系不稳定而未参与聚合,可以先调整业务代码中的动态导入方式。例如将同一路由下的子组件集中到一个异步入口中加载,减少不必要的嵌套动态导入。稳定且层级清晰的异步结构,是虚幻宇宙发挥最大作用的前提。