为什么有这篇文章
最近我为开源项目 tkestack
的前端控制台部分添加了对 commit 的 lint,在添加过程中遇到了一些困难和疑惑,在此记录一下
v4 配置方式
安装 husky
npm i husky -D
添加配置
在 package.json 中添加如下配置
//package.json
"lint-staged": {
"./**/*.{ts,tsx,js,jsx}": [
"eslint",
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
或者添加.huskyrc 文件
// .huskyrc
{
"hooks": {
"pre-commit": "lint-staged"
}
}
v6+ 配置方式
第一次使用,按照 husky doc 进行配置
安装 husky
npm i husky -D
开启 Git hooks
npx husky install
执行到这里,我得到一个错误
npx husky install
.git can't be found (see https://git.io/Jc3F9)
可以推断出原因是这条命令需要在项目根目录执行。即.git
所在的目录
tkestack 是一个前后端同在一个仓库的项目,目录结构如下
.
└── web
└── console
我们现在在 console 目录,所以正确的方式应该是
cd ../../
npx husky install ./web/console/.husky
添加自动执行husky install
的钩子
如果其他用户进行开发,显然我们是不希望他需要先阅读 husky 的文档,然后手动执行npx husky install
的,所以我们添加一个 npm scripts 的钩子
npm set-script prepare "husky install"
由于目录的原因,对应的调整为
npm set-script prepare "cd ../../ && husky install ./web/console/.husky"
注意 ⚠️:npm set-script
只在 npm version 7.x 生效,如果你用的是 6.x 或者更低,还是手动在package.json
中添加吧
现在查看package.json
应该可以看到
// package.json
{
"scripts": {
"prepare": "cd ../../ && husky install ./web/console/.husky"
}
}
添加我们的 hook
npx husky add .husky/pre-commit "lint-staged"
测试一下
修改一个文件使之不符合 eslint 规范,然后我们做一次提交
git add .
git commit 'test husky'
结果居然成功提交了,显然我们的 lint 没有生效
我们检查一下现在我们的 console 目录有哪些变化,会发现多了一个.husky 目录,目录下有一个pre-commit
文件,文件内容如下:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
lint-staged
大致可以猜测到当 pre-commit 时,实际是执行了这个 shell 脚本,由于我们的 lint-staged 并不是全局安装,在这里并不能执行 所以我们在 package.json 中添加一个 script
// package.json
{
"scripts": {
"prepare": "cd ../../ && husky install",
"pre-commit": "lint-staged"
}
}
接着修改.husky/pre-commit
文件
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run pre-commit
再尝试一下修改一个文件并提交
还是成功了,lint 依然没有生效,
查看文档可知pre-commit
这个 shell 实际执行目录是在.git
所在目录,所以接着修改.husky/pre-commit
文件
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd ./web/console
npm run pre-commit
最后再次修改文件并 commit,终于可以看到命令行执行了lint-staged
,lint 失败,commit 未成功
为什么从 v4 到 v6
很显然,v6 的配置 相比 v4 复杂了很多,那么为什么 husky 官方要修改之前的配置方式呢? 要了解这个问题之前我们先看一下 husky v4 的工作方式
husky@v4 是如何运行的
首先 husky 在安装时会在 .git/hooks/ 目录安装所有可能会使用到的 hooks
比如当我们进行一次提交时,每个 git hook 检查我们有没有在 package.json 或者.huskyrc 中定义对应的钩子:
$ git commit
pre-commit (native) → husky/runner.js (node)
→ is a pre-commit defined in `.huskyrc.js`? → YES, run it
prepare-commit-msg (native) → husky/runner.js (node)
→ is a prepare-commit-msg defined in `.huskyrc.js`? → NO, do nothing
commit-msg (native) → husky/runner.js (node)
→ is a commit-msg defined in `.huskyrc.js`? → NO, do nothing
post-commit (native) → husky/runner.js (node)
→ is a post-commit defined in `.huskyrc.js`? → NO, do nothing
优点: 用户可以在配置文件中添加、更新、删除 hook,husky 会自动判断执行
缺点: 不管我们有没有配置 hook,所有的 git hook 都会执行一遍,包括执行 node husky/runner.js
husky@v6 是如何运行的
2016 年,Git 2.9 引入了core.hooksPath
,他使用户可以自定义 git hooks 的目录,在此之前,hooks 只能放在.git/hooks/目录下,然而.git 目录是不能提交的,但是现在配置core.hooksPath
之后,比如 husky 使用husky install
配置 .husky/作为 git hooks 目录,我们可以提交.husky/目录作为项目的一部分。
- 优点:
- 避免了创建和运行所有 git hooks
- 现在 git hooks 和 husky 配置文件实际上是一个文件,方便管理
- 缺点:
- 配置复杂繁琐
参考
Why husky has dropped conventional JS config
2021.07.21 补充
有后端同学遇到了无法提交代码的问题,具体表现是修改了后端代码,然后提交,发现触发的 husky 的 commit 钩子,经过排查是后端同学在前端目录执行了 npm install,导致挂载了 husky 的钩子 ,解决方案:
git config --unset core.hooksPath
因为 v6 版本的 husky 的原理就是设置了 core.hooksPath, 所以我们重置它就好了