本文目录导读:

Git 钩子脚本可以通过在代码提交前或提交后运行性能检查工具,来实施“性能门禁”,以下是一些常见的方法和示例脚本,涵盖前端(JS/TS)和后端(Java/Python/Go)等场景。
性能门禁的核心思路
性能门禁通常检查以下维度:
- 代码体积/资源体积:避免提交过大的文件或打包产物
- 执行时间:防止引入明显变慢的函数或算法
- 内存/CPU 使用:检测内存泄漏或高消耗操作
- 网络请求:检查 API 调用次数、payload 大小
- 构建速度:防止退化导致构建过慢
常用工具与集成方式
| 检查类型 | 推荐工具 | 适用场景 |
|---|---|---|
| 代码复杂度 | ESLint (复杂度规则)、Plato、Radon | JS/TS/Python |
| 文件体积 | du、size-limit、webpack-bundle-analyzer |
前端资源 |
| 打包体积 | size-limit、bundlesize |
前端构建 |
| 函数执行时间 | 自定义 Benchmark 脚本 | 后端关键逻辑 |
| 内存泄漏 | Node.js heap snapshot、Valgrind | 后端/服务端 |
| API 性能 | Lighthouse CI (lighthouse-ci) | 前端性能评分 |
| 构建时间 | 计时脚本 | CI/CD 环境 |
Git 钩子检查性能门禁示例
检查文件大小上限(pre-commit 钩子)
#!/bin/bash
# .git/hooks/pre-commit
# 设置最大文件大小(单位:字节,这里设为 1MB)
MAX_SIZE=$((1 * 1024 * 1024))
# 获取本次提交的改动文件(仅暂存区)
changed_files=$(git diff --cached --name-only --diff-filter=ACM)
threshold_exceeded=false
for file in $changed_files; do
if [ -f "$file" ]; then
file_size=$(stat --format=%s "$file")
if [ "$file_size" -gt "$MAX_SIZE" ]; then
echo "❌ 性能门禁失败: $file 大小为 $(numfmt --to=iec $file_size),超出限制 $(numfmt --to=iec $MAX_SIZE)"
threshold_exceeded=true
fi
fi
done
if [ "$threshold_exceeded" = true ]; then
echo "请优化大文件后再提交。"
exit 1
fi
exit 0
检查前端打包体积(pre-push 钩子,需要 Node.js)
// .git/hooks/pre-push (先设置可执行权限)
// 需要 Node.js 环境
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
// 配置:最大允许的打包体积(字节)
const MAX_BUNDLE_SIZE = 200 * 1024; // 200KB
try {
// 运行构建(仅用于检查,不覆盖dist)
execSync('npm run build -- --profile', {
cwd: process.cwd(),
stdio: 'pipe',
timeout: 120000 // 2分钟超时
});
// 找到构建产物(以 dist 为例)
const distDir = path.join(process.cwd(), 'dist');
if (!fs.existsSync(distDir)) {
console.log('⚠️ 未找到 dist 目录,跳过打包体积检查');
process.exit(0);
}
const files = fs.readdirSync(distDir, { recursive: true });
let totalSize = 0;
for (const file of files) {
const fullPath = path.join(distDir, file);
if (fs.statSync(fullPath).isFile()) {
totalSize += fs.statSync(fullPath).size;
}
}
if (totalSize > MAX_BUNDLE_SIZE) {
console.error(`❌ 性能门禁失败: 打包总大小 ${(totalSize / 1024).toFixed(2)}KB,超过限制 ${(MAX_BUNDLE_SIZE / 1024).toFixed(2)}KB`);
process.exit(1);
} else {
console.log(`✅ 打包体积检查通过: ${(totalSize / 1024).toFixed(2)}KB`);
}
} catch (error) {
console.error('❌ 构建或体积检查出错:', error.message);
process.exit(1);
}
检查代码复杂度(pre-commit,ESLint)
#!/bin/bash
# .git/hooks/pre-commit (需要已安装 eslint)
# 检查是否有 ESLint 配置
if [ ! -f ".eslintrc.js" ] && [ ! -f ".eslintrc.json" ]; then
echo "⚠️ 未找到 ESLint 配置,跳过复杂度检查"
exit 0
fi
# 获取被修改的 JS/TS 文件
changed_js_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx)$')
if [ -z "$changed_js_files" ]; then
exit 0
fi
# 运行 ESLint 复杂度规则(必须配置了 complexity 规则)
npx eslint --rule 'complexity: ["error", 10]' $changed_js_files 2>&1
if [ $? -ne 0 ]; then
echo "❌ 性能门禁失败: 存在高复杂度函数,请重构后提交"
exit 1
fi
exit 0
检查数据库查询/API 性能(后端 pre-commit,自定义脚本)
#!/bin/bash
# .git/hooks/pre-commit
# 检查是否有 SQL 查询或 API 调用的改动
changed_sql_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sql|prisma|entity|repository)\.(ts|js|py|java)$')
if [ -z "$changed_sql_files" ]; then
exit 0
fi
# 演示:检查是否包含 N+1 查询模式(关键字:forEach 内调用 find/findOne)
for file in $changed_sql_files; do
if grep -qP 'await.*find(One)?\(.*\)\s*\)' "$file" 2>/dev/null; then
echo "❌ 性能门禁失败: $file 中可能存在 N+1 查询模式(循环内逐个查找)"
echo "建议使用批量查询(IN 子句)或 JOIN 代替"
exit 1
fi
done
echo "✅ SQL 查询模式检查通过"
exit 0
集成 Lighthouse CI 性能评分(pre-push)
# .github/workflows/performance-gate.yml (作为 GitHub Actions,但逻辑可移植到钩子)
# 实际钩子脚本:调用 lighthouse-ci 并检查分数
#!/bin/bash
# .git/hooks/pre-push
# 需要先安装 lighthouse-ci
# npm install -g @lhci/cli
# 假设有一个暂存或预览地址
URL="https://your-staging-url.com"
# 运行 Lighthouse 并输出 JSON 结果
lhci collect --url=$URL --numberOfRuns=2
lhci assert --preset=lighthouse:recommended 2>&1 | grep -q "All assertions passed"
if [ $? -ne 0 ]; then
echo "❌ 性能门禁失败: Lighthouse 分数未达到阈值"
exit 1
fi
echo "✅ 性能评分通过"
exit 0
钩子脚本放置与权限
# 将脚本放到 .git/hooks/ 下,并赋予可执行权限 chmod +x .git/hooks/pre-commit chmod +x .git/hooks/pre-push # 若要全局分发至团队,可使用 git 模板或 husky 管理
使用 Husky(前端项目推荐)
package.json 中添加命令:
npx husky add .husky/pre-commit "npx eslint --rule 'complexity: [\"error\", 10]' --ext .js,.ts --diff" npx husky add .husky/pre-push "npm run build && node scripts/check-bundle-size.js"
性能门禁的常见阈值参考
| 检查项 | 合理阈值 |
|---|---|
| 单个文件大小 | 500KB ~ 2MB(视项目类型) |
| 打包 JS 体积 | 200KB ~ 1MB(gzip 后更严格) |
| 函数圈复杂度 | 10 ~ 15 |
| 构建时间 | 2 ~ 5 分钟 |
| Lighthouse 性能评分 | ≥ 80 分 |
| 单个 API 响应时间 | < 200ms |
注意事项
- 性能门禁不要过于严格,避免阻碍正常开发,可以设置“警告”和“拒绝”两个级别。
- 只在特定分支(如 main/release)强制执行,开发分支可以只提示。
- 本地钩子可被绕过,CI/CD 管道应保留强制的性能检查。
- 避免在钩子中运行耗时过长的检查(如完整构建),可放到
pre-push或 CI 阶段。