在 Webpack 5 的模块联邦体系中,不同构建产物之间会形成多个独立的作用域,官方社区常把这种隔离状态称为 Competing Universe,也就是竞争宇宙。它并不是一个单独的配置项,而是一种描述多入口、多远程应用共享依赖时可能出现的冲突场景。假设一个微前端宿主应用同时加载了两个子应用,它们都依赖 React,但一个是 React 17,另一个是 React 18。如果这两个子应用各自在本地打包了一份 React,运行时就会同时存在两份不同的 React 实例,状态管理、Context、事件系统都会出现不可预期的行为。Competing Universe 要解决的就是如何在这些相互竞争的依赖版本之间建立一套统一的仲裁机制。

从实现角度看,Webpack 5 的 Module Federation 插件提供了一个 shared 配置项,它允许声明哪些依赖需要在多个构建之间共享。当宿主应用和远程应用对同一个依赖声明了不同的版本范围时,Webpack 会根据版本要求进行协商,优先使用满足所有方要求的最低版本,如果无法满足,则根据 singleton 和 eager 等选项决定是否加载多个副本。这种协商过程就是竞争宇宙的核心逻辑:不同的宇宙各自携带自己的依赖候选,最终由 Webpack 运行时决定哪个版本进入公共作用域。
从 externals 到 shared:依赖共享的演进
在 Webpack 5 出现之前,微前端项目通常使用 externals 或 DLL 的方式来共享公共依赖。externals 会假设依赖由全局环境提供,比如把 React 挂载到 window.React 上,所有子应用都从全局对象读取。这种方式虽然避免了重复打包,但版本管理极其脆弱:一旦某个子应用升级了 React 版本,其他子应用必须同步升级,否则就会遇到全局对象被覆盖或者 API 不兼容的问题。DLL 方案也有类似问题,它需要预先构建一个公共库,并且要求所有消费者都使用完全相同的版本,灵活性很低。
Webpack 5 的 shared 机制则完全不同。它不再依赖全局变量,而是通过模块联邦运行时在构建产物中注入共享作用域。每个构建可以声明自己需要什么版本的依赖,以及是否强制单例。运行时会在加载远程模块时动态检查版本,如果已经存在兼容版本,就复用;如果不存在,就加载远程应用自己的版本。这样,不同宇宙之间的依赖竞争被转化为版本协商,而不是粗暴的全局覆盖。比如一个远程应用声明 requiredVersion 为 ^17.0.0,而宿主应用声明 ^18.0.0,当 singleton 为 true 时,Webpack 会优先加载宿主应用的 React 18,并向远程应用发出警告,但不会强制中断运行。
配置示例:管理 React 版本的竞争宇宙
下面是一个典型的 Module Federation 配置,它展示了如何在一个宿主应用中处理两个远程应用对 React 版本的不同要求。
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
app1: "app1@http://localhost:3001/remoteEntry.js",
app2: "app2@http://localhost:3002/remoteEntry.js",
},
shared: {
react: {
singleton: true,
requiredVersion: "^18.0.0",
},
"react-dom": {
singleton: true,
requiredVersion: "^18.0.0",
},
},
}),
],
};
在这个配置中,宿主应用强制 React 以单例模式共享,并且要求版本为 18.x。如果远程应用 app1 声明的是 React 17,app2 声明的是 React 18,那么运行时启动后,app2 会直接复用宿主应用的 React 18,而 app1 则无法满足版本的严格要求。此时 Webpack 会尝试从 app1 的远程入口加载它自己的 React 17,但由于 singleton 为 true,它会检查是否已经存在 React 18 实例。如果存在,就会阻止 React 17 的加载,并输出一个版本冲突警告。最终结果是 app1 会运行在 React 18 环境下,虽然它内部的代码预期是 React 17,但大多数 API 是向后兼容的,因此不会崩溃。
如果希望更灵活地处理版本冲突,可以设置 singleton: false 或者直接不配置 singleton。这样名为 Competing Universe 的不同依赖版本就可以并行存在。但这会带来另一个问题:同一个页面上同时存在两份 React 时,事件系统会分属两套不同的合成事件体系,第三方库如果依赖 React 的内部实例就会出现异常。因此在实际项目中,建议对核心框架始终使用 singleton: true,而对一些工具库可以根据影响面决定是否允许多版本共存。
竞争宇宙下的缓存与构建性能
当多个构建产物同时管理共享依赖时,Webpack 5 的持久化缓存机制会面临额外的复杂度。默认情况下,每个构建的模块图都是独立存储的,但如果多个构建使用了相同的 shared 依赖源,Webpack 需要确保缓存的模块标识在不同宇宙之间不会串扰。为了提升多应用构建的性能,可以使用 Webpack 5 的 experiments.futureDefaults 来启用更合理的默认缓存策略,或者显式配置 cache.type 为 filesystem 并区分不同的 cacheDirectory。
例如在多宇宙场景中,宿主应用和远程应用可能都在同一台机器上构建。如果它们共用同一个缓存目录,可能会因为不同的版本判断逻辑导致缓存污染。建议为每个构建单独指定缓存目录,例如 host/.webpack_cache、app1/.webpack_cache 等。这样可以避免不同宇宙之间的模块依赖记录互相干扰,同时保留缓存带来的构建加速效果。另外,合理配置 output.uniqueName 也很有必要,它可以避免多个远程应用在同一页面加载时因全局变量碰撞产生的运行时错误。
理解 Competing Universe 并不需要死记某个术语,而是要掌握 Webpack 5 在模块联邦场景下处理依赖竞争的底层思路。通过精确控制 shared 的版本范围和 singleton 选项,结合独立的缓存目录与输出命名,完全可以让多个微前端应用在同一个宿主环境中稳定共存,同时避免重复加载和不必要的运行时冲突。
Webpack 5Competing Universe模块联邦修改时间:2026-08-20 05:53:11