前端框架 精选推荐

前端 CI/CD 流程自动化:从代码提交到生产部署的完整指南

HTMLPAGE 团队
14 分钟阅读

系统讲解前端项目的持续集成与持续部署,涵盖 GitHub Actions、自动化测试、构建优化、多环境部署等核心环节,帮助团队构建高效的交付流水线。

#CI/CD #GitHub Actions #自动化部署 #DevOps #前端工程化

前端 CI/CD 流程自动化:从代码提交到生产部署的完整指南

"代码写完了,怎么上线?" 这个问题的答案,区分了业余项目和专业工程。一套成熟的 CI/CD 流程,能让团队从繁琐的手动操作中解放出来,专注于创造价值。

为什么前端需要 CI/CD

没有 CI/CD 的日常

周五下午 5:30
产品经理: "这个 Bug 修好了吗?能发版吗?"
开发者: "修好了,我本地测过了"

周五下午 5:45
开发者手动执行: npm run build
开发者: "构建完成,我传到服务器上..."

周五下午 6:00
线上出现白屏
开发者: "???我本地明明是好的啊"

这个场景你一定不陌生。问题出在哪?

  1. "我本地是好的":本地环境与生产环境不一致
  2. 手动构建:容易遗漏步骤,比如忘记执行 lint
  3. 直接发版:没有经过自动化测试验证
  4. 单点故障:只有一个人知道怎么发版

有了 CI/CD 的日常

周五下午 5:30
开发者合并 PR

CI 自动执行:
✓ ESLint 检查
✓ TypeScript 类型检查
✓ 单元测试 (覆盖率 85%)
✓ E2E 测试
✓ 构建产物
✓ 部署到预发环境
✓ 自动通知产品经理验收

验收通过后,一键发布生产环境

CI/CD 流水线设计

一条完整的前端 CI/CD 流水线通常包含以下阶段:

┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
│  代码   │ → │  检查   │ → │  测试   │ → │  构建   │ → │  部署   │
│  提交   │   │  Lint   │   │  Test   │   │  Build  │   │  Deploy │
└─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘
     │             │             │             │             │
     │             │             │             │             │
   Commit      ESLint        Unit          Bundle        Staging
   Push        TSC           E2E           Optimize      Production
               Prettier      Coverage      Assets

GitHub Actions 实战

GitHub Actions 是目前最流行的 CI/CD 工具之一,与 GitHub 无缝集成,配置简单但功能强大。

基础配置

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

# 设置并发策略,避免同一分支重复运行
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint & Type Check
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run ESLint
        run: pnpm lint

      - name: Run TypeScript
        run: pnpm type-check

  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    needs: lint # 依赖 lint job
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Run tests with coverage
        run: pnpm test:coverage
      
      - name: Upload coverage report
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
          fail_ci_if_error: true

  e2e:
    name: E2E Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Install Playwright
        run: pnpm exec playwright install --with-deps
      
      - name: Build application
        run: pnpm build
      
      - name: Run E2E tests
        run: pnpm test:e2e
      
      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [test, e2e]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Build
        run: pnpm build
        env:
          NODE_ENV: production
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

多环境部署配置

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches:
      - main      # 生产环境
      - develop   # 开发环境
  workflow_dispatch: # 支持手动触发
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

jobs:
  deploy-staging:
    name: Deploy to Staging
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Build for staging
        run: pnpm build
        env:
          VITE_API_URL: ${{ vars.STAGING_API_URL }}
          VITE_ENV: staging
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          working-directory: ./

  deploy-production:
    name: Deploy to Production
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Build for production
        run: pnpm build
        env:
          VITE_API_URL: ${{ vars.PRODUCTION_API_URL }}
          VITE_ENV: production
      
      - name: Deploy to CDN
        run: |
          # 上传静态资源到 OSS/S3
          aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}/ \
            --cache-control "public, max-age=31536000, immutable" \
            --exclude "*.html"
          
          # HTML 文件使用较短的缓存时间
          aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}/ \
            --cache-control "public, max-age=0, must-revalidate" \
            --include "*.html"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ap-northeast-1
      
      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

缓存优化

CI/CD 流水线中,依赖安装往往是最耗时的环节。合理的缓存策略能大幅提升构建速度。

依赖缓存

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js with cache
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'  # 自动缓存 pnpm store
      
      # 或者手动配置缓存
      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

构建缓存

- name: Cache Next.js build
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      ${{ github.workspace }}/.next/cache
    key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
    restore-keys: |
      ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
      ${{ runner.os }}-nextjs-

- name: Cache Turbo
  uses: actions/cache@v4
  with:
    path: .turbo
    key: ${{ runner.os }}-turbo-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-turbo-

分支策略与保护规则

Git Flow 分支模型

main (生产环境)
  │
  ├── develop (开发环境)
  │     │
  │     ├── feature/user-auth
  │     ├── feature/payment
  │     └── feature/dashboard
  │
  ├── release/v1.2.0 (预发布)
  │
  └── hotfix/critical-bug (紧急修复)

分支保护规则配置

