正在加载,请稍候…

2026 年的模块联邦:使用 Webpack 和 Vite 构建微前端

从第一性原理学习模块联邦:主机/远程架构、共享依赖、动态加载、版本管理和独立部署流水线。

2026 年的模块联邦:使用 Webpack 和 Vite 构建微前端

2026 年的模块联邦:发生了什么变化

模块联邦(Module Federation)于 2020 年随 Webpack 5 引入,现已显著成熟。到 2026 年,它已不仅仅是 Webpack 的特性——生态系统包括原生 Vite 插件、框架无关的实现,以及经过数百个企业级部署的生产环境实战检验。

本指南涵盖从第一性原理到高级生产模式的模块联邦,包括使 Vite 用户也能使用它的 Vite Federation 插件。

2026 年的模块联邦:使用 Webpack 和 Vite 构建微前端插图

什么是模块联邦?

模块联邦允许 JavaScript 应用在运行时动态加载其他应用的代码。与传统的代码拆分(拆分你自己的代码)不同,联邦允许你在完全独立的部署之间共享代码。

核心模型:

  • 主机(Host):加载远程模块的壳应用
  • 远程(Remote):暴露模块的独立应用
  • 共享模块(Shared modules):共享的依赖(如 React),避免重复加载
浏览器加载壳应用(主机)
        |
        |-- 在运行时,发现远程 URL
        |
        |-- 加载 product-catalog.example.com/remoteEntry.js
        |       └── 提供:ProductCatalog、ProductCard 组件
        |
        |-- 加载 cart.example.com/remoteEntry.js
                └── 提供:CartWidget、useCart hook

每个远程都可以独立部署。对购物车服务的更改不需要重新部署壳应用或任何其他远程。

Webpack 5 模块联邦设置

// webpack.config.js — 主机应用
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',

      remotes: {
        // 格式:name@url/remoteEntry.js
        productCatalog: 'productCatalog@https://catalog.example.com/remoteEntry.js',
        cart: 'cart@https://cart.example.com/remoteEntry.js',
        userProfile: 'userProfile@https://profile.example.com/remoteEntry.js',
      },

      shared: {
        react: {
          singleton: true,        // 所有远程中仅有一个 React 实例
          requiredVersion: '^18.0.0',
          eager: true,            // 随主机加载,而非懒加载
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0',
          eager: true,
        },
        // 共享设计系统
        '@company/design-system': {
          singleton: true,
          requiredVersion: '^3.0.0',
        },
      },
    }),
  ],
}
// webpack.config.js — 远程应用(产品目录)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'productCatalog',
      filename: 'remoteEntry.js',

      exposes: {
        // 该远程向主机提供的内容
        './ProductCatalog': './src/components/ProductCatalog',
        './ProductCard': './src/components/ProductCard',
        './useProductSearch': './src/hooks/useProductSearch',
      },

      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        '@company/design-system': { singleton: true, requiredVersion: '^3.0.0' },
      },
    }),
  ],
}
// 在主机中——使用远程组件
// 这些组件在运行时从 catalog.example.com 动态加载

const ProductCatalog = React.lazy(() => import('productCatalog/ProductCatalog'))
const ProductCard    = React.lazy(() => import('productCatalog/ProductCard'))

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <ProductCatalog />
    </Suspense>
  )
}

Vite Federation 插件

@originjs/vite-plugin-federation 插件将模块联邦引入 Vite 项目:

npm install -D @originjs/vite-plugin-federation
// vite.config.ts — 主机
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'shell',
      remotes: {
        productCatalog: 'https://catalog.example.com/assets/remoteEntry.js',
        cart: 'https://cart.example.com/assets/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
  build: {
    modulePreload: false,
    target: 'esnext', // 模块联邦需要
    minify: false,    // 可选:便于调试
    cssCodeSplit: false,
  },
})
// vite.config.ts — 远程(产品目录)
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'productCatalog',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductCatalog': './src/components/ProductCatalog.tsx',
        './ProductCard': './src/components/ProductCard.tsx',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
  build: {
    target: 'esnext',
  },
})

2026 年的模块联邦:使用 Webpack 和 Vite 构建微前端插图

动态远程发现

在配置中硬编码远程 URL 不够灵活。动态发现提供了运行时灵活性:

// remoteRegistry.ts
interface RemoteConfig {
  name: string
  url: string
  version: string
}

class RemoteRegistry {
  private registry: Map<string, RemoteConfig> = new Map()

  async initialize() {
    // 从注册服务获取远程配置
    const response = await fetch('/api/micro-frontend-registry')
    const configs: RemoteConfig[] = await response.json()

    configs.forEach(config => {
      this.registry.set(config.name, config)
    })
  }

