Skip to content

第16章 微前端的 DevOps 与工程化

"微前端的真正挑战不在拆分代码——在于让十个团队同时向生产环境交付,且互不干扰。"

本章要点

  • 设计独立构建、独立部署的 CI/CD 管线,实现子应用从提交到上线的全自动化流水线
  • 理解语义化版本在微前端中的特殊挑战,掌握兼容性矩阵与版本协商机制
  • 构建跨应用的监控与可观测性体系,快速定位"到底是谁的子应用出了问题"
  • 在微前端架构下实现灰度发布与 A/B 测试,做到子应用级别的精细化流量控制

凌晨两点四十五分,你被一条告警惊醒。

生产环境的错误率在过去十分钟内飙升了 300%。你打开 Grafana 面板,看到订单子应用的 JS 报错量从每分钟 3 次跳到了每分钟 120 次。你的第一反应是回滚——但回滚哪个?主应用半小时前刚部署了一个导航栏优化,商品子应用两小时前推了一版新的详情页,而订单子应用本身三天没有发布过。

错误堆栈指向一个 TypeError: Cannot read properties of undefined (reading 'formatPrice'),发生在订单子应用调用共享组件库的 @shared/utils 包中。你翻看提交记录,发现商品子应用两小时前的部署附带升级了共享组件库的版本,将 formatPrice 的函数签名从 formatPrice(value: number) 改成了 formatPrice(value: number, options?: FormatOptions)——本身是向后兼容的改动。但问题在于,Module Federation 的运行时版本协商将订单子应用拉到了新版本的共享库,而这个新版本内部重构了模块导出结构,formatPrice 从默认导出变成了具名导出。

三个子应用、三次独立部署、一个共享依赖、一次无意的破坏性变更——这就是微前端 DevOps 的真实战场。

这一章,我们不谈理论模型。我们谈的是:如何设计一套工程化体系,让上面这种事故不可能发生——或者至少,当它发生时,你能在 30 秒内定位原因、60 秒内完成回滚。

下图展示了微前端 CI/CD 管线的完整架构,从代码提交到生产部署:

16.1 独立构建 + 独立部署的 CI/CD 管线设计

微前端的核心承诺之一是独立部署。但"独立部署"远不是"每个子应用一个 Git 仓库、各跑各的 CI"这么简单。独立部署的真正挑战在于:如何在保证独立性的同时,维护全局一致性。

16.1.1 仓库策略:Monorepo vs Polyrepo

在设计 CI/CD 之前,必须先回答一个前置问题:代码怎么组织?

方案一:Polyrepo(多仓库)
├── repo: main-app          # 主应用
├── repo: order-app          # 订单子应用
├── repo: product-app        # 商品子应用
├── repo: user-app           # 用户子应用
└── repo: shared-libs        # 共享库

方案二:Monorepo(单仓库)
repo: micro-frontend-platform
├── apps/
│   ├── main/                # 主应用
│   ├── order/               # 订单子应用
│   ├── product/             # 商品子应用
│   └── user/                # 用户子应用
├── packages/
│   ├── shared-utils/        # 共享工具库
│   ├── shared-components/   # 共享组件库
│   └── shared-types/        # 共享类型定义
└── turbo.json / nx.json     # 构建编排

两种策略各有利弊,但在实践中,Monorepo + 独立部署管线正在成为微前端团队的主流选择,原因有三:

  1. 原子性变更:修改共享库和使用方可以在同一个 PR 中完成,CI 自动验证兼容性
  2. 统一工具链:ESLint、TypeScript、构建配置在顶层统一管理,避免各子应用配置漂移
  3. 依赖可见性:在 Monorepo 中,谁依赖了什么、哪个版本、有没有冲突——一目了然

关键在于:Monorepo 不等于 Monobuild。代码在一起管理,但构建和部署是独立的。

下图对比了 Polyrepo 和 Monorepo 两种仓库策略在微前端场景下的工作流差异:

16.1.2 基于变更检测的增量构建

Monorepo 下的核心问题是:订单子应用改了一行代码,不应该触发商品子应用的构建。这需要变更检测

yaml
# .github/workflows/ci.yml — GitHub Actions 实现
name: Micro Frontend CI/CD

on:
  push:
    branches: [main, 'release/**']
  pull_request:
    branches: [main]

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      main-app: ${{ steps.changes.outputs.main-app }}
      order-app: ${{ steps.changes.outputs.order-app }}
      product-app: ${{ steps.changes.outputs.product-app }}
      user-app: ${{ steps.changes.outputs.user-app }}
      shared-libs: ${{ steps.changes.outputs.shared-libs }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            main-app:
              - 'apps/main/**'
              - 'packages/shared-types/**'
            order-app:
              - 'apps/order/**'
              - 'packages/shared-utils/**'
              - 'packages/shared-components/**'
            product-app:
              - 'apps/product/**'
              - 'packages/shared-utils/**'
              - 'packages/shared-components/**'
            user-app:
              - 'apps/user/**'
              - 'packages/shared-utils/**'
            shared-libs:
              - 'packages/**'

  build-order-app:
    needs: detect-changes
    if: needs.detect-changes.outputs.order-app == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm --filter order-app build
      - run: pnpm --filter order-app test
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: order-app-dist
          path: apps/order/dist/
          retention-days: 7

  # build-product-app, build-user-app 结构类似,此处省略

  deploy-order-app:
    needs: [build-order-app]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: order-app-dist
          path: dist/
      - name: Deploy to CDN
        run: |
          # 带版本号的部署路径,支持回滚
          VERSION=$(cat dist/version.json | jq -r '.version')
          DEPLOY_PATH="micro-apps/order/${VERSION}"

          aws s3 sync dist/ "s3://${CDN_BUCKET}/${DEPLOY_PATH}" \
            --cache-control "public, max-age=31536000, immutable"

          # 更新版本映射表(关键!)
          echo "{\"version\": \"${VERSION}\", \"path\": \"${DEPLOY_PATH}\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" \
            > /tmp/manifest.json
          aws s3 cp /tmp/manifest.json \
            "s3://${CDN_BUCKET}/micro-apps/order/latest.json" \
            --cache-control "no-cache, no-store, must-revalidate"
        env:
          CDN_BUCKET: ${{ secrets.CDN_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

基于 VitePress 构建