用 lint-staged 提升前端代码质量与提交效率实战
我的写法,亲测靠谱
在项目里引入 lint-staged 已经好几年了,从一开始照着文档抄配置,到后来踩了一堆坑,现在总算摸出一套自己用着顺手的写法。我现在的配置核心就一条:只处理 Git 暂存区的文件,不搞全局 lint,也不让 pre-commit 阻塞太久。
这是我现在项目里最常用的配置(package.json 里):
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less}": [
"stylelint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}
为什么这么写?首先,我不用 glob 匹配太宽泛的规则,比如 * 或 **/*,这种写法看似省事,实则容易把 node_modules、dist、.git 等目录里的文件也误伤进去。虽然 lint-staged 默认会忽略 .gitignore 里的文件,但有些临时文件或 IDE 生成的文件可能没被 ignore,一旦被误处理,轻则报错,重则直接改坏文件。
其次,我把 ESLint 和 Prettier 分开执行,但顺序很重要:先 eslint --fix,再 prettier --write。因为 ESLint 的 fix 可能会破坏格式(比如缩进、引号),而 Prettier 是最后统一格式的。如果反过来,Prettier 先格式化,ESLint 再 fix,可能又把格式弄乱了,导致最终提交的代码格式不一致。
另外,我不把测试命令(如 jest)塞进 lint-staged。有人喜欢在 pre-commit 里跑单元测试,但实际体验非常差——测试一跑几十秒,开发者等得烦躁,最后干脆 git commit --no-verify 跳过。Lint-staged 的定位是“快速修复格式和基础 lint”,不是完整 CI。测试留到 push hook 或 CI 流程里更合适。
这几种错误写法,别再踩坑了
我见过太多团队把 lint-staged 配置写成“看起来能跑,但实际埋雷”的样子。下面这些是我反复踩过的坑,你最好避开。
- 用 shell 命令拼接,不处理空格和特殊字符:比如写成
"eslint {filenames}"。当文件名带空格(比如my file.js)时,命令会直接崩。正确做法是让 lint-staged 自动传参,或者用函数形式处理文件列表。 - 在 Windows 上用单引号:比如
'eslint --fix'。Windows 的 cmd 不认单引号,会报 “’eslint’ is not recognized” 错误。统一用双引号,或者直接写命令字符串,不加引号(npm scripts 里默认支持)。 - lint-staged 和 husky 版本不匹配:特别是 husky v4 升级到 v7/v8 时,配置方式大变。我曾经在一个老项目里升级 husky,结果 pre-commit 完全失效,折腾了半天才发现 lint-staged 的调用方式变了。建议锁版本,或者用最新稳定版重新配置。
- 把 .lintstagedrc 放在奇怪的位置:lint-staged 默认只读项目根目录的配置。如果你把它放在
scripts/或config/下,它根本不会生效。我就干过这事,以为路径能自定义,结果 commit 时完全没触发。
还有一个经典反面案例:有人为了“保险”,在 lint-staged 里写 git add .。这简直是灾难!想象一下,你改了三个文件,只暂存了两个,但 git add . 会把第三个未暂存的文件也加进去,导致提交了不该提交的内容。Lint-staged 本身就会自动 git add 处理后的文件,你不需要、也不应该手动干预。
实际项目中的坑
在真实项目中,lint-staged 看似简单,但细节问题不少。我总结几个高频痛点:
1. 大文件卡死:如果某个 JS 文件特别大(比如打包产物误提交),ESLint –fix 可能跑十几秒甚至卡死。我的做法是在 .eslintignore 里明确排除 dist、build、coverage 等目录,同时在 lint-staged 的 glob 里避免匹配到这些文件。实在不行,就加个超时机制(不过 lint-staged 本身不支持 timeout,得自己封装脚本)。
2. Prettier 和 ESLint 冲突:比如 ESLint 要求单引号,Prettier 用双引号。这时候先 fix 再 format 会导致格式来回震荡。解决方案是用 eslint-config-prettier 关掉 ESLint 中和 Prettier 冲突的规则,让 Prettier 成为唯一格式权威。我现在的项目都这么干,省心。
3. 新人 clone 项目后 pre-commit 不生效:这是因为 husky 的 hook 没装上。Husky v7+ 要求运行 husky install 才会生成 hooks。我通常会在 package.json 的 prepare 脚本里加上:
{
"scripts": {
"prepare": "husky install"
}
}
这样每次 npm install 后都会自动安装 hooks,避免新人漏掉这步。
4. monorepo 项目配置混乱:在 pnpm workspace 或 yarn workspace 里,每个 package 可能有自己的 lint 规则。这时候我建议在根目录统一配置 lint-staged,但命令要指向具体 package 的 script。比如:
{
"lint-staged": {
"packages/*/src/**/*.{js,ts}": "cd packages && eslint --fix"
}
}
不过更稳妥的做法是用函数形式,动态判断文件属于哪个 package,再执行对应命令。但这样配置复杂,我一般图省事,就要求所有子包共享同一套 lint 规则。
一点小技巧
有时候我想临时跳过 lint-staged,比如紧急修复一个 typo,不想等 ESLint 跑完。这时候我会用 git commit --no-verify。但团队里总有人忘记这个 flag,或者不知道。所以我在团队规范里明确写了:只有真正紧急的情况才能跳过,否则 PR 会被打回。
另外,我偶尔会遇到“明明改了文件,但 lint-staged 没触发”的情况。90% 的原因是文件没 git add。Lint-staged 只处理 staged 文件,unstaged 的改动它看不见。新手常犯这个错,以为改了就能触发,其实必须先 add。
最后,如果你的项目用了 TypeScript,记得在 ESLint 命令里加上 --ext .ts,.tsx,否则 TS 文件可能被忽略。不过我现在的配置直接用 glob 匹配 *.ts,所以没这个问题。
以上是我个人对 lint-staged 的完整实战总结,有更优的实现方式欢迎评论区交流。这个工具虽小,但配好了能极大提升团队代码一致性,配不好就是天天救火。希望我的踩坑经验能帮你少走点弯路。

暂无评论