  async loadRemote(name: string, module: string) {
    const config = this.registry.get(name)
    if (!config) throw new Error(`Remote '${name}' not found in registry`)

    // 动态远程加载
    // @ts-ignore — __webpack_init_sharing__ 和 __webpack_share_scopes__ 由 MF 注入
    await __webpack_init_sharing__('default')

    const container = window[name as keyof Window] as any
    if (!container) {
      // 动态加载远程入口脚本
      await loadScript(config.url)
    }

    await container.init(__webpack_share_scopes__.default)
    const factory = await container.get(module)
    return factory()
  }
}

function loadScript(url: string): Promise<void> {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = () => resolve()
    script.onerror = () => reject(new Error(`Failed to load remote: ${url}`))
    document.head.appendChild(script)
  })
}

export const remoteRegistry = new RemoteRegistry()

跨微前端共享状态

微前端之间需要通信,但不能紧密耦合:

// shared-state/eventBus.ts — 作为共享模块发布
type EventMap = {
  'cart:item-added': { productId: string; quantity: number }
  'user:authenticated': { userId: string; token: string }
  'navigation:route-change': { path: string }
}

class EventBus {
  private listeners: Map<keyof EventMap, Set<Function>> = new Map()

  on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void) {
    if (!this.listeners.has(event)) this.listeners.set(event, new Set())
    this.listeners.get(event)!.add(handler)
    return () => this.listeners.get(event)!.delete(handler)
  }

  emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
    this.listeners.get(event)?.forEach(handler => handler(data))
  }
}

// 确保所有微前端中为单例
declare global {
  interface Window { __EVENT_BUS__?: EventBus }
}

export const eventBus = window.__EVENT_BUS__ ?? (window.__EVENT_BUS__ = new EventBus())

版本管理与兼容性

微前端最棘手的部分:当主机期望 React 18.2 但远程提供 React 18.3 时会发生什么?

// 严格版本锁定——最安全,但需要协调部署
shared: {
  react: {
    singleton: true,
    strictVersion: true,
    requiredVersion: '18.2.0', // 精确版本
  }
}

// 灵活版本范围——更独立的部署
shared: {
  react: {
    singleton: true,
    strictVersion: false,      // 版本不匹配时仅警告而非报错
    requiredVersion: '^18.0.0', // 任何 18.x 版本均可接受
  }
}

共享依赖策略

  • React、ReactDOM:始终单例,严格版本锁定
  • 设计系统:单例,semver 次版本灵活性
  • 工具库(lodash、date-fns):非单例,每个远程管理自己的版本

2026 年的模块联邦:使用 Webpack 和 Vite 构建微前端插图

独立部署流水线

# .github/workflows/deploy-catalog.yml
name: Deploy Product Catalog
on:
  push:
    branches: [main]
    paths:
      - 'apps/product-catalog/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: |
          cd apps/product-catalog
          npm ci
          npm run build

      - name: Integration test with host
        run: |
          # 启动指向新目录构建的主机应用
          HOST_URL=http://localhost:3000           CATALOG_REMOTE_URL=http://localhost:3001/assets/remoteEntry.js           npx playwright test tests/integration/catalog.spec.ts

      - name: Deploy to CDN
        run: |
          aws s3 sync apps/product-catalog/dist             s3://company-mfe-catalog/latest/             --cache-control "public, max-age=31536000"

          # 更新注册表以指向新版本
          curl -X PUT https://registry.example.com/api/remotes/productCatalog             -d '{"url": "https://cdn.example.com/catalog/latest/assets/remoteEntry.js"}'

何时使用模块联邦

在以下情况下使用带有模块联邦的微前端

  • 多个团队拥有同一应用的不同部分
  • 独立部署功能是业务需求
  • 应用的不同部分具有真正不同的技术栈或升级节奏
  • 应用足够大,以至于带有共享包的单一仓库不够用

在以下情况下不要使用

  • 单个团队拥有整个前端
  • 应用规模为中小型
  • 加载远程的网络延迟在你的用例中不可接受
  • 团队没有运行多个部署流水线的操作成熟度

实话实说:大多数应用不需要微前端。一个组织良好、拥有明确所有权和共享组件库的单一仓库可以用更少的复杂性解决相同的组织问题。当团队和部署需求真正要求时,才考虑使用模块联邦。

生产环境检查清单

在模块联邦上线之前:

  • 远程入口 URL 通过 CDN 提供,并带有适当的缓存头
  • 实现了远程加载失败时的回退行为
  • 每个远程组件都包裹了错误边界
  • 集成测试验证跨远程通信
  • 性能预算考虑了远程入口开销(每个约 5-15KB)
  • 版本注册表具有回滚能力
  • 监控对远程加载失败发出警报