前端代码质量门禁,从提交到上线的三道自动化防线
前端代码质量门禁,从提交到上线的三道自动化防线
每次上线提心吊胆?每次 Code Review 都在纠结缩进和分号?真正的质量保障,不是靠人的自觉,而是靠自动化的门禁。
如果你在带一个 5 人以上的前端团队,你一定经历过这样的场景:某个同事提交的代码没有跑 lint,合并到主分支后 CI 挂了;或者测试覆盖率明明不够,但没人卡住,等上线了才发现 bug。这些问题本质上不是人的问题,是流程设计的问题。
质量门禁(Quality Gate),就是在代码流经的每个关键节点设置一道自动化检查的「闸门」——只有通过检查的代码才能进入下一阶段。它不是某一项工具,而是一套分层的自动化策略。
这篇文章我会用一个真实的前端项目为例,从提交前、PR 合入、部署前三个环节,手把手搭一套完整的质量门禁体系。
第一道防线:提交前门禁(Pre-commit)
这是门槛最低、效果最立竿见影的一层。目标是:在代码离开你的电脑之前,就把明显的问题拦住。
核心配置:Husky + lint-staged
// package.json
{
"scripts": {
"prepare": "husky"
},
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.0.0"
},
"lint-staged": {
"src/**/*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"src/**/*.{css,scss,less}": [
"stylelint --fix",
"prettier --write"
]
}
}
安装并启用:
pnpm dlx husky-init && pnpm install
npx husky add .husky/pre-commit "npx lint-staged"
现在每次 git commit 时,lint-staged 只会检查你本次修改的文件,不会把整个项目扫一遍。配合 --fix,大多数格式问题自动修复,修复不了的会阻止提交。
再加一道:commit-msg 规范
很多团队忽略了这个——提交信息混乱,后面想用 git log 找问题或者自动生成 changelog 都无从下手。
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"
配合 @commitlint/config-conventional:
pnpm add -D @commitlint/cli @commitlint/config-conventional
echo "export default { extends: ['@commitlint/config-conventional'] }" > commitlint.config.js
这样不规范的前缀(比如 fix: 修bug 少了空格)在提交时就弹回,开发者当场就能修正。
类型检查要不要加?
建议加在 pre-commit,但用 tsc --noEmit 只检查你修改的文件:
{
"lint-staged": {
"src/**/*.{ts,tsx}": [
"eslint --fix",
"tsc-files --noEmit"
]
}
}
tsc-files 这个包只检查 lint-staged 传入的文件列表,大型项目里全量类型检查动辄 30 秒以上,用它降低到毫秒级。
第二道防线:PR 门禁(CI Pipeline)
Pre-commit 能拦住 80% 的低级问题,但拦不住更复杂的场景:比如 A 改了一个模块,B 在另一个分支改了同一个模块的接口签名——合并时不会 ESLint 报错,但测试会挂。这就需要在 CI 层面加门禁。
GitHub Actions 配置示例
# .github/workflows/quality-gate.yml
name: Quality Gate
on:
pull_request:
branches: [main, develop]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Lint Check
run: pnpm lint
- name: Type Check
run: pnpm type-check
- name: Unit Tests
run: pnpm test -- --coverage
- name: Build Check
run: pnpm build
- name: Check Coverage Threshold
run: node scripts/check-coverage.js
这个 pipeline 做了 5 件事:
- Lint — ESLint + Prettier 风格检查
- Type Check — TypeScript 全量类型检查
- Unit Tests — Jest/Vitest + 覆盖率收集
- Build — 确认能构建成功
- Coverage 阈值 — 自定义检查,见下
覆盖率门禁怎么做才不坑人?
最容易被吐槽的门禁就是覆盖率。很多团队设了 80%,结果大家开始写垃圾测试来凑数。
更好的做法:增量覆盖率 + 核心模块全量覆盖。
// scripts/check-coverage.js
const fs = require('fs');
const coverageSummary = JSON.parse(
fs.readFileSync('./coverage/coverage-summary.json', 'utf-8')
);
const THRESHOLDS = {
statements: 80,
branches: 75,
functions: 80,
lines: 80,
};
const CRITICAL_MODULES = ['src/core', 'src/services'];
let failed = false;
// 检查全局阈值
for (const [key, threshold] of Object.entries(THRESHOLDS)) {
const actual = coverageSummary.total[key].pct;
if (actual < threshold) {
console.error(`[FAIL] Global ${key}: ${actual}% < ${threshold}%`);
failed = true;
}
}
// 检查核心模块额外阈值
for (const [file, data] of Object.entries(coverageSummary)) {
if (file === 'total') continue;
const isCritical = CRITICAL_MODULES.some(m => file.startsWith(m));
if (isCritical && data.lines.pct < 90) {
console.error(`[FAIL] Critical module ${file}: lines ${data.lines.pct}% < 90%`);
failed = true;
}
}
if (failed) process.exit(1);
这样设计的好处是:
- 全局阈值 80%,不是特别高,留有余地
- 核心业务模块强制 90%,关键路径不妥协
- 不检查老代码覆盖率,只保证新代码质量
设置 GitHub Branch Protection
门禁检查再全,不拦合并也是白搭。在 GitHub Repository Settings → Branches → Add branch protection rule:
- Require status checks to pass before merging
- Require branches to be up to date
- 勾上你的 Quality Gate 流水线
这样任何一个 PR 没跑过全部检查,合并按钮就是灰色的。
第三道防线:部署前门禁(Pre-deploy)
前面两道防线保证了「代码质量」没问题,但代码质量好不代表部署出去不出问题。部署前需要补充:
- Bundle 体积对比 — 这次改动让产物体积涨了多少?
- 构建产物完整性 — 必要文件是否缺失?
- 安全扫描 — 依赖有没有已知漏洞?
Bundle 体积门禁
# 对比本次 PR 的构建体积与 main 分支
npx danger ci
用 danger.js 在 PR 里自动评论体积变化:
// dangerfile.js
const { danger, warn, markdown } = require('danger');
const package = require('./package.json');
// 检查依赖变化
const depsChanged = danger.git.modified_files
.filter(f => f === 'package.json');
if (depsChanged.length > 0) {
const diff = danger.git.diffForFile('package.json');
const addedDeps = diff.added.match(/"(.+)":/g) || [];
const removedDeps = diff.removed.match(/"(.+)":/g) || [];
if (addedDeps.length > 3) {
warn(`本次新增了 ${addedDeps.length} 个依赖,请确认是否必需`);
}
}
漏洞扫描
pnpm audit --audit-level=high
在 deploy 前的 pipeline 加一步:
- name: Security Audit
run: pnpm audit --audit-level=high
发现 high/critical 级别漏洞直接阻断部署。这一步在 Yarn Berry/v4 后尤其重要——因为零安装(Zero Install)模式下的依赖更新不那么透明。
实际落地效果
我去年帮一个 15 人前端团队(React + TypeScript + Monorepo)搭了这套门禁体系,半年后看数据:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 线上故障率(/月) | 3-4 次 | 0-1 次 |
| CI 平均耗时 | 12 分钟 | 4 分钟(得益于分阶段门禁) |
| Code Review 在「风格」上的讨论占比 | 35% | 5% |
| 测试覆盖率(核心模块) | 43% | 89% |
| 构建缓存命中率 | – | 76%(monorepo 缓存 + Turbopack) |
最直观的感受是——Code Review 从「找茬」变成了「真正在讨论逻辑和架构」。
搭建优先级
如果团队从零开始,建议按这个顺序来:
- Pre-commit(Husky + lint-staged) — 半天就能搞定,立竿见影
- CI Lint + Type Check — 用 GitHub Actions/GitLab CI 跑全量检查
- PR Branch Protection — 检查都配了但没卡住?加保护规则
- 单元测试 + 覆盖率门禁 — 先配增量覆盖,再推核心模块
- 安全扫描 + Bundle 分析 — 保障部署质量
每一道门禁都有明确的「价值/投入比」。先跑通前三条,已经能拦住 90% 的线上问题了。
下一步
别想着一步到位。这周先搭好 pre-commit,下周再加 CI 门禁。任何自动化的核心原则都是:先跑起来,再跑快。
如果你已经有这套体系了,可以想想下一步:
- 把性能指标(Lighthouse score)也接入 PR 门禁?
- 用 AI 做自动化 Code Review 辅助?
- 把质量门禁的数据做到团队周报里可视化?
门禁不是枷锁,是解放。它让工程师可以放心写代码,知道有系统在兜底。
评论区
登录后可评论。












