随着前端应用规模的持续膨胀,传统的单体前端架构逐渐暴露出协作效率低、部署耦合、技术栈锁定等问题。微前端(Micro Frontends)将微服务理念引入前端领域,允许将一个大应用拆分成多个更小、更独立的前端应用,由不同团队独立开发、测试、部署,最终组合成一个完整的用户体验。
27.2.1 微前端的核心价值
- 独立部署:每个子应用可以独立构建、独立发布,互不影响,加快迭代速度。
- 技术栈无关:主应用与子应用可以选用不同的框架(React / Vue / Angular 等),甚至同一框架的不同版本。
- 团队自治:按业务域垂直划分团队,团队全权负责自己的模块,减少跨团队沟通成本。
- 增量升级:老项目可以逐步迁移框架或重构,不需要一次性重写。
27.2.2 主流实现方式
目前主流的微前端方案可以分为两大类:JavaScript 运行时集成和构建时集成。这里重点介绍在 React 生态中落地最广泛的两个方案:基于 Webpack 5 的 Module Federation 和基于路由分发的 qiankun。
方案一:Webpack 5 Module Federation(模块联邦)
Module Federation 是 Webpack 5 推出的原生微前端能力,允许多个独立构建的应用程序在运行时动态共享模块,而无需统一构建。每个应用可以对外暴露组件、工具函数、状态等,也能消费来自其他应用的模块。
核心概念:
- Host(宿主应用):主应用,负责加载和整合远程模块,通常提供外壳、路由、公共依赖等。
- Remote(远程应用):子应用,独立构建、独立部署,暴露特定的模块给 Host 使用。
React 中的落地示例:
- 配置 Remote 应用(如
user-app)
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserPage': './src/pages/UserPage',
'./UserProfile': './src/components/UserProfile',
},
shared: ['react', 'react-dom'], // 共享依赖,避免重复加载
}),
],
};
在 UserPage 组件中正常编写 React 代码,导出即可。
- 配置 Host 应用(如
main-app)
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'mainApp',
remotes: {
userApp: 'userApp@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
- 在 Host 应用中消费远程模块
import React, { lazy, Suspense } from 'react';
const UserPage = lazy(() => import('userApp/UserPage'));
function App() {
return (
<div>
<h1>主应用</h1>
<Suspense fallback={<div>加载用户模块...</div>}>
<UserPage />
</Suspense>
</div>
);
}
优势:
- 原生支持,无需额外框架,依赖 Webpack 5。
- 真正的运行时共享模块,加载速度快,可做到组件级动态加载。
- 代码共享能力强,可避免重复加载 React 等公共库。
劣势:
- 对构建工具强依赖(必须使用 Webpack 5),且配置相对复杂。
- 样式隔离、JS 沙箱等需要自行处理或借助社区方案(如
@module-federation/utilities)。 - 子应用间的通信常需要额外设计(如共享状态库、自定义事件)。
方案二:qiankun(基于 single-spa 封装)
qiankun 是蚂蚁集团开源的微前端框架,基于 single-spa 封装,提供更简洁的 API、HTML Entry 加载方式、完整的 JS 沙箱和样式隔离能力,在国内落地场景非常多。
核心特性:
- HTML Entry:通过加载子应用的完整 HTML 文件(包含 CSS/JS)来接入,子应用可以独立访问 URL 直接运行。
- JS 沙箱:基于 Proxy 实现多实例 JS 隔离,防止全局变量污染。
- 样式隔离:支持严格样式隔离(Shadow DOM)和实验性的 Scoped CSS(自动添加选择器前缀)。
- 资源预加载:内置预加载策略,优化子应用切换体验。
React 中的落地步骤:
- 改造子应用(如
react-app)
- 在
src/index.js中导出生命周期钩子,并与 React 挂载/卸载逻辑对接:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
let root = null;
// 挂载函数
export async function mount(props) {
const { container, onGlobalStateChange, setGlobalState } = props;
root = ReactDOM.createRoot(
container ? container.querySelector('#root') : document.getElementById('root')
);
root.render(<App globalState={{ onGlobalStateChange, setGlobalState }} />);
}
// 卸载函数
export async function unmount(props) {
root?.unmount();
}
// 独立运行时直接渲染(非微前端环境)
if (!window.__POWERED_BY_QIANKUN__) {
mount({});
}
- 调整 Webpack 配置,输出 UMD 格式,并允许跨域:
// vue.config.js 或 craco.config.js
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
globalObject: 'window',
},
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' },
},
};
- 主应用注册子应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3002', // 子应用的地址
container: '#subapp-container',
activeRule: '/app-react', // 路由匹配规则
props: { message: '来自主应用的数据' },
},
]);
start();
- 主应用路由跳转
当用户访问 /app-react 时,qiankun 会自动加载子应用并挂载到指定 DOM 容器中,切换路由时自动卸载。
优势:
- 接入简单,开箱即用的沙箱隔离和样式隔离,安全性高。
- 支持多种前端框架(React / Vue / Angular / 原生 JS 等)。
- 活跃的社区和丰富文档,问题解决路径多。
劣势:
- 需要子应用遵循约定改造导出生命周期,增加了一定侵入性。
- 性能和隔离机制基于 JS 沙箱,对于大量动态脚本的场景可能会有小概率的兼容问题。
- 本质上是通过加载整个子应用 HTML 来运行,比 Module Federation 的粒度更粗。
27.2.3 两种方案的对比与选型建议
| 维度 | Module Federation | qiankun |
|------|------------------|---------|
| 粒度 | 模块/组件级 | 应用级 |
| 隔离能力 | 需自行处理 | 内置 JS 沙箱、样式隔离 |
| 依赖工具 | Webpack 5(或 tools 支持 MFP 的构建器) | 不限构建工具 |
| 通信方式 | 共享状态库、自定义事件 | 官方提供全局状态 API initGlobalState |
| 子应用侵入性 | 低(仅暴露模块) | 中(需导出生命周期) |
| 调试与开发 | 主应用控制远程加载,需整体启动 | 子应用可独立运行,开发体验好 |
| 性能 | 共享模块,体积占用小 | 加载整个 HTML 入口,公共依赖可能重复 |
| 适合场景 | 同一技术栈的微前端,或多个应用需要共享复杂组件的场景 | 混合技术栈,需要强隔离的老项目接入 |
实际选型决策:
- 如果团队全部使用 React,且需要频繁地在不同子应用间共享 UI 组件、工具函数,或者应用之间需要组件级复用,Webpack 5 MD Module Federation 是更现代、更高效的选择。
- 如果项目涉及多个异构技术栈,或需要将老旧 Angular.js / jQuery 项目平滑接入到新的 React 主应用中,qiankun 的沙箱机制和框架无关性会大大降低迁移风险。
- 也可以混合使用,比如用 qiankun 做整体路由编排,而在 React 子应用内部使用 Module Federation 进一步拆分,形成多层微前端架构。但要权衡架构复杂度。
27.2.4 微前端在 React 项目中的实战要点
1. 公共依赖治理
无论是哪种方案,都应尽量提取 react、react-dom、react-router 等核心库作为共享依赖,避免每个子应用都打包一份,造成体积浪费和版本冲突。
Module Federation 可以通过 shared 配置自动协商版本;qiankun 则需要手动将公共库挂载到 window(如 externals + CDN),或使用 publicPath 配合扩展插件。
2. 路由与导航
主应用负责顶层路由分发,子应用内部使用自己的路由。qiankun 推荐子应用使用 history 模式,但需注意主/子路由路径不要冲突。可以约定子应用路由以子应用名称为前缀。
3. 跨应用通信
轻量级通信可以直接用自定义事件(CustomEvent)或者主应用下发的回调。稍复杂的场景推荐使用简单的发布/订阅模式,或利用 qiankun 提供的 initGlobalState 实现全局 state 共享。
在 Module Federation 场景下,可以共享一个微型状态管理库(如 Zustand store 实例),或通过浏览器原生 CustomEvent 传递消息。
4. 样式管控
qiankun 默认未开启严格隔离时,子应用的样式可能相互污染。建议通过 CSS Modules、CSS-in-JS 或 BEM 命名约定来避免类名碰撞。开启 Shadow DOM 可实现完美隔离,但某些 UI 库可能不兼容(如弹出层会挂载到 document.body),需评估代价。
Module Federation 同样面临样式污染问题,可利用 CSS Modules 或 postcss 自动添加前缀。
5. 错误隔离与降级
单个子应用崩溃不应拖垮整个主应用。主应用需要为每个子应用包裹 ErrorBoundary,当远程模块加载失败或运行报错时展示降级 UI,保证主体功能可用。
27.2.5 典型落地架构示意
┌─────────────────────────────────┐
│ 主应用 (Host) │
│ ┌─────────────┐ ┌─────────────┐│
│ │ 导航 + 路由│ │ 全局状态库 ││
│ └─────────────┘ └─────────────┘│
│ ┌─────────────────────────────┐│
│ │ 子应用容器(#subapp-view)││
│ │ ││
│ │ ┌─────────┐ ┌─────────┐ ││
│ │ │用户模块 │ │订单模块 │ ││
│ │ │(React) │ │(Vue) │ ││
│ │ └─────────┘ └─────────┘ ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘
- 主应用使用 React 构建外壳、登录态、公共头部/侧边栏。
- 用户模块由 Team A 维护,独立仓库,独立部署,通过 Module Federation 暴露
UserProfile等组件。 - 订单模块由 Team B 维护,使用 Vue,通过 qiankun 接入,路由
/orders激活。 - 全局状态通过主应用下发用户信息、主题等,子应用通过共享 store 或 qiankun 通信 API 获取。
微前端不是银弹,它引入了更高的架构复杂度、性能监控难度和调试成本。只有在应用规模、团队规模、维护周期达到一定量级时,才值得投入。对于中小型项目或是单团队维护的系统,组件化、模块化拆分加 MonoRepo 管理通常是更轻量的解决方案。