模块联邦为什么被称为推广宇宙
Webpack 5 中的模块联邦常被社区形容为一个可推广的宇宙,是因为它打破了构建边界,使不同项目像星系中的独立应用一样通过共享模块相互连接。传统微前端方案通常要求宿主在构建阶段确定依赖,或者借助全局变量和 CDN 暴露组件。而模块联邦把模块加载延迟到运行时,每个应用都可以独立开发和部署,只需要在配置中声明自己暴露哪些模块、消费哪些远程入口。

这种模式下没有中心化的包管理器参与,也不需要在宿主中提前安装远程应用的代码。远程应用启动后,暴露出一个入口文件,宿主通过动态 import 语法按需拉取。比如 A 应用可以暴露按钮组件,B 应用无需下载 A 的源码,只要声明 remotes 指向 A 的远程入口文件,就能在代码里直接 import 该组件。模块之间的依赖通过 shared 字段协商,Webpack 会在运行时检查版本兼容性并复用公共依赖,从而避免重复打包。
推广宇宙这个说法并非官方规范,而是对模块联邦生态能力的形象概括:一旦团队内几个核心应用采用这种协议,后续新项目接入成本会越来越低,可复用的模块数量也会加速增长。它不仅能共享组件,还可以共享工具函数、状态管理模块、路由配置甚至整段业务逻辑。真正要落好,需要先理解插件配置和运行机制,而不是只看到它能减少发版这一点。
ModuleFederationPlugin 的核心配置与示例
要让两个应用之间形成推广宇宙,最核心的配置项是 ModuleFederationPlugin。它通常出现在 webpack.config.js 的 plugins 数组中,主要包含 name、filename、exposes、remotes、shared 五个字段。name 表示当前应用的唯一标识,filename 是远程入口文件的名称,一般设置为 remoteEntry.js。exposes 声明当前应用对外暴露的模块路径,remotes 声明要消费的远程应用地址,shared 则用来配置多个应用共享的依赖。
下面是一个远程应用的配置示例,它暴露了 Button 和 utils:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
port: 3001,
},
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./utils': './src/utils/format',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
}),
],
};
宿主应用的配置则通过 remotes 字段声明远程入口地址。远程地址通常在开发环境指向 localhost 的端口,生产环境换成 CDN 或静态资源域名。配置完成后,宿主代码里可以直接写动态 import,Webpack 会把它拆成异步 chunk,并在运行时拼接远程入口路径。示例如下:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
port: 3000,
},
plugins: [
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
}),
],
};
在业务代码中,可以这样消费远程模块:
import React, { lazy, Suspense } from 'react';
const RemoteButton = lazy(() => import('remoteApp/Button'));
export default function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<RemoteButton text="来自远程应用" />
</Suspense>
);
}
推广宇宙落地时的依赖共享与边界问题
模块联邦最容易被低估的部分是 shared 配置。如果不用 singleton,多个远程应用各自带一份 react,页面上会出现多个 React 实例,轻则体积膨胀,重则 hooks 调用报错。开启 singleton 后,Webpack 会尽量复用宿主已经加载的依赖,但如果版本范围不兼容,运行时可能加载多份。因此团队最好在 shared 中明确版本号,并通过 package.json 统一 lockfile,让每个应用的公共依赖保持相同主版本。
样式隔离也是推广宇宙中常见的问题。远程组件带来的 CSS 默认会插入宿主页面,不同应用的 class 命名可能互相覆盖。可以借助 CSS Modules、BEM 命名规范,或者使用 shadow DOM 做更严格的隔离。对于大型微前端项目,团队应当在接入规范中约束全局样式、重置样式和第三方 UI 库的引入方式,避免远程组件卸载后样式残留或污染宿主页面。
路由协同方面,模块联邦本身不提供路由管理,它只负责模块加载。宿主应用需要决定如何把远程页面挂到自己的路由表中。常见做法是宿主路由使用通配符匹配,将未知路径交给远程应用处理,或者预先在宿主注册远程路由配置。由于远程入口是异步加载的,路由懒加载和 Suspense 的配合要稳定,否则首屏会闪烁或者出现空白。
跨域问题同样直接影响推广宇宙的可用性。远程入口文件通常部署在不同域名下,浏览器 fetch 或 script 加载会受到 CORS 限制。需要确保远程服务器返回正确的 Access-Control-Allow-Origin 响应头,生产环境可以使用 CDN 统一域名,或者通过反向代理把远程入口映射到同源路径。开发阶段如果使用 webpack-dev-server,可以启用 headers 配置允许跨域访问。
与传统微前端方案的取舍
相比 qiankun 这类基于 HTML 入口的微前端框架,Webpack 5 的模块联邦更偏重于构建产物级别的共享,它不要求宿主管理子应用的挂载和卸载生命周期,也不需要每个子应用都打包成完整的 HTML。模块联邦的粒度可以细到单个组件或函数,而 qiankun 一般以整个子应用为最小调度单元。因此在已有大量独立部署应用、希望保留各自技术栈和发布节奏的场景下,模块联邦往往更灵活。
但模块联邦也有明显短板。它对 Webpack 5 的依赖较强,旧项目需要从 Webpack 4 升级并调整配置;对运行时共享依赖的理解要求高,团队需要建立版本协商机制;调试跨应用模块时,source map 和错误堆栈可能不如单体应用直观。另外,模块联邦不能替代后端服务治理,也不能解决接口鉴权、灰度发布、监控告警等问题,它解决的是前端代码组织和加载效率问题。
如果团队规模不大、应用之间没有强共享需求,引入模块联邦反而会增加认知负担。但当多个业务线都需要复用同一套设计系统、表单引擎或数据请求模块时,推广宇宙的优势会很快显现。建议先用两个低风险应用做试点,把 exposed 模块的边界、shared 依赖版本和远程入口地址管理方式跑通,再逐步推广到更多项目。
总体来看,Promoting Universe 推广宇宙并不是某个具体 API,而是 Webpack 5 模块联邦能力与多团队协作模式结合后形成的一种架构生态。掌握其配置思路和边界约束,比记住几个参数更重要。