跳轉到內容

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

微前端架構與 Module Federation 實戰

當單體前端應用膨脹到難以維護時,微前端架構是解決之道。Module Federation 作為 Webpack 5 的原生特性,讓多個獨立構建的應用在運行時共享模塊成為可能。本文將系統講解微前端架構設計、Module Federation 配置、樣式隔離、通信機制及生產部署。


一、微前端架構概覽

1.1 為什麼需要微前端

單體應用的問題:

  • 構建時間隨代碼量線性增長
  • 團隊協作衝突頻繁
  • 技術棧難以升級
  • 部署耦合,一個小改動需要全量發佈

微前端的優勢:

  • 獨立開發、獨立部署、獨立技術棧
  • 增量升級,漸進式重構
  • 團隊自治,降低協作成本
  • 故障隔離,一個子應用崩潰不影響全局

1.2 主流方案對比

方案原理優點缺點
Module FederationWebpack 5 運行時共享原生支持、細粒度共享依賴 Webpack 5
qiankunHTML 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 App

2.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 UserApp
typescript
// 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 提供了最原生的模塊共享方案,但需要根據團隊和項目特點選擇合適的微前端策略。


相關閱讀:

最後更新於: