1. lint-staged 到底在解决什么问题
很多团队做前端工程化,第一个想引入的“门槛工具”往往是 lint-staged。原因很简单:它解决的是代码检查效率里最扎心的一个场景——我明明只改了三个文件,为什么要等全项目的 ESLint 跑完?更别说老项目里那几千条历史 warning,一提交就是一堆噪音。lint-staged 的做法很直接:把所有检查只钉在 Git 暂存区的文件上,你提交什么,就检查什么。
我第一次接触这个概念的时候其实挺困惑的:lint-staged 不过是一个 npm 包,它凭什么能插手 Git 的行为?后来把原理搞清楚之后才发现,它本质上是一个“聪明的命令组织器”——通过读取暂存区文件列表,把你要运行的检查命令逐条拼装出来,传给这些文件的路径,然后在 pre-commit 阶段执行。整个过程不依赖任何魔法,只是把工程化里的“范围控制”做对了。
1.1 全量检查的疼痛,做过前端工程化的人都知道
我先说一个特别典型的场景。项目从三五个文件长到三五百个文件,团队里任何一次git commit都要先跑一遍全量 ESLint。如果项目是老的 Vue 2 + JavaScript 技术栈,中途又引入了 TypeScript,那情况就更“酸爽”了:历史代码里几百条any、no-unused-vars、vue/no-mutating-props这类 warning,你根本不敢开--fix,因为可能把老祖宗的业务逻辑顺手改坏。
全量检查带来的问题不只是慢,更致命的是“噪音淹没信号”。新代码本来只有三五个规范问题,被历史 warning 一冲,开发者在提交信息里看到的全是和自己无关的报错。久而久之,团队对代码检查这件事产生了强烈的抗拒心理——反正也跑不完、跑了也是别人的错,不如直接跳过。代码规范就这么名存实亡了。
还有个容易忽略的成本:CI 流水线里的 lint 环节。全量 lint 在大仓库里动辄几分钟起步,一旦失败还得回退重跑,提交队列直接堵死。我见过最夸张的案例,一个中台项目全量跑完 ESLint 加 Stylelint 需要十一分钟,结果一半的时间是在处理别人的历史债务。这种状态下,lint 已经不是质量保障,而是研发效率的杀手。
1.2 lint-staged 的核心设计思路
lint-staged 把问题重新定义了一下:我们真正要保证质量的,不是“整个仓库历史上所有代码”,而是“这一次提交带来的变更”。于是它把检查范围压缩到 Git 暂存区(Staged Area)里那些文件上,配合 husky 之类的 Git hooks 工具,在pre-commit这个时间点把规则“精准打击”。
这个思路的优势非常明显。第一是快,你改了 5 个文件,它就只检查这 5 个,100 个项目文件剩下的 95 个完全不碰。第二是噪音少,历史 warning 被天然隔离,新代码的问题一目了然,开发者不会因为海量报错而无所适从。第三是安全,检查范围小意味着误判和误修的概率也低,--fix这类自动修复命令可以放心配置。
当然,lint-staged 不是用来替代全量检查的。它更适合做“提交前守门员”,而全量 CI 检查依然应当保留,作为兜底防线。这个工具的核心价值,是在开发者的即时反馈与团队规范之间找到一个性价比最高的平衡点。我经常跟团队里的人说:如果只能给提交环节加一个守门工具,那一定是 lint-staged,因为它把“检查”这个动作对开发效率的干扰降到了最低。
2. 先把 Git 暂存区和钩子机制拎清楚
要真正理解 lint-staged,绕不开 Git 的两个基础概念:暂存区(stage/index)和 hooks。很多人用了几年 Git,天天git add、git commit,但从来没想过这背后到底是什么在起作用。我习惯用一个“三间房”的类比来讲这事。
2.1 工作区、暂存区、仓库之间的“三间房”
把你的项目目录看作一间办公室。工作区是办公桌,你的源代码文件摊在这里,怎么改都行;暂存区是放在门边的“待发货纸箱”,git add就是把文件从桌面放进纸箱;仓库则是楼下的仓库货架,只有git commit才会把纸箱里的东西正式入库建档。
日常开发中很多人有一个误区:以为git commit会把工作区里“所有修改过的东西”一起提交。实际上 Git 只认纸箱里的东西——也就是git add过的内容。你工作区里改了一半的文件,如果没有git add,提交时是进不去的。lint-staged 盯的正是这个纸箱,它通过读取暂存区文件列表,确定“这次提交要带走哪些源码文件”,然后只对这批文件做检查。
理解这个逻辑之后你会明白一件事:lint-staged 和“你工作区里有多少未提交的改动”没关系,它只看你已经git add的内容。如果你改了 10 个文件但只 add 了 3 个,它检查的也只有这 3 个。这既是优势,也是不少人踩坑的地方——明明改了文件,但忘了git add,lint 跑了一通空气。
2.2 pre-commit 钩子:什么时候触发 lint-staged
Git 本身提供了一套 hooks 机制,也就是在特定动作发生时自动执行自定义脚本。.git/hooks/目录下有一堆.sample结尾的模板文件,比如pre-commit.sample、post-commit.sample。它的工作逻辑很简单:当你执行git commit时,Git 会在正式提交前调用pre-commit钩子,这个钩子如果退出码非 0,提交就会被中断。
lint-staged 本身不负责触发,它只是被调用的那个“执行者”。我们真正配置的是pre-commit钩子里的那一行npx lint-staged。手动写 hooks 当然也可以,但 hooks 脚本不在版本控制里,团队成员 Clone 下来之后是缺失的。所以更常见的做法是用 husky 这类工具来管理 hooks 脚本,husky 把钩子内容写入.husky/目录并纳入 Git 版本控制,团队成员安装依赖时通过prepare脚本自动激活。
关于 hooks 有一点要特别注意:如果在钩子脚本里改了文件(比如eslint --fix或prettier --write),这些改动默认不会自动进入本次提交。lint-staged 在 v10 之后的版本会自动帮你把修改后的文件重新git add,但如果你只写了原生 pre-commit 脚本,就必须手动处理这一步。这也是很多人从“手写 hooks”转向 lint-staged 的直接原因之一。
3. 安装配置与环境准备
配置 lint-staged 之前,先保证你的 Git 基础环境是干净的。这不是废话——我在实际排查问题的时候发现,很多“lint-staged 不生效”的案子,最后都查到了 Git 版本过旧、Node 版本不匹配、husky 钩子根本没装上这些“前置问题”上。
3.1 安装与版本选择
在一个标准的 npm 项目里,安装 lint-staged 只需要一条命令:
npm install --save-dev lint-staged husky如果项目用的是 pnpm 或 yarn,命令对应的改成pnpm add -D或yarn add -D即可。这里我把 husky 一并装上了,因为 lint-staged 本身不是一个常驻进程,它需要一个触发时机,而 pre-commit 钩子是它最常见的舞台。
版本选择上有个小知识点:lint-staged 对 Node 版本有要求,新版(比如 v15+)通常需要 Node 20 以上,老项目如果 Node 还停留在 16 或 18,记得安装前看一眼engines字段。另外,如果你是从老项目升级上来的,注意 v10 前后的配置格式差异很大,最明显的变化是命令从“字符串拼接”改成了“数组”,并且任务执行后会自动把修改的文件重新git add。这部分我在后面的配置示例里会专门讲到。
3.2 配置文件怎么写:三种载体
lint-staged 的配置可以放在三个位置:package.json 里的lint-staged字段、独立的.lintstagedrc文件、以及lint-staged.config.js。三者的优先级和写法略有差别,但最终解析出来的数据结构是一样的。
以 package.json 为例,最常见的长这样:
{ "lint-staged": { "*.{js,ts}": "eslint --fix", "*.{json,md,css,scss}": "prettier --write" } }这是一个“键为 glob 匹配模式、值为要执行命令”的映射表。这里有个 v10 之后非常重要的细节:值推荐写成数组,而不是字符串。理由是 lint-staged 把“每个数组元素”当成一条独立的命令来解析,如果你写成"eslint --fix && prettier --write",一大串逻辑会被当成单个命令传给 shell,跨平台行为不一致且容易出错。
推荐的数组写法是:
{ "*.{js,ts}": ["eslint --fix", "prettier --write"] }由于数组里的命令会按顺序执行,多条命令之间天然就是“串行”关系,完全不需要自己拼&&。如果你需要更复杂的逻辑——比如根据文件列表动态生成命令,也可以把值写成一个函数:
// lint-staged.config.js export default { "*.ts": (filenames) => filenames.map((file) => `tsc --noEmit --pretty false ${file}`) }函数接收匹配到的文件路径数组,返回一个命令字符串或字符串数组。这种写法比较灵活,适合处理tsc --noEmit这类“一次只能处理有限文件”的工具,我后面细说。
3.3 常用配置项与参数释义
除了 glob 映射表,lint-staged 本身还提供了一些顶层配置项,用来控制它的执行行为。我用得比较多的有这几个:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
concurrency | boolean/number | true | 是否并发执行多个任务,也可指定并发数量 |
shell | boolean/string | false | 是否通过 shell 执行命令,管道、重定向需要设为 true |
maxArgLength | number | 0 | 每个命令分块执行的最大参数长度,防止 E2BIG |
relative | boolean | false | 传给命令的文件路径是否使用相对路径 |
quiet | boolean | false | 静默模式,只输出报错不输出任务信息 |
debug | boolean | false | 输出调试日志,排查问题时最有用 |
allowEmpty | boolean | false | 当没有匹配到暂存文件时,是否仍然正常退出 |
这些参数可以直接写在 CLI 上,比如npx lint-staged --debug,也可以放进配置文件里统一管理。注意concurrency默认是 true 不是 1,这意味着如果你的多个任务之间有依赖关系(比如先 prettier 再 eslint),可能会因为并发执行而出问题。不过实际使用中,同一个 glob 匹配到的多个命令是按数组顺序执行的,并发只发生在“不同 glob 规则”之间,这个要理清楚。
还有一个隐藏参数--diff,它允许你改变 lint-staged 的“检查范围基准”。默认情况下它只看暂存区(即git diff --staged的内容),但你可以手动指定一个提交区间,比如npx lint-staged --diff HEAD~3 HEAD,这样就能检查最近三次提交涉及的文件。这个能力在 CI 里做“增量检查”很好用,不过日常本地提交基本用不到。
4. 核心机制拆解:它怎么做到“只查暂存区”
lint-staged 的神奇之处,不在于它有多复杂的算法,而在于它把一个常见需求做得很周全。它的整个处理流程可以概括为三步:先拿到暂存区文件列表,再用 glob 模式筛选出需要检查的部分,最后把匹配到的文件路径作为参数传给对应命令。
4.1 获取暂存区文件列表的原理
第一步是关键。lint-staged 本质上是在调用 Git 命令获取暂存区文件清单。原理上等价于执行:
git diff --name-only --cached --diff-filter=ACMR--cached(也就是--staged)告诉 Git 只展示已经加入暂存区的文件,而不是工作区里所有改动的文件。--diff-filter=ACMR的含义是只保留类型为 Added(新增)、Copied(复制)、Modified(修改)、Renamed(重命名)的文件,把 Deleted(删除)的文件过滤掉。
这里有一个很多人不知道的细节:为什么不直接git diff HEAD?因为git diff HEAD比较的是工作区与 HEAD 之间的差异,它会把“已经 add 的”和“改完但还没 add 的”混在一起。而 lint-staged 要的是“纸箱里装了什么”,所以必须用--cached。另外,首次提交时 HEAD 还不存在,git diff HEAD会直接报错,而git diff --cached在这种情况下会退化成与空目录树比较,正常列出所有暂存文件。lint-staged 选择这个命令,天然兼容了新仓库的第一个 commit。
拿到文件列表之后,lint-staged 还会做一次过滤,把 Git 不关心的路径、子模块相关的文件等边界情况处理掉。这部分底层逻辑在版本迭代中有过多次调整,但对我们使用者来说,只需要记住一件事:它拿到的列表,一定都是“本次提交真正会带走的源码文件”。
4.2 glob 匹配规则:模式不是正则
第二步是 glob 匹配。lint-staged 的配置键遵循 glob 语法,底层用的是 micromatch 这个库。glob 的语法和正则有点像但又不完全一样,第一次接触的人最容易在这里犯迷糊。
一种常见的误解是把*.js理解为“只匹配根目录下的 js 文件”。在 lint-staged 的语义里,*.js会匹配仓库内所有层级的.js文件,包括src/foo/bar.js和packages/app/index.js。这是它源于 micromatch 的matchBase行为。如果你真的只想匹配根目录下的文件,需要写成/*.js。
**表示递归匹配任意层级,{js,ts,tsx}是花括号展开,!开头表示排除。举个例子:
{ "**/*.{js,ts,tsx}": ["eslint --fix"], "!**/*.min.js": [] }上面第一条匹配所有源码目录下的 js/ts/tsx 文件;第二条用空数组把*.min.js排除掉,空数组表示“匹配到但不执行任何命令”。这种写法在维护遗留项目时特别有用,可以精准跳过那些历史生成的压缩文件。
另外需要注意,glob 匹配是基于“仓库根目录”的相对路径来做的,不是基于你执行命令时所在的目录。这点在 monorepo 场景下尤其重要,我见过一个团队在子包目录里跑npx lint-staged,结果匹配规则全部失效,因为路径基准变了。
4.3 任务分发与自动暂存
拿到匹配的文件列表之后,lint-staged 就会开始“干活”了。它会按照配置文件里定义的顺序,把文件路径分批传给对应的命令。比如你配置了"*.{js,ts}": ["eslint --fix", "prettier --write"],那么假设暂存区里有 10 个 js 文件,lint-staged 会执行:
eslint --fix file1.js file2.js ... file10.js prettier --write file1.js file2.js ... file10.js这里有一个工程上的细节:如果文件数量太多,命令行参数就会超出系统限制,报 E2BIG 错误。lint-staged 的解决办法是使用maxArgLength配置,把文件列表拆成多个小块分别执行。默认值 0 表示不限制拆分,但如果你遇到 E2BIG 问题,把它设置成比如 1000,就能自动分块。
整个执行过程中还有一个“隐式操作”很容易被忽略:自动重新暂存。.js文件经过eslint --fix后内容大概率会变,如果 lint-staged 不处理,这些修复后的改动就不会被包含进本次提交,你又得手动git add。v10 之后的版本默认会在任务全部成功后自动执行git add,把被修改的文件重新放回暂存区。这个行为非常关键,也是很多老教程里“要在命令里手动拼git add”的做法如今已经不推荐的原因。
5. 完整实操:搭一条 pre-commit 流水线
理论知识聊完了,下面进入动手环节。我会从零开始搭一套前端项目常用的 pre-commit 检查流程,每一步都给出可以直接复制的命令和配置,同时解释每一步背后的选择。
5.1 五分钟快速跑通
假设你已经有一个 npm 项目,并且初始化了 Git 仓库。下面的步骤可以复制粘贴执行:
# 1. 安装依赖 npm install --save-dev lint-staged husky # 2. 初始化 husky,生成 .husky 目录 npx husky init # 3. 在 pre-commit 钩子中写入 lint-staged echo "npx lint-staged" > .husky/pre-commit做完这三步,一个最基础的 pre-commit 检查就搭好了。为了验证它真的在起作用,我们再往 package.json 里加一段配置:
{ "scripts": { "lint": "eslint . --ext .js,.ts" }, "lint-staged": { "*.{js,ts}": ["eslint --fix"] } }然后做一次真实的提交测试:
# 修改一个 js 文件,故意留下一个 no-unused-vars 错误 git add . git commit -m "test: lint-staged"如果配置正确,你会看到 ESLint 报错信息,并且 commit 被中断。把错误修掉之后再次git add和git commit,提交才能成功。这一步跑通,说明你的 pre-commit 流水线已经工作起来了。
有一个很容易踩的坑:因为eslint --fix可能会改动文件,如果刚才那次提交恰好被中断了,修复后的文件需要再次git add。lint-staged 会自动暂存它运行时产生的改动,但一旦因为报错中断,之后的“修复 + add”流程还是得你手动来一遍。
5.2 配合 husky 的正确姿势
husky 的用法在 v5 之后有了很大变化。老版本用的是.huskyrc.json,在配置里写:
{ "hooks": { "pre-commit": "lint-staged" } }新版本换成了文件系统的方式,钩子脚本直接放在.husky/目录下,内容就是普通的 shell 脚本。npx husky init会自动帮你生成.husky/pre-commit,里面默认内容一般是npm test之类的占位符,我们把它覆盖成npx lint-staged即可。
在团队协作时需要注意一个问题:.husky/目录必须提交到 Git 仓库,否则新成员 clone 下来不会有钩子。由于 husky 的prepare脚本会在npm install时自动执行,所以新成员只要正常安装依赖,钩子就会自动就位。
还有一个关于 “hard reset 之后钩子丢失” 的经典问题:如果你或你的同事曾经运行过git reset --hard,.husky目录里的可执行权限可能会丢。解决办法是在项目 scripts 里加一个"prepare": "husky",然后在出问题时执行一次npm run prepare重新激活。
5.3 多语言多工具混合场景
现实中的项目很少只有一种检查工具。前端项目通常是 ESLint + Prettier + Stylelint 的组合;如果用了 TypeScript,可能还想在提交时做一次快速的类型检查;加上后端部分,可能还有 golangci-lint、ruff、gofmt 等工具。lint-staged 最舒服的地方就在这里,它不管工具链有多杂,只看文件后缀。
一个比较典型的前后端混合项目配置长这样:
{ "lint-staged": { "*.{js,ts,tsx,vue}": ["eslint --fix", "prettier --write"], "*.{css,scss,less,vue}": ["stylelint --fix", "prettier --write"], "*.go": ["gofmt -w", "goimports -w"], "*.{md,json,yml,yaml}": ["prettier --write"] } }这个配置把不同类型的文件交给不同的工具链处理,每种文件只会被匹配到的规则执行。有个细节值得注意:.vue文件同时出现在第一和第二条规则里,这是故意的——Vue 单文件组件里既有 JS/TS 逻辑又有 CSS 样式,需要 ESLint 和 Stylelint 都跑一遍。
关于 TypeScript 类型检查,这里必须多说一句。很多团队会这样配:
{ "*.ts": ["tsc --noEmit"] }但这个配置在大部分场景下会报错。原因很简单:tsc的单文件模式和--noEmit一起使用时,并不能只检查传入的那几个文件——它会把整个项目的类型依赖关系都拉进来,结果要么极其慢,要么直接报“Cannot find module”这类错误。正确的做法有两种:要么在 pre-commit 里直接跑全量tsc --noEmit(适合中小项目),要么用 lint-staged 的函数配置,把文件列表批量传入一个自定义的tsc -p脚本做个增量检查。我个人更推荐把类型检查放到 CI 里,而 pre-commit 只负责语法规范和格式化——毕竟类型的准确性,往往需要全项目上下文。
6. 常见问题与排查技巧实录
lint-staged 出现问题的场景,翻来覆去就那么几类。我把这几年在团队里排查过的典型案例整理成了一张速查表,方便你一眼定位。
6.1 高频报错速查表
| 现象 | 大概率原因 | 解法 |
|---|---|---|
| 提交时完全没有检查动作 | husky 钩子未安装,或.husky/pre-commit为空 | 执行npm run prepare,确认钩子内容包含npx lint-staged |
报No staged files match any configured task | 有暂存文件,但没有任何 glob 匹配到 | 检查 glob 写法,确认路径基准是仓库根目录 |
命令中用了||或&&不生效 | 默认不走 shell,||是给 shell 的语法 | 配置"shell": true,或改用数组分开写命令 |
| 报 E2BIG | 文件列表过长,命令行参数超出系统限制 | 设置"maxArgLength": 1000等分块参数 |
eslint --fix修改后未包含进提交 | 用的是老版本 lint-staged 或手动写了 post-checkout | 升级到 v10+,不要手动git add |
| 新成员 clone 后钩子不生效 | .husky/未提交,或prepare脚本缺失 | 确认.husky/入库,package.json 里加"prepare": "husky" |
| Windows 下命令执行异常 | 路径分隔符或 shell 解析差异 | 设置"shell": true,或避免在命令里写 shell 语法 |
6.2 我踩过的一些坑
第一个坑是“手动 git add 带来的重复暂存”。老版本 lint-staged 配置里,为了确保--fix修改后的文件能进提交,会这样写:
{ "*.{js,ts}": ["eslint --fix", "git add"] }如果你把这个老写法原封不动搬到新版本,会发现git add在 lint-staged 已经自动暂存之后又执行了一遍,偶尔会把目录里其他未暂存的文件也带进暂存区。遇到这种情况,直接把git add从配置里删掉就行。
第二个坑是二进制文件和图片。如果你把*.{png,jpg,webp}也配进了 prettier,轻则检查报错,重则把图片文件“格式化”成另外的二进制内容。我在一个团队里见过一次事故,就是误把图片文件交给 ESLint 检查,结果--fix把文件改坏了,最后只能从 Git 历史里恢复。遇到二进制资源,要么不配置规则,要么明确用!排除。
第三个坑是eslint --cache与 lint-staged 的搭配。--cache确实能让 lint 更快,但缓存文件如果生成在项目目录里,很容易被误提交。我建议配置ESLINT_USE_FLAT_CONFIG之外,还要把缓存文件路径指到.eslintcache并加入.gitignore。否则 lint-staged 看到暂存区里多了个缓存文件,又会跳出来匹配规则,把检查流程搅成一团。
6.3 一个完整的排查思路
如果你遇到“lint-staged 不生效”这种问题,我建议按下面的顺序排查,不要一上来就怀疑是 lint-staged 坏了。
第一步,先在命令行手动执行npx lint-staged,看它是否正常工作。如果这一步能跑通、能报错,说明 lint-staged 本身没问题,问题出在触发环节。第二步,检查.husky/pre-commit文件内容,确认里面有npx lint-staged;同时检查项目的prepare脚本是否存在。第三步,执行git config --list,确认 hooksPath 有没有被全局配置篡改过。如果还不行,用npx lint-staged --debug看它到底拿到了哪些文件、匹配了哪些规则。
这套排查流程几乎能解决 99% 的 “不生效” 问题。剩下那 1%,大概率是同事在自己电脑上全局装了一个老版本 husky 或 lint-staged,覆盖了项目内安装的版本。遇到这种“隐身变量”,让当事人检查一下全局 npm 包,问题往往立刻水落石出。
7. 进阶玩法与性能优化
lint-staged 用顺手之后,很多人会开始琢磨怎么让它更快、更符合团队习惯。这一节是一些相对进阶的玩法,属于那种“知道的人少、但效果立竿见影”的技巧。
7.1 让检查跑得更快
首先是最基础的优化:只让 lint-staged 处理必要的文件类型。很多项目的 package.json 里会写一大串规则,把"*.{js,ts,vue,json,md,yaml,css,scss,html}"全部粗暴地丢给eslint --fix——这既不合理也不高效。正确的思路是按工具划分职责,ESLint 只看代码逻辑,Prettier 管格式,Stylelint 管样式,各司其职,避免一个文件和所有工具都过一遍。
其次是用好concurrency和maxArgLength。多个任务之间默认并发执行,如果任务之间没有依赖关系,没必要强行串行。当碰到大数量文件时,合理设置maxArgLength让 lint-staged 分块执行,比让它一次扛下所有参数要稳定得多。我之前在一个 monorepo 里配置了 2000 作为阈值,几十个文件一次提交只花了不到三秒,体验很不错。
最后是充分利用--diff参数。这个参数虽然默认只处理暂存区,但如果你愿意,可以在 CI 里这样用:对每次 MR 触发的构建,执行npx lint-staged --diff origin/main...HEAD,只检查比起分支新增的那些文件。这样既不用给 CI 上全量 lint,又能保证每个 MR 的增量代码是干净的,速度还快得离谱。
7.2 团队落地建议
lint-staged 这种东西,一个人用是锦上添花,全团队用才能真正立住规范。落地时我有几条实操建议。
第一,锁版本。package.json 里把 lint-staged 和 husky 的版本写死,或者用锁文件固定住,不要让人天天“顺手升级”。这两个工具的配置格式在不同大版本之间差异不小,一旦有人悄悄升级,可能导致全团队的配置突然失效。
第二,把配置模板化。如果团队里有多个前端项目,建议把 lint-staged 的配置复制到每个项目里,或者抽成一个共享的配置文件(比如@scope/lint-config),通过包引用的方式统一维护。这样新项目开坑时,不用再对着旧项目的 package.json 扒拉半天的配置。
第三,做好失败时的体验。pre-commit 阶段检查失败会打断开发者,如果报错信息不友好,很容易让人产生抵触情绪。我的建议是:在 lint-staged 配置里加上--silent或者通过quiet参数减少输出噪音,同时在团队文档里写明“如果 pre-commit 检查挂了,第一件事是看报错文件路径,而不是直接--no-verify跳过”。其实无论你怎么引导,总会有人用--no-verify绕过检查,所以 CI 里的全量 lint 永远不能省。这是最后一道防线,也是 lint-staged 这种“轻量守门员”无法替代的地方。
在团队落地过程中,我个人最深的体会是:lint-staged 的配置一定不要贪多求全,先上 ESLint 和 Prettier,跑稳之后再逐步叠加其他工具。没有人会喜欢一个“每次提交要跑四个检查”的仓库,但如果是“每次提交只多花两秒钟,就能自动帮你修好格式”的流程,大家很快就会习惯它。工具存在的终极意义不是给开发流程添堵,而是让规范变成一种不打断心流的肌肉记忆——这才是 lint-staged 最值得花时间去调教的地方。