# .github/branch-protection.yml (需要配合 GitHub App)
branches:
  - name: main
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 2
        dismiss_stale_reviews: true
        require_code_owner_reviews: true
      required_status_checks:
        strict: true
        contexts:
          - "lint"
          - "test"
          - "e2e"
          - "build"
      enforce_admins: true
      required_linear_history: true
      allow_force_pushes: false
      allow_deletions: false
  
  - name: develop
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
      required_status_checks:
        strict: true
        contexts:
          - "lint"
          - "test"

自动化版本管理

使用 Changesets 或 semantic-release 自动管理版本号和 CHANGELOG。

Changesets 配置

# .github/workflows/release.yml
name: Release

on:
  push:
    branches:
      - main

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 获取完整历史
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      - name: Create Release Pull Request or Publish
        id: changesets
        uses: changesets/action@v1
        with:
          publish: pnpm release
          version: pnpm version-packages
          commit: 'chore: release packages'
          title: 'chore: release packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Changeset 配置文件

// .changeset/config.json
{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "your-org/your-repo" }
  ],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

通知与监控

Slack 通知

- name: Notify Slack on success
  if: success()
  uses: slackapi/slack-github-action@v1.24.0
  with:
    payload: |
      {
        "text": "✅ 部署成功",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*${{ github.repository }}* 部署成功\n环境: ${{ github.ref_name }}\n提交: `${{ github.sha }}`\n作者: ${{ github.actor }}"
            }
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": { "type": "plain_text", "text": "查看部署" },
                "url": "${{ steps.deploy.outputs.url }}"
              }
            ]
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

- name: Notify Slack on failure
  if: failure()
  uses: slackapi/slack-github-action@v1.24.0
  with:
    payload: |
      {
        "text": "❌ 部署失败",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*${{ github.repository }}* 部署失败\n分支: ${{ github.ref_name }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|查看日志>"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

飞书通知

- name: Notify Feishu
  if: always()
  run: |
    STATUS="${{ job.status }}"
    if [ "$STATUS" = "success" ]; then
      COLOR="green"
      EMOJI="✅"
    else
      COLOR="red"
      EMOJI="❌"
    fi
    
    curl -X POST "${{ secrets.FEISHU_WEBHOOK }}" \
      -H "Content-Type: application/json" \
      -d '{
        "msg_type": "interactive",
        "card": {
          "header": {
            "title": { "tag": "plain_text", "content": "'"$EMOJI"' 前端部署通知" },
            "template": "'"$COLOR"'"
          },
          "elements": [
            {
              "tag": "div",
              "fields": [
                { "is_short": true, "text": { "tag": "lark_md", "content": "**项目**\n${{ github.repository }}" }},
                { "is_short": true, "text": { "tag": "lark_md", "content": "**分支**\n${{ github.ref_name }}" }},
                { "is_short": true, "text": { "tag": "lark_md", "content": "**状态**\n'"$STATUS"'" }},
                { "is_short": true, "text": { "tag": "lark_md", "content": "**作者**\n${{ github.actor }}" }}
              ]
            }
          ]
        }
      }'

安全最佳实践

密钥管理

# 使用 GitHub Environments 管理不同环境的密钥
jobs:
  deploy:
    environment: production  # 引用 production 环境的密钥
    steps:
      - name: Deploy
        run: ./deploy.sh
        env:
          # 从环境中获取密钥
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

依赖安全扫描

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main, develop]
  schedule:
    - cron: '0 0 * * 1'  # 每周一扫描

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run npm audit
        run: pnpm audit --audit-level=high
        continue-on-error: true
      
      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

性能监控集成

- name: Lighthouse CI
  uses: treosh/lighthouse-ci-action@v10
  with:
    urls: |
      https://staging.example.com/
      https://staging.example.com/products
      https://staging.example.com/about
    budgetPath: ./lighthouse-budget.json
    uploadArtifacts: true
    temporaryPublicStorage: true

- name: Bundle Size Check
  uses: siddharthkp/bundlesize@v2
  env:
    BUNDLESIZE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
// lighthouse-budget.json
[
  {
    "path": "/*",
    "resourceSizes": [
      { "resourceType": "script", "budget": 300 },
      { "resourceType": "total", "budget": 500 }
    ],
    "resourceCounts": [
      { "resourceType": "third-party", "budget": 10 }
    ]
  }
]

完整的 Monorepo CI/CD

对于 Monorepo 项目,使用 Turborepo 可以大幅提升构建效率。

# .github/workflows/ci-monorepo.yml
name: CI (Monorepo)

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Turbo 需要比较 commit
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      
      - run: pnpm install --frozen-lockfile
      
      # 使用 Turbo 的远程缓存
      - name: Build with Turbo
        run: pnpm turbo run build lint test --filter="...[HEAD^1]"
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

结语

CI/CD 不仅仅是技术实践,更是团队协作方式的升级。一条设计良好的流水线:

  1. 提升信心:每次提交都经过自动化验证
  2. 加速交付:从代码到生产只需要几分钟
  3. 降低风险:问题在早期就被发现
  4. 解放人力:不再需要人工执行重复操作

从今天开始,为你的项目配置 CI/CD 吧。投入的时间会在未来成倍地回报给你。

"如果一件事值得做,就值得自动化。" —— 某位不愿透露姓名的 DevOps 工程师