微前端架構實戰: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#架构