跳转到内容

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


相关阅读: