微前端架构与 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 提供了最原生的模块共享方案,但需要根据团队和项目特点选择合适的微前端策略。
相关阅读: