微前端架構與 Module Federation 實戰 2026 | 大型前端應用拆分完全指南

當單體前端應用膨脹到難以維護時,微前端架構是解決之道。Module Federation 作為 Webpack 5 的原生特性,讓多個獨立構建的應用在運行時共享模塊成為可能。本文將系統講解微前端架構設計、Module Federation 配置、樣式隔離、通信機制及生產部署。
一、微前端架構概覽
1.1 為什麼需要微前端
單體應用的問題:
- 構建時間隨代碼量線性增長
- 團隊協作衝突頻繁
- 技術棧難以升級
- 部署耦合,一個小改動需要全量發佈
微前端的優勢:
- 獨立開發、獨立部署、獨立技術棧
- 增量升級,漸進式重構
- 團隊自治,降低協作成本
- 故障隔離,一個子應用崩潰不影響全局
1.2 主流方案對比
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| Module Federation | Webpack 5 運行時共享 | 原生支持、細粒度共享 | 依賴 Webpack 5 |
| qiankun | HTML Entry + JS 沙箱 | 框架無關、生態成熟 | 沙箱有性能開銷 |
| iframe | 瀏覽器原生隔離 | 完全隔離 | 體驗差、通信複雜 |
| Web Components | 自定義元素 | 標準化 | 生態不成熟 |
| NPM 包 | 構建時集成 | 簡單 | 無法獨立部署 |
1.3 Module Federation 核心概念
┌─────────────────────────────────────────────────────────┐
│ Host (Shell) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Remote A │ │ Remote B │ │ Remote C │ │
│ │ (用戶中心) │ │ (訂單系統) │ │ (報表系統) │ │
│ │ │ │ │ │ │ │
│ │ expose: │ │ expose: │ │ expose: │ │
│ │ ./UserApp │ │ ./OrderApp │ │ ./ReportApp│ │
│ │ │ │ │ │ │ │
│ │ shared: │ │ shared: │ │ shared: │ │
│ │ react │ │ react │ │ react │ │
│ │ vue │ │ vue │ │ vue │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Host 通過 remoteEntries 加載子應用 │
│ shared 依賴在運行時自動去重 │
└─────────────────────────────────────────────────────────┘二、Module Federation 實戰
2.1 Host(主應用)配置
typescript
// webpack.config.ts (Host)
import { ModuleFederationPlugin } from 'webpack/container'
import { defineConfig } from 'webpack-cli'
export default defineConfig({
entry: './src/bootstrap.ts',
output: {
publicPath: 'auto', // 自動推斷公共路徑
},
plugins: [
new ModuleFederationPlugin({
// 主應用名稱
name: 'host',
// 引用的遠程應用
remotes: {
userApp: 'userApp@https://user.example.com/remoteEntry.js',
orderApp: 'orderApp@https://order.example.com/remoteEntry.js',
reportApp: 'reportApp@https://report.example.com/remoteEntry.js',
},
// 共享依賴
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
'react-router-dom': { singleton: true },
vue: { singleton: true, requiredVersion: '^3.4.0' },
pinia: { singleton: true },
},
}),
],
})typescript
// src/bootstrap.ts — 異步入口(必須)
import('./app')typescript
// src/app.tsx
import React, { Suspense, lazy } from 'react'
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
// 懶加載遠程應用
const UserApp = lazy(() => import('userApp/UserApp'))
const OrderApp = lazy(() => import('orderApp/OrderApp'))
const ReportApp = lazy(() => import('reportApp/ReportApp'))
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首頁</Link>
<Link to="/users">用戶中心</Link>
<Link to="/orders">訂單系統</Link>
<Link to="/reports">報表系統</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/*" element={<UserApp />} />
<Route path="/orders/*" element={<OrderApp />} />
<Route path="/reports/*" element={<ReportApp />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
function Home() {
return <h1>Host Application</h1>
}
export default App2.2 Remote(子應用)配置
typescript
// webpack.config.ts (Remote - User App)
import { ModuleFederationPlugin } from 'webpack/container'
export default defineConfig({
entry: './src/bootstrap.ts',
output: {
publicPath: 'auto',
},
plugins: [
new ModuleFederationPlugin({
name: 'userApp',
// 暴露的模塊
exposes: {
'./UserApp': './src/UserApp',
'./UserProfile': './src/components/UserProfile',
'./useAuth': './src/hooks/useAuth',
},
// 共享依賴
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
'react-router-dom': { singleton: true },
},
}),
],
})typescript
// src/UserApp.tsx (Remote - 暴露的組件)
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import UserList from './pages/UserList'
import UserDetail from './pages/UserDetail'
const UserApp = () => {
return (
<Routes>
<Route path="/" element={<UserList />} />
<Route path="/:id" element={<UserDetail />} />
</Routes>
)
}
export default UserApptypescript
// src/bootstrap.ts
import('./index')typescript
// src/index.tsx — 獨立運行入口
import React from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import UserApp from './UserApp'
const root = createRoot(document.getElementById('root')!)
root.render(
<BrowserRouter>
<UserApp />
</BrowserRouter>
)2.3 動態加載遠程應用
typescript
// 動態註冊遠程應用
const dynamicRemotes = {
userApp: 'userApp@https://user.example.com/remoteEntry.js',
orderApp: 'orderApp@https://order.example.com/remoteEntry.js',
}
// 動態注入 remote
function injectRemote(name: string, url: string): Promise<void> {
return new Promise((resolve, reject) => {
if ((window as any)[name]) {
resolve()
return
}
const script = document.createElement('script')
script.src = url
script.onload = () => resolve()
script.onerror = () => reject(new Error(`Failed to load ${url}`))
document.head.appendChild(script)
})
}
// 使用
async function loadUserApp() {
await injectRemote('userApp', dynamicRemotes.userApp)
const module = await (window as any).userApp.get('./UserApp')
return module()
}三、Vite + Module Federation
3.1 使用 vite-plugin-federation
typescript
// vite.config.ts (Host)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
react(),
federation({
name: 'host',
remotes: {
userApp: 'https://user.example.com/assets/remoteEntry.js',
orderApp: 'https://order.example.com/assets/remoteEntry.js',
},
shared: ['react', 'react-dom', 'react-router-dom'],
}),
],
build: {
target: 'esnext',
minify: 'esbuild',
cssCodeSplit: true,
},
})typescript
// vite.config.ts (Remote)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
react(),
federation({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserApp': './src/UserApp',
},
shared: ['react', 'react-dom'],
}),
],
build: {
target: 'esnext',
minify: 'esbuild',
},
})3.2 Vue 3 集成
typescript
// vite.config.ts (Vue Host)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
vue(),
federation({
name: 'host',
remotes: {
dashboardApp: 'https://dashboard.example.com/assets/remoteEntry.js',
settingsApp: 'https://settings.example.com/assets/remoteEntry.js',
},
shared: ['vue', 'vue-router', 'pinia'],
}),
],
build: {
target: 'esnext',
},
})typescript
// src/router.ts (Host)
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/dashboard',
component: () => import('dashboardApp/DashboardApp'),
},
{
path: '/settings',
component: () => import('settingsApp/SettingsApp'),
},
]
export const router = createRouter({
history: createWebHistory(),
routes,
})四、樣式隔離
4.1 CSS Modules
typescript
// 子應用使用 CSS Modules 避免樣式衝突
// UserApp.module.css
.container {
padding: 20px;
}
.title {
color: #333;
}typescript
// UserApp.tsx
import styles from './UserApp.module.css'
function UserApp() {
return (
<div className={styles.container}>
<h1 className={styles.title}>User App</h1>
</div>
)
}4.2 Shadow DOM 隔離
typescript
// 使用 Shadow DOM 完全隔離樣式
class MicroApp extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
:host { display: block; }
.container { padding: 20px; }
/* 所有樣式都被隔離 */
</style>
<div class="container">
<div id="root"></div>
</div>
`
// 在 Shadow DOM 內掛載子應用
const root = shadow.getElementById('root')!
createRoot(root).render(<UserApp />)
}
}
customElements.define('micro-user-app', MicroApp)4.3 CSS 前綴自動化
typescript
// PostCSS 配置:自動添加前綴
// .postcssrc.js
module.exports = {
plugins: [
require('postcss-prefix-selector')({
prefix: '[data-micro-app="user"]',
transform(prefix, selector) {
if (selector.startsWith('html') || selector.startsWith('body')) {
return selector
}
return `${prefix} ${selector}`
}
})
]
}
// 使用
// <div data-micro-app="user">
// <UserApp />
// </div>4.4 Tailwind CSS 隔離
typescript
// tailwind.config.js (子應用)
module.exports = {
// 使用 important 選擇器隔離
important: '[data-micro-app="user"]',
content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}五、應用間通信
5.1 自定義事件
typescript
// shared/eventBus.ts
type EventHandler = (data: any) => void
class EventBus {
private handlers = new Map<string, Set<EventHandler>>()
on(event: string, handler: EventHandler): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set())
}
this.handlers.get(event)!.add(handler)
// 返回取消訂閱函數
return () => this.off(event, handler)
}
off(event: string, handler: EventHandler): void {
this.handlers.get(event)?.delete(handler)
}
emit(event: string, data: any): void {
this.handlers.get(event)?.forEach(handler => {
try {
handler(data)
} catch (err) {
console.error(`Event handler error [${event}]:`, err)
}
})
}
}
export const eventBus = new EventBus()
// Host 發送事件
eventBus.emit('user:login', { userId: '123', name: 'Alice' })
// Remote 監聽事件
const unsubscribe = eventBus.on('user:login', (data) => {
console.log('User logged in:', data)
})
// 組件卸載時取消訂閱
onUnmounted(unsubscribe)5.2 共享狀態
typescript
// 共享 Pinia Store
// shared/stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref<{ id: string; name: string } | null>(null)
const token = ref<string>('')
const isLoggedIn = computed(() => !!user.value)
function setUser(userData: { id: string; name: string }) {
user.value = userData
}
function setToken(t: string) {
token.value = t
}
function logout() {
user.value = null
token.value = ''
}
return { user, token, isLoggedIn, setUser, setToken, logout }
})typescript
// Host 中初始化 Pinia
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
// Remote 中使用同一個 Pinia 實例
// 因為 shared 中已經 singleton,所以會複用
import { useAuthStore } from 'shared/stores/auth'
const authStore = useAuthStore()
console.log(authStore.user) // 從 Host 共享的狀態5.3 Props 傳遞
typescript
// Host 傳遞 props 給 Remote
const UserApp = lazy(() => import('userApp/UserApp'))
function App() {
return (
<UserApp
theme="dark"
locale="zh-CN"
onNavigate={(path) => navigate(path)}
sharedData={{ userId: '123', permissions: ['read', 'write'] }}
/>
)
}
// Remote 接收 props
function UserApp({ theme, locale, onNavigate, sharedData }: UserAppProps) {
return (
<div className={theme === 'dark' ? 'dark-theme' : ''}>
<button onClick={() => onNavigate('/orders')}>
查看訂單
</button>
</div>
)
}六、路由集成
6.1 React Router 集成
typescript
// Host 路由配置
import { BrowserRouter, Routes, Route } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
{/* 首頁 */}
<Route index element={<Home />} />
{/* 子應用路由 */}
<Route path="/users/*" element={
<Suspense fallback={<Loading />}>
<UserApp />
</Suspense>
} />
<Route path="/orders/*" element={
<Suspense fallback={<Loading />}>
<OrderApp />
</Suspense>
} />
</Route>
</Routes>
</BrowserRouter>
)
}typescript
// Remote 路由配置(使用相對路徑)
import { Routes, Route } from 'react-router-dom'
function UserApp() {
return (
<Routes>
{/* 使用相對路徑,自動拼接 /users */}
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
<Route path="create" element={<UserCreate />} />
<Route path="settings" element={<UserSettings />} />
</Routes>
)
}6.2 Vue Router 集成
typescript
// Host 路由
const routes = [
{
path: '/dashboard',
component: () => import('dashboardApp/DashboardApp'),
children: [
// 子應用內部路由由子應用自己管理
]
}
]typescript
// Remote 路由(DashboardApp 內部)
const routes = [
{ path: '', component: DashboardHome },
{ path: 'analytics', component: Analytics },
{ path: 'reports', component: Reports },
]
const router = createRouter({
history: createWebHistory('/dashboard'),
routes,
})七、共享依賴策略
7.1 單例共享
typescript
// webpack.config.ts
new ModuleFederationPlugin({
shared: {
// 單例:整個應用只加載一個版本
react: {
singleton: true,
requiredVersion: '^18.0.0',
eager: false, // 不在初始包中
},
// 嚴格版本匹配
'react-dom': {
singleton: true,
requiredVersion: '18.2.0',
strictVersion: true,
},
// 允許降級
lodash: {
singleton: false, // 允許多版本
requiredVersion: '^4.17.0',
},
},
})7.2 共享工具庫
typescript
// 創建共享 UI 組件庫
// shared-ui/webpack.config.ts
new ModuleFederationPlugin({
name: 'sharedUI',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Modal': './src/components/Modal',
'./Input': './src/components/Input',
'./useToast': './src/hooks/useToast',
'./utils': './src/utils',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
})
// 各子應用引用
new ModuleFederationPlugin({
remotes: {
sharedUI: 'sharedUI@https://shared.example.com/remoteEntry.js',
},
})
// 使用
import Button from 'sharedUI/Button'
import { useToast } from 'sharedUI/useToast'八、部署策略
8.1 獨立部署
CDN / 靜態服務器
├── host.example.com/ # 主應用
│ ├── index.html
│ └── assets/
├── user.example.com/ # 用戶中心
│ ├── remoteEntry.js
│ └── assets/
├── order.example.com/ # 訂單系統
│ ├── remoteEntry.js
│ └── assets/
└── shared.example.com/ # 共享庫
├── remoteEntry.js
└── assets/8.2 版本管理
typescript
// 通過配置文件管理遠程應用版本
const remoteConfig = {
remotes: {
userApp: process.env.NODE_ENV === 'production'
? 'userApp@https://user.example.com/v2.1.0/remoteEntry.js'
: 'userApp@https://user-staging.example.com/v2.1.0-beta/remoteEntry.js',
}
}
// 動態版本加載
async function loadRemoteWithVersion(name: string, version: string) {
const url = `https://${name}.example.com/${version}/remoteEntry.js`
await injectScript(url)
return (window as any)[name]
}8.3 回滾策略
yaml
# .github/workflows/deploy-remote.yml
name: Deploy Remote App
on:
push:
tags: ['user-app-v*']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: pnpm build
- name: Upload to CDN with version
run: |
VERSION=${GITHUB_REF#refs/tags/user-app-v}
aws s3 sync dist/ s3://cdn-bucket/user-app/${VERSION}/
- name: Update latest pointer
run: |
aws s3 sync dist/ s3://cdn-bucket/user-app/latest/
- name: Rollback on failure
if: failure()
run: |
aws s3 sync s3://cdn-bucket/user-app/previous/ s3://cdn-bucket/user-app/latest/九、性能優化
9.1 預加載遠程應用
typescript
// 在頁面空閒時預加載子應用
function prefetchRemote(remoteName: string, url: string) {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = url
link.as = 'script'
document.head.appendChild(link)
})
}
}
// 使用
prefetchRemote('userApp', 'https://user.example.com/remoteEntry.js')
prefetchRemote('orderApp', 'https://order.example.com/remoteEntry.js')9.2 共享依賴預加載
typescript
// 預加載共享依賴
const link = document.createElement('link')
link.rel = 'modulepreload'
link.href = 'https://cdn.example.com/react.production.min.js'
document.head.appendChild(link)9.3 錯誤邊界與降級
typescript
import { Component, ReactNode } from 'react'
interface State {
hasError: boolean
error?: Error
}
class RemoteErrorBoundary extends Component<{ children: ReactNode }, State> {
state: State = { hasError: false }
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: any) {
console.error('Remote app error:', error, info)
// 上報到監控系統
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>子應用加載失敗</h2>
<button onClick={() => window.location.reload()}>
重新加載
</button>
</div>
)
}
return this.props.children
}
}
// 使用
<RemoteErrorBoundary>
<Suspense fallback={<Loading />}>
<UserApp />
</Suspense>
</RemoteErrorBoundary>十、總結
- ✅ 微前端架構模式與方案對比(MF、qiankun、iframe、Web Components)
- ✅ Module Federation 核心概念(Host、Remote、exposes、shared)
- ✅ Webpack 5 MF 完整配置(主應用 + 子應用)
- ✅ Vite + vite-plugin-federation 實戰
- ✅ Vue 3 / React 路由集成
- ✅ 樣式隔離(CSS Modules、Shadow DOM、前綴自動化、Tailwind 隔離)
- ✅ 應用間通信(事件總線、共享狀態、Props 傳遞)
- ✅ 共享依賴策略(單例共享、共享 UI 庫)
- ✅ 部署策略(獨立部署、版本管理、回滾方案)
- ✅ 性能優化(預加載、錯誤邊界、降級方案)
微前端是大型前端應用架構演進的重要方向,Module Federation 提供了最原生的模塊共享方案,但需要根據團隊和項目特點選擇合適的微前端策略。
相關閱讀: