人人都会AI编程

17.3 模块联邦与微前端构建支持

更新时间:2026-07-10

为什么需要模块联邦

随着前端项目规模膨胀,单个应用(巨石应用)的构建、部署和团队协作成本越来越高。微前端架构将一个大应用拆分为多个独立子应用,每个子应用可以由不同团队独立开发、独立部署,最后在运行时组合成一个完整的产品。

传统的微前端方案(如 qiankun、iframe)通常需要一个中心化的“基座”应用来加载子应用,并且子应用之间共享依赖困难,容易导致重复加载和版本冲突。模块联邦(Module Federation) 是 Webpack 5 推出的核心特性,它允许一个应用在运行时动态加载另一个应用的模块,无需中心化编排,并且可以精细控制依赖的共享和版本。

模块联邦的核心概念

  • Host(宿主):消费远程模块的应用。
  • Remote(远程):暴露模块供其他应用消费的应用。
  • Shared(共享依赖):多个应用间共享的库(如 react、react-dom),可以配置为单例,避免重复加载和版本冲突。
  • Exposes(暴露模块):Remote 应用中向外暴露的模块路径。

它的本质是:每个应用都可以既是 Host 又是 Remote,在运行时通过异步 chunk 加载其他应用的代码,如同使用本地模块一样。

在 React 项目中使用模块联邦

假设我们有两个独立 React 项目:host-appremote-appremote-app 暴露一个 Header 组件,host-app 在运行时加载并使用它。

第一步:在 remote-app 中配置暴露

使用 Webpack 5 的 ModuleFederationPlugin,在 webpack.config.js 中添加:

// remote-app/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp', // 远程应用的唯一名称
      filename: 'remoteEntry.js', // 远程入口文件
      exposes: {
        // 暴露组件:键是外部使用时的路径,值是本地文件路径
        './Header': './src/components/Header',
      },
      shared: {
        react: { singleton: true, eager: false, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: false, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};
  • singleton: true 确保整个页面只有一个 React 实例。
  • eager: false 表示异步加载共享依赖,避免初始包体积过大。

第二步:在 host-app 中配置消费

// host-app/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        // 名字:remoteName@remoteEntry地址
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, eager: false, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: false, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};

remotes 中的 remoteApp@http://localhost:3001/remoteEntry.js 告诉 webpack 在运行时从该 URL 加载远程模块。注意开发环境通常需要启动远程应用的服务。

第三步:在 Host 中使用远程组件

// host-app/src/App.jsx
import React, { Suspense } from 'react';

// 使用动态 import 加载远程模块
const RemoteHeader = React.lazy(() => import('remoteApp/Header'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>加载中...</div>}>
        <RemoteHeader title="微前端标题" />
      </Suspense>
      <main>这是宿主应用的主体内容</main>
    </div>
  );
}

export default App;

import('remoteApp/Header') 会触发加载远程入口文件,然后异步获取组件。React.lazy 配合 Suspense 处理加载状态。

使用 Vite 的模块联邦

Vite 生态中可以使用 @originjs/vite-plugin-federation 插件实现类似功能,配置略有差异但思想一致:

// vite.config.js (remote)
import federation from '@originjs/vite-plugin-federation';

export default {
  plugins: [
    federation({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/components/Header',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
  build: {
    target: 'esnext', // 必须,因为模块联邦使用原生 ES 模块
  },
};
// vite.config.js (host)
import federation from '@originjs/vite-plugin-federation';

export default {
  plugins: [
    federation({
      name: 'hostApp',
      remotes: {
        remoteApp: 'http://localhost:3001/assets/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

同理,在代码中用动态 import 引入远程组件。

微前端架构的最佳实践

  1. 独立仓库与独立部署

每个子应用应放在独立 Git 仓库,有独立的 CI/CD 管道。部署后更新 remoteEntry.js 的地址即可,宿主无需重新部署。

  1. 共享依赖的统一管理

reactreact-domreact-router 等核心库设为 singleton,避免多个实例导致的 Hooks 报错或状态不一致。可以考虑使用一个共享配置包来维护依赖版本。

  1. 样式隔离

默认情况下,远程组件的 CSS 会注入到全局,可能污染宿主样式。建议使用 CSS Modules 或 CSS-in-JS 方案做到样式隔离,或配置模块联邦的 shared 对样式作用域的控制。

  1. 通信方式

子应用间应尽量松耦合,推荐使用自定义事件、共享状态库(如 zustand 的 global store)或通过宿主传递回调 Props。避免直接依赖子应用的内部状态。

  1. 路由集成

如果远程应用有自己的路由,宿主可以选择将部分路径委托给远程应用(模块联邦暴露一个路由配置或页面组件),宿主通过 React Router 动态嵌套。

  1. 降级与容灾

远程模块加载失败时,应当显示降级 UI,避免整个宿主崩溃:

<ErrorBoundary fallback={<div>组件加载失败</div>}>
  <Suspense fallback={<div>loading...</div>}>
    <RemoteHeader />
  </Suspense>
</ErrorBoundary>

模块联邦 vs 传统微前端方案

| 方案 | 特点 | 缺点 |
|------|------|------|
| qiankun / single-spa | 基于路由分发,主子应用生命周期管理,成熟度高 | 需要改造子应用,JS 沙箱和样式隔离有性能损耗,依赖中心基座 |
| iframe | 完美的隔离性,简单 | 通信麻烦,URL 不同步,性能较差,用户体验割裂 |
| 模块联邦 | 运行时按需加载模块,极度灵活,可共享依赖避免重复加载,无中心基座 | 需要 webpack 5 / vite 插件,样式隔离需自行处理,调试复杂,对构建配置要求高 |

模块联邦更适合组件级别的跨应用共享,特别适合技术栈统一、团队自治且需要高度复用的场景(如大型 SaaS 平台中的公共组件库按需加载)。它能做到真正的“去中心化”微前端,每个应用都能提供能力并被消费。

总结

模块联邦为 React 项目的微前端实践提供了原生级的模块共享能力。它改变了传统的构建方式,让不同团队的代码可以在运行时无缝融合。结合 React 的组件化特性和动态 import,你可以构建出高度可扩展、独立演进的前端架构。然而,这也对团队的工程规范、依赖管理和监控能力提出了更高要求。在引入之前,务必评估项目的实际规模和拆分需求,避免过度工程化。