微前端架构实战:Module Federation、qiankun 与自研方案的选型与落地
技术架构(更新于 2026年5月12日)
微前端:巨石应用的解药
当单仓库前端代码超过 50 万行、团队超过 5 个、构建超过 10 分钟——微前端是必然选择。
| 痛点 | 微前端的解法 |
|---|---|
| 构建慢 | 子应用独立构建,增量部署 |
| 团队冲突 | 子应用独立仓库,独立发布 |
| 技术栈锁定 | 每个子应用可选技术栈 |
| 耦合严重 | 共享通过契约,而非直接引用 |
一、方案对比
| 方案 | 隔离性 | 通信 | 性能 | 复杂度 | 技术栈 |
|---|---|---|---|---|---|
| iframe | 完美 | postMessage | 差 | 低 | 任意 |
| qiankun (JS 沙箱) | 好 | props/eventBus | 好 | 中 | 任意 |
| Module Federation | 无 | 共享模块 | 优 | 中 | Webpack 5+ |
| single-spa | 好 | 自定义 | 好 | 高 | 任意 |
| EMP | 无 | 共享模块 | 优 | 中 | Webpack 5 |
二、iframe 方案:简单但受限
实现
<iframe
src="https://sub-app.example.com"
sandbox="allow-scripts allow-same-origin allow-forms"
style="width: 100%; height: 100%; border: none;"
></iframe>
优势与局限
| 优势 | 局限 |
|---|---|
| 完美 CSS/JS 隔离 | 性能差(每个 iframe 独立进程) |
| 天然安全 | URL 不同步,刷新丢失状态 |
| 零改造成本 | 弹窗/遮罩层被 iframe 裁剪 |
| 任意技术栈 | 通信只能用 postMessage |
适用场景:第三方嵌入、低交互需求、安全隔离优先。
三、qiankun:阿里出品的 JS 沙箱方案
主应用配置
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app-order',
entry: '//localhost:8081',
container: '#subapp-container',
activeRule: '/order',
props: { mainStore, userInfo },
},
{
name: 'app-user',
entry: '//localhost:8082',
container: '#subapp-container',
activeRule: '/user',
props: { mainStore, userInfo },
},
]);
start({
prefetch: 'all', // 预加载
sandbox: { strictStyleIsolation: true }, // 严格样式隔离
singular: false, // 允许多个子应用同时挂载
});
子应用改造
// 子应用入口
let root = null;
function render(props) {
const { container, mainStore } = props;
root = createApp(App);
root.mount(container ? container.querySelector('#app') : '#app');
}
// 导出生命周期
export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() { root.unmount(); }
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
JS 沙箱原理
qiankun 使用 Proxy 拦截 window 操作:
const fakeWindow = new Proxy(window, {
get(target, key) {
// 优先从沙箱取值
if (sandbox.hasOwnProperty(key)) return sandbox[key];
return target[key];
},
set(target, key, value) {
// 写入沙箱而非真实 window
sandbox[key] = value;
return true;
},
});
样式隔离
| 模式 | 实现 | 局限 |
|---|---|---|
strictStyleIsolation |
Shadow DOM | 弹窗样式丢失、全局样式不穿透 |
experimentalStyleIsolation |
CSS Scope 前缀 | 动态插入的样式不处理 |
四、Module Federation:Webpack 5 的运行时模块共享
基本概念
App A (Host) ──远程加载──→ App B (Remote) 的模块
↑ ↑
共享依赖 共享依赖
(react, lodash...) (react, lodash...)
Remote 配置(提供模块)
// webpack.config.js (App B - Remote)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'appOrder',
filename: 'remoteEntry.js',
exposes: {
'./OrderList': './src/components/OrderList',
'./OrderDetail': './src/pages/OrderDetail',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
Host 配置(消费模块)
// webpack.config.js (App A - Host)
new ModuleFederationPlugin({
name: 'appMain',
remotes: {
appOrder: 'appOrder@http://localhost:8081/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
})
动态加载远程组件
const OrderList = React.lazy(() => import('appOrder/OrderList'));
function App() {
return (
<Suspense fallback={<Loading />}>
<OrderList />
</Suspense>
);
}
Module Federation 2.0 (2026)
| 新特性 | 说明 |
|---|---|
| 原生 ESM 支持 | 不再依赖 Webpack runtime |
| Rspack 兼容 | 构建速度 5-10x 提升 |
| Vite 插件 | @originjs/vite-plugin-federation |
| 类型安全 | 远程模块类型自动推导 |
| 运行时动态注册 | 无需构建时确定 remote URL |
五、通信方案
方案一:CustomEvent
// 子应用发布
window.dispatchEvent(new CustomEvent('order:created', {
detail: { orderId: '12345' },
}));
// 主应用订阅
window.addEventListener('order:created', (e) => {
console.log('新订单:', e.detail);
});
方案二:共享状态(Micro Store)
class MicroStore {
state = {};
listeners = new Map();
setState(key, value) {
this.state[key] = value;
this.listeners.get(key)?.forEach(fn => fn(value));
}
subscribe(key, listener) {
if (!this.listeners.has(key)) this.listeners.set(key, new Set());
this.listeners.get(key).add(listener);
return () => this.listeners.get(key).delete(listener);
}
}
// 主应用创建,通过 props 传递给子应用
const store = new MicroStore();
方案三:URL 同步
// 子应用通过 URL 参数通信
const params = new URLSearchParams(location.search);
const token = params.get('token');
// 主应用监听路由变化
router.afterEach((to) => {
// 通知子应用路由变化
store.setState('route', to.path);
});
六、公共依赖管理
| 策略 | 实现 | 优劣势 |
|---|---|---|
| externals + CDN | 所有公共库走 CDN | 简单但版本锁定 |
| Module Federation shared | 运行时版本协商 | 灵活但配置复杂 |
| Monorepo 共享包 | pnpm workspace | 强一致但耦合 |
| NPM 私有包 | 发布到私有 registry | 解耦但需发布流程 |
Module Federation shared 配置最佳实践
shared: {
react: {
singleton: true, // 只加载一个版本
strictVersion: true, // 版本不匹配时报错
requiredVersion: deps.react,
eager: false, // 异步加载
},
lodash: {
singleton: false, // 允许多版本
requiredVersion: deps.lodash,
},
}
七、部署策略
独立部署
CDN
├── /main/ → 主应用 (hash: a1b2c3)
├── /app-order/ → 订单子应用 (hash: d4e5f6)
├── /app-user/ → 用户子应用 (hash: g7h8i9)
└── /shared/ → 共享资源 (react, lodash...)
版本管理
// remote-config.json(主应用动态加载)
{
"apps": {
"app-order": {
"url": "https://cdn.example.com/app-order/d4e5f6/remoteEntry.js",
"version": "2.3.1"
},
"app-user": {
"url": "https://cdn.example.com/app-user/g7h8i9/remoteEntry.js",
"version": "1.8.0"
}
}
}
主应用启动时拉取此配置,实现子应用独立部署、灰度发布。
八、选型决策树
需要完美隔离?
├── 是 → iframe
└── 否 → 同一技术栈?
├── 是 → Module Federation
└── 否 → 需要沙箱隔离?
├── 是 → qiankun
└── 否 → single-spa / 自研
2026 年推荐:新项目优先考虑 Module Federation 2.0 + Rspack,兼顾性能与开发体验。需要强隔离再考虑 qiankun。
#微前端#Module Federation#qiankun#架构