有时候真不是大家不想写好代码,实在是手一快就忘了格式化,或者临提交才发现缩进一团糟。Review时满屏的空白字符变更比逻辑改动还多,那个心情,血压直接飙到 180。
后来我们在项目里加了一道防线:利用 Git Hook,在 commit 之前自动做格式化和静态扫描,不通过直接不给提交。这事做下来,整个代码库干净多了,PR 里再也没有“格式化战争”。下面分享一份我们正在用的 pre-commit 脚本,以及借助 lint-staged 的更优雅玩法。
裸装的 pre-commit 钩子
最早我们是直接在 .git/hooks/pre-commit 里塞一个脚本,让 Git 在提交前强制跑检查。简单粗暴,不依赖第三方工具。脚本长这样:
#!/bin/bash
# .git/hooks/pre-commit# 获取所有暂存区中新增或修改的文件(不包含删除)
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)if [ -z "$STAGED_FILES" ]; thenecho "没有暂存文件,允许提交"exit 0
fiecho "==================== 代码格式检查 ===================="
# 先跑 Prettier 格式化,这里只检查不直接修改(如果你想让脚本帮你改,可以去掉--check)
FORMAT_ERROR=0
while IFS= read -r FILE; do# 只处理 JavaScript/TypeScript 文件,防止误伤if [[ "$FILE" =~ \.(js|jsx|ts|tsx)$ ]]; thennpx prettier --check "$FILE" --config .prettierrc 2>/dev/nullif [ $? -ne 0 ]; thenecho "❌ $FILE 格式不规范,请先执行 prettier --write 再提交"FORMAT_ERROR=1fifi
done <<< "$STAGED_FILES"if [ $FORMAT_ERROR -ne 0 ]; thenecho ">> 格式化检查未通过,提交中止"exit 1
fiecho "==================== 静态扫描 ===================="
# 再跑 ESLint
LINT_ERROR=0
while IFS= read -r FILE; doif [[ "$FILE" =~ \.(js|jsx|ts|tsx)$ ]]; thennpx eslint --quiet "$FILE" --config .eslintrc.js 2>/dev/nullif [ $? -ne 0 ]; thenLINT_ERROR=1fifi
done <<< "$STAGED_FILES"if [ $LINT_ERROR -ne 0 ]; thenecho ">> 静态扫描发现错误,提交中止"exit 1
fiecho "✅ 所有检查通过,允许提交"
exit 0
解释几个容易踩坑的点:
git diff --cached --name-only --diff-filter=ACMR只拿暂存区里新增 (A)、复制 (C)、修改 (M)、重命名 (R) 的文件,避免去检查已经删除的文件导致报错。- 循环里用
2>/dev/null把一些恼人的内部输出吞掉,只保留关键信息。 - Prettier 我用
--check而不是直接--write,因为我不喜欢 Hook 偷偷改文件内容,万一格式变化和逻辑修改混在一起,回滚很麻烦。团队习惯是让开发者自己跑一次修复命令再重新提交,责任清晰。
这个脚本有个明显的缺点:对大项目比较慢,因为每次提交都会全量检查所有暂存文件。而且如果只是想快速提交一个 hotfix,它也会强制跑一遍。所以后来我们上了 lint-staged。
进阶版:lint-staged + husky
与其手写循环,不如把活儿交给 lint-staged,它只对被 staged 的文件执行指定命令,快且配置清晰。
安装 husky 和 lint-staged:
npm install husky lint-staged --save-dev
npx husky install
然后在 package.json 里配置:
"lint-staged": {"*.{js,jsx,ts,tsx}": ["prettier --check","eslint --quiet"],"*.{json,md,css}": ["prettier --check"]
}
接着用 husky 接管 pre-commit 钩子:
npx husky add .husky/pre-commit "npx lint-staged"
生成的 .husky/pre-commit 就这么一行:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"npx lint-staged
这种方式的亮点在哪?
- 只跑受影响的文件,大仓也不怕慢。
- 配置文件集中管理,新成员克隆项目后
npm install就自动启用 hook,不用再去手动拷贝.git/hooks。 - 扩展性强,后期加 stylelint、commitlint 只需要往
lint-staged里追加命令。
不过有个小问题:prettier --check 失败只会报错不自动修,如果你更喜欢“提交时自动格式化”,可以把命令改成 prettier --write。但这样会导致文件被修改后需要重新 add,如果没注意可能会漏掉改动。我们的折中方案是:报错后提醒开发者跑一次格式化命令,再重新提交。钩子不做隐式修改。
效果与感受
上了这套之后,代码风格问题直接被拦在本地,CI 再也不用浪费时间去跑格式检查。Review 时只看逻辑改动,清爽多了。偶尔有新同事抱怨“怎么提交不了”,告诉他先跑一下 lint 就好了,习惯之后都觉得很顺。
当然,这套防线不能解决所有质量问题,比如单元测试覆盖率、复杂度控制,还得靠 CI 来做。但至少在编码风格这一关,不用再跟同事拉扯了。
如果你还没在项目里用 Git Hook 做质量门禁,强烈建议试试。不想自己写脚本就直接上 husky + lint-staged,五分钟配完,长期受益。
