CI/CD 前端实践
概述
持续集成(Continuous Integration)和持续交付(Continuous Delivery)是现代前端工程化体系中不可或缺的基础设施。CI/CD 流水线自动化地完成代码检查、测试、构建和部署流程,确保代码质量和发布效率。本文从前端开发者的视角出发,系统地介绍 CI/CD 的核心概念、主流工具链、最佳实践和常见场景。
GitHub Actions
GitHub Actions 是 GitHub 内置的 CI/CD 平台,通过 Workflow 文件定义自动化流程,语法简洁、生态丰富。
Workflow 基本语法
Workflow 文件存放在仓库的 .github/workflows/ 目录下,使用 YAML 格式定义。一个典型的 Workflow 文件包含以下核心元素:
name — Workflow 的名称,在 GitHub 界面中显示。
on — 触发条件,支持 push、pull_request、schedule(定时触发)、workflow_dispatch(手动触发)等多种事件。可以针对特定分支、路径或标签进行精细化配置。
jobs — 定义一个或多个 Job,每个 Job 运行在独立的 Runner 环境中。Job 之间默认并行执行,可以通过 needs 关键字设置依赖关系实现串行执行。
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test -- --coverage
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: dist/Job 与 Step
Job 是 Workflow 中的独立执行单元,每个 Job 可以指定不同的 Runner 运行环境(如 ubuntu-latest、windows-latest、macos-latest)。Job 之间通过 needs 建立依赖图,形成有向无环图(DAG)执行模型。
Step 是 Job 内的最小执行单元,按顺序执行。每个 Step 可以执行命令(run)或引用 Action(uses)。Step 之间可以通过 ${{ steps.step_id.outputs.output_name }} 共享数据。
Action 机制
Action 是可复用的单元,可以从 GitHub Marketplace 获取或自行编写。常用 Action 包括:
actions/checkout— 检出仓库代码actions/setup-node— 配置 Node.js 环境actions/cache— 缓存依赖以加速后续运行actions/upload-artifact/actions/download-artifact— 在 Job 之间传递文件
矩阵构建
矩阵构建(Matrix Strategy)允许在多个操作系统或 Node 版本的组合上并行运行 Job,大幅提高测试覆盖率:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18, 20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test注意:上述示例中使用了 ${{ }} 语法,这是 GitHub Actions 表达式语法的一部分。在 VitePress 文档中若需要在行内代码块中展示此类语法,应使用 <code v-pre> 标签包裹,或使用 ::: v-pre 容器。
缓存策略
依赖安装是前端流水线中最耗时的环节之一。通过 actions/cache 对 node_modules 进行缓存,可以将后续运行的安装时间从分钟级降低到秒级:
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-缓存 key 基于 package-lock.json 的哈希值生成,当依赖锁文件发生变化时自动失效。restore-keys 提供回退匹配策略,即使 key 未精确命中也能恢复最近的缓存。
环境变量与 Secret
环境变量通过 env 关键字定义,区分明文变量和加密 Secret:
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- run: npm run build
- run: npm run deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Secret 在 GitHub 仓库的 Settings > Secrets and variables > Actions 中配置,运行时自动注入但不会出现在日志中。
部署到 GitHub Pages
使用 actions/deploy-pages Action 可以方便地部署静态站点到 GitHub Pages:
jobs:
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- id: deployment
uses: actions/deploy-pages@v4需要先在仓库 Settings > Pages 中将 Source 设置为 "GitHub Actions"。
NPM 发布
对于组件库或工具库,可以在 CI 中自动发布到 NPM registry:
jobs:
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}建议结合 changesets 或 semantic-release 实现语义化版本管理和自动发布。
GitLab CI
GitLab CI 是 GitLab 自带的 CI/CD 系统,通过仓库根目录下的 .gitlab-ci.yml 文件定义流水线。
.gitlab-ci.yml 基本结构
stages:
- lint
- test
- build
- deploy
variables:
NODE_ENV: development
NODE_VERSION: "20"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
lint:
stage: lint
image: node:20
script:
- npm ci
- npm run lint
test:
stage: test
image: node:20
script:
- npm ci
- npm run test -- --coverage
artifacts:
paths:
- coverage/
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
deploy:
stage: deploy
image: node:20
script:
- npm ci
- npm run deploy
environment:
name: production
url: https://example.com
only:
- mainStage 与 Job
Stage 定义流水线的阶段,同一 Stage 内的 Job 并行执行,当前 Stage 所有 Job 成功后才会进入下一 Stage。常用 Stage 顺序为:lint → test → build → deploy。
Job 是 Stage 内的执行单元,每个 Job 定义运行环境(image)、执行脚本(script)、缓存策略、 artifacts 等配置。
Runner
Runner 是 GitLab CI 的实际执行者,分为三种类型:
- Shared Runner — GitLab 官方提供的公共 Runner,适合开源项目
- Group Runner — 在组级别共享,组内所有项目可用
- Specific Runner — 绑定到特定项目,可部署在私有环境
前端项目通常选用 node:20 作为基础镜像,也可以使用自行构建的定制镜像来预装依赖加快速度。
缓存与 Artifact
Cache 用于加速依赖安装,在 Job 之间共享。通常缓存 node_modules/ 目录:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull-pushkey 控制缓存的作用域,通常基于分支或 lock 文件哈希生成。
Artifact 用于在 Job 之间传递构建产物,与 Cache 不同,Artifact 是有明确用途的输出文件:
- 测试覆盖率报告传给部署 Job 或在 GitLab UI 中查看
- 构建产物(
dist/)传给部署 Job - Artifact 默认保留 30 天,可通过
expire_in控制
环境与 Review Apps
GitLab CI 的环境功能支持部署追踪和 Review Apps:
deploy_review:
stage: deploy
script:
- npm run deploy -- --env=review
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.preview.example.com
on_stop: stop_review
only:
- merge_requests
stop_review:
stage: deploy
script:
- npm run teardown -- --env=review
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manualReview Apps 特性可以为每个合并请求自动创建独立的预览环境,方便设计师和产品经理审查功能。
Pages 部署
GitLab Pages 的配置非常简洁:
pages:
stage: deploy
script:
- npm ci
- npm run build
- mv dist/ public/
artifacts:
paths:
- public/
only:
- mainGitLab CI 会自动将 public/ 目录的内容发布到 GitLab Pages 服务。
Docker 容器化
Docker 容器化为前端应用提供一致的运行环境和可靠的部署方式。
Dockerfile 多阶段构建
多阶段构建是前端 Docker 化的标准实践,通过分离构建环境和运行环境来减小镜像体积:
# 第一阶段:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 第二阶段:运行
FROM nginx:stable-alpine AS runner
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]第一阶段使用 node:20-alpine 进行依赖安装和构建,第二阶段仅将构建产物复制到 nginx:stable-alpine 镜像中。最终镜像体积通常只有 20-30 MB,比单阶段构建缩小 10 倍以上。
Nginx 配置
针对 SPA(单页应用)的 Nginx 配置示例:
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
# gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_vary on;
# HTTP 缓存控制
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
expires -1;
add_header Cache-Control "no-store";
}
# 健康检查
location = /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}反向代理配置
在需要代理后端 API 时,配置反向代理:
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
}gzip 压缩
gzip 压缩是前端性能优化的基本手段,在 Nginx 层面开启后,传输体积可减少 60%-80%。推荐对以下类型开启压缩:
text/htmltext/csstext/plainapplication/javascriptapplication/jsonimage/svg+xml
小文件(小于 1 KB)不建议压缩,因为压缩后的开销可能反而增加体积。通过 gzip_min_length 控制最小压缩阈值。
HTTP 缓存策略
合理的 HTTP 缓存策略对前端性能至关重要:
| 资源类型 | 缓存策略 | Cache-Control |
|---|---|---|
| HTML(入口文件) | 不缓存 | no-store |
| JS/CSS(带 hash) | 长期缓存 | public, immutable, max-age=31536000 |
| 图片/字体 | 长期缓存 | public, immutable, max-age=31536000 |
| API 响应 | 按需 | no-cache 或 max-age=60 |
健康检查
容器编排平台(如 Kubernetes)通过健康检查判断容器是否正常运行:
Nginx 配置中:
location = /health {
access_log off;
return 200 "healthy\n";
}Docker Compose 中:
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10sDocker Compose
在开发环境中,使用 Docker Compose 编排前端应用及其依赖服务:
version: "3.8"
services:
frontend:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
depends_on:
backend:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
backend:
image: my-api:latest
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3镜像优化技巧
- 选择 Alpine 基础镜像 —
node:20-alpine比node:20小约 200 MB - 合理利用层缓存 — 将变化频率低的文件(
package.json、package-lock.json)放在 COPY 命令的前面 - 多阶段构建 — 分离构建环境和运行环境
- 清理不必要的文件 — 构建完成后删除
.npm缓存和临时文件 - 使用
.dockerignore— 排除 node_modules、.git、.env 等文件进入构建上下文
CI 流程设计
一套完善的前端 CI 流程设计需要考虑质量、效率、安全等多个维度。
标准流程:Lint → Test → Build → Deploy
push/PR → Lint → Test → Build → Deploy(staging) → 手动审批 → Deploy(production)- Lint — 代码风格和基础质量检查,执行最快,作为第一道关卡
- Test — 单元测试、集成测试,确保功能正确性
- Build — 生产构建,验证项目能否正确打包
- Deploy — 部署到目标环境
并行阶段设计
在资源允许的情况下,可以将独立的任务并行执行以缩短流水线总耗时:
jobs:
lint:
# ...
type-check:
# ...
unit-test:
# ...
build:
needs: [lint, type-check, unit-test]
# ...这种方式要求 Runner 资源充足,适合中大型项目团队。
手动审批
生产环境部署通常需要人工审核,避免意外发布:
GitHub Actions:
deploy-production:
needs: deploy-staging
environment:
name: production
runs-on: ubuntu-latest
environment:
name: production
steps:
- run: echo "Deploying to production..."在 GitHub 的 Environment 设置中启用 Required reviewers 即可实现审批门禁。
GitLab CI:
deploy_production:
stage: deploy
script:
- npm run deploy:production
environment:
name: production
when: manual
only:
- mainwhen: manual 使得该 Job 需要手动点击执行。
回滚策略
回滚是发布流程的最后一道防线。常见的回滚方式包括:
- Git revert — 提交回滚 commit,触发新的部署流水线
- Artifact 回滚 — 重新部署上一次成功的构建产物
- 容器镜像回滚 — 重新部署上一版本的 Docker 镜像标签
建议保留最近 5-10 次成功构建的产物或镜像,以便快速回滚。
分支策略集成
CI 流程需要与 Git 分支策略紧密配合:
| 分支 | 触发事件 | CI 流程 |
|---|---|---|
feature/* | push | Lint + Test |
develop | push | Lint + Test + Build + Deploy(staging) |
main | push | Lint + Test + Build + Deploy(production) |
release/* | push | 完整流程 + 手动审批 |
hotfix/* | push | 完整流程 + 优先部署 |
代码质量门禁
代码质量门禁(Quality Gate)在流程的不同阶段设置检查点,只有通过所有门禁的代码才能进入下一个阶段。
ESLint 阈值
在 CI 中运行 ESLint 时可以设置严格模式,将 warning 也视为错误:
// .eslintrc.json
{
"rules": {
"no-unused-vars": "error",
"no-console": "warn"
}
}# CI 命令
npx eslint src/ --max-warnings 0--max-warnings 0 确保没有任何 warning 被允许,严格把控代码质量。
测试覆盖率
设置覆盖率阈值,低于阈值则 CI 失败:
# vitest 配置
npx vitest run --coverage --coverage.threshold=80推荐的前端覆盖率红线:
- 语句覆盖率(Statements):≥ 80%
- 分支覆盖率(Branches):≥ 75%
- 函数覆盖率(Functions):≥ 80%
- 行覆盖率(Lines):≥ 80%
构建大小对比
在 PR 中自动对比构建产物大小,防止无意识的体积膨胀:
# 使用 size-limit
npx size-limit --json > size-report.json可以集成 size-limit 或 bundlesize 工具,在 PR 评论中展示体积变化,超过阈值则阻止合并。
Lighthouse 评分
对部署后的预览环境进行 Lighthouse 审计,确保性能指标达标:
npx lighthouse https://preview.example.com --output json --output-path ./lighthouse-report.json关键指标阈值建议:
- Performance ≥ 90
- Accessibility ≥ 90
- Best Practices ≥ 90
- SEO ≥ 90
安全检查
集成安全扫描工具到 CI 流程中:
# npm 审计
npm audit --audit-level=high
# 依赖安全检查
npx snyk test对于检测到的高危或严重漏洞,流水线应直接失败并通知开发者。
部署策略
不同的业务场景需要不同的部署策略来平衡风险与效率。
蓝绿部署
蓝绿部署维护两套完全相同的生产环境(蓝环境和绿环境),通过切换流量实现零停机发布。
deploy_blue:
stage: deploy
script:
- deploy-to blue
- health-check blue
switch_to_blue:
stage: switch
script:
- switch-traffic blue
when: manual
deploy_green:
stage: deploy
script:
- deploy-to green
- health-check green
switch_to_green:
stage: switch
script:
- switch-traffic green
when: manual优势:回滚极其快速,只需切换流量;劣势:资源消耗翻倍。
滚动更新
滚动更新逐步替换运行中的实例,适用于容器化部署(Kubernetes 原生支持):
# Kubernetes Deployment 策略
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0一次替换一个或多个 Pod,始终保持一定数量的可用实例,避免服务中断。
灰度发布
灰度发布(金丝雀发布)将新版本先暴露给一小部分用户,观察稳定后再全量发布:
初始状态:100% 流量 → v1.0
灰度阶段:5% 流量 → v2.0, 95% 流量 → v1.0
观察期:监控错误率和性能指标
全量:100% 流量 → v2.0CI 中可以结合流量管理工具(如 Nginx、Envoy、Istio)实现自动化灰度流程。
CDN 预热
前端静态资源部署到 CDN 后需要预热,避免用户首次访问时触发回源:
# CDN 预热脚本示例
curl -X POST "https://api.cdn.example.com/prefetch" \
-H "Authorization: Bearer $CDN_TOKEN" \
-d '{"urls": ["https://example.com/assets/app.js", "https://example.com/assets/app.css"]}'预热应在部署步骤完成后、流量切换前执行。
版本回滚
回滚预案应在发布前准备好:
- 确认回滚版本 — 确定要回滚到的目标版本号
- 执行回滚 — 重新部署目标版本的构建产物或镜像
- 缓存清理 — 清理 CDN 缓存,确保用户获取到回滚后的资源
- 验证 — 执行健康检查和冒烟测试
- 通知 — 通过 IM/邮件通知相关团队成员
环境分支
环境分支策略将不同环境映射到不同分支:
main— 生产环境develop— 开发/集成环境release/*— 预发布环境feature/*— 个人开发环境
CI 流程根据分支自动选择部署目标,减少人工配置错误。
监控与告警
部署不是终点,部署后的监控和告警才是保障服务稳定性的关键。
部署后健康检查
部署完成后立即执行健康检查:
- run: |
for i in {1..10}; do
status=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/health)
if [ "$status" = "200" ]; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i failed, retrying..."
sleep 5
done
echo "Health check failed"
exit 1性能对比
部署后自动对比关键性能指标与基线:
- LCP(Largest Contentful Paint) — 最大内容绘制,应 < 2.5s
- FID(First Input Delay) — 首次输入延迟,应 < 100ms
- CLS(Cumulative Layout Shift) — 累计布局偏移,应 < 0.1
- TTFB(Time to First Byte) — 首字节时间,应 < 800ms
通过对比部署前后的性能数据,及时发现性能退化。
错误率监控
部署后需要密切关注前端错误率:
- JS 运行时错误 — 通过
window.onerror或window.addEventListener('error')捕获 - 资源加载错误 — 监听资源加载失败事件
- API 请求错误率 — 监控 HTTP 4xx/5xx 响应比例
- 白屏检测 — 检查页面根元素是否包含预期内容
建议集成 Sentry、FunDebug 或自建监控平台。
回滚触发条件
定义自动回滚的触发条件,减少人工响应时间:
| 指标 | 触发阈值 | 操作 |
|---|---|---|
| 错误率 | 较基线上升 > 1% | 自动回滚 |
| API 5xx 率 | > 5% | 自动回滚 |
| LCP | 较基线退化 > 30% | 告警 + 手动回滚 |
| 白屏率 | > 0.1% | 自动回滚 |
| 服务器 CPU | > 90% 持续 5 分钟 | 告警 + 扩容 |
自动回滚应与告警系统联动,通过钉钉、飞书、Slack 等渠道即时通知相关负责人。
结语
CI/CD 是前端工程化从"能跑"到"跑得好"的关键一跃。一个成熟的 CI/CD 体系不仅提升了交付效率,更重要的是通过自动化质量门禁和灰度机制建立了代码从提交到上线的安全通道。随着微前端、Serverless、Edge Functions 等新架构的普及,CI/CD 的复杂性也在持续增长,但核心理念始终不变:自动化一切,将人为失误降到最低。