原文地址:(https://blog.typicode.com/husky-git-hooks-javascript-config/)
一个关于我最喜欢 husky 的新功能的简单说明以及是什么促成了它。
背景
我已经为husky工作了七年,并且关闭了,在JavaScript项目中,600个和Git hooks相关的issues。在一月,随着新版本的发布,大约100个 issues 被关闭了。
从V0开始,husky使用JavaScript文件配置Git 钩子,它既可以是.huskyrc.js文件,也可以是package.json文件中的字段。
现在Git钩子是使用 .husky/ 目录下的单个钩子文件配置的。
为什么突然变化这么大?
万事万物都在不停地变化和进步。Js生态已经改变了(引进了yarn,monorepos变成了趋势,出现了新的工具和最佳实践,甚至npm都有些许不同了...),同时Git也引进了一个新的令人激动的特性。
在谈论新的配置方法和它的优点时,让我们先看看husky到目前为止所采取的方法。
husky是怎么样工作的
在Git上,配置一个钩子,使用一种非常linux的方式,你只需要简单地将一个可执行的文本文件放到.git/hooks目录下。
为了能够运行用户在.huskyrc.js文件中创建的任何Git钩子,husky必须在运行之前,在.git/hooks/目录下,安装所有可能的Git钩子。
例如,当一个commit被生成的时候,每一个Git钩子都会去检查,在.huskyrc.js文件中是否有与之相应的钩子被定义。
$ 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
它的优点是:用户能够通过.huskyrc.js文件新增,更新和删除Git 钩子,并且相应的改变也会自动生效。
缺点是,即使没有任何东西需要运行,node也会被启动。
你可能会问,难道我们就不能只安装我们所需要的钩子吗?三年前我们就有这个想法,但是husky再也不会自动“工作”了。
例如,让我们考虑下面的配置:
// .huskyrc.js
module.exports = {
hooks: {
'pre-commit': 'cmd'
}
}
.git/hooks/pre-commit ← is somehow created
然后你再修改它:
// .huskyrc.js
module.exports = {
hooks: {
// 'pre-commit': 'cmd', ← 移除
'commit-msg': 'cmd' ← 新增
}
}
但现在,你的 .git/hooks 目录是不一致的。
.git/hooks/pre-commit ← 仍然存在
.git/hooks/commit-msg ← 不存在
因为你的钩子不是在同一个地方定义的,而是两个地方(.huskyrc.js和.git/hooks/),因此你需要一种机制来保持Js世界和Git世界的同步。
每一次.huskyrc.js改变时,你(和你的合作者)都需要去重新生成钩子文件。重新生成这件事,可以绑定在某些事件上,但是却没有一种可靠的方式,去覆盖所有可能的案例,同时这还可能会出现一些意想不到的事情。这就是为什么这种方式被驳回了。
husky的新方法
当你的抽象有问题的时候,它通常是需要退后一步的信号。
到目前为止,我们知道哪些?
- 根据设计,Git钩子的配置是通过可执行脚本完成的。
- Husky初始的化的方法是间接的,并且还有点神奇。
- 从Js配置中生成Git钩子可能会不同步的。
我们可否利用Git做得更好?
在2016年,Git 2.9 引进了core.hooksPath配置。它让你告诉Git,不要使用默认的.git/hooks/,而是另外一个目录。
下面几点就是新版本husky构建的基础:
- husky install命令,告诉Git使用.husky/目录作为Git的钩子目录。
- husky add命令,创建一个轻量级包装的shell脚本,以支持某些额外的特性。
它既解决了第一个问题(额外的Git钩子),也解决了第二个(在同一个地方定义你的Git 钩子)。
就是说,当你用新版husky创建一个Git钩子的时候,它是一个纯碎的shell脚本并且能够直接运行。在Git和你直接已经不再有任何其他东西。我发现它很漂亮。
但是...
- 有目录这种配置方式并不常见
做这个决定,是因为其他工具也使用目录去存储配置。例如,一个仓库可能会有下列目录.vscode/, .github/, .storybook/, …
技术上讲,它仅仅只是你目录树中另一个入口,一个 .husky/ 目录不会比.husky.js文件更加杂乱。
这也是Git钩子设计的方式,以及husky遵循这种方式的原因。
您也可以选择将.husky/目录放在任何地方。例如,它可以放在 config/ 目录下,和其他配置文件放到一起。
- “Js工具像,Jest、ESlint、Prettier...都是用Js配置文件配置的,husky也是个Js工具”
你能用.jestrc.js, .eslintrc.js, .prettierrc.js配置,这是有意义的,因为它们完全是用Js写的。
但是husky是个特列,它不是纯碎的Js工具。它是“混合的”。它是用来和一个非Js,并且已经有一种配置方式的工具来交流的。
- 设置起来肯定很困难
husky自带有一个 init 命令(推荐),你可以很好地开始。
$ npx @husky/init # done!
这就是说,手动设置husky只需完成一次。您必须在package.json中更改一行,并且运行一个命令去增加一个钩子(这就是全部)。
这和 jest, eslint 有着相同的步骤,添加命令到package.json文件中,然后创建一个配置文件.jestrc.js, .eslintrc.js, …
(不过有一个例外,如果您正在发布一个包,并且同时使用yarn v2,它需要改变三行)。
- 已经有了core.hooksPath,为什么还需要husky
Husky基于上个版本的反馈,提供了一些安全保护。用户友好的错误信息和一些额外的特性。它在完全的native和一点点用户友好上取得平衡。
- 迁移将会很复杂
有一个 husky-4-to-6 命令行工具会自动替你完成这件事。
但是我仍然想在package.json中定义钩子
好消息是,没人会阻止你这么做,实际上它非常简单,执行下面的命令:
$ npx husky add .husky/pre-commit "npm run pre-commit"
在你的package.json文件中创建一个pre-commit script:
// package.json
{
"scripts": {
"pre-commit": "npm test && eslint"
}
}
你已经完成了定义。
结论
我希望它能让事情变得更加清晰。几个月来,整个版本都在仔细地考虑,怎么提供访问Git 钩子最好的方法,以及在保持husky4主要特性时,具有最好的扩展性。
总体来说,它更加符合Git哲学和包管理器的建议。
用Js格式配置纯Js工具是有意义的,毕竟是同一种语言。
然而,当有些东西是 native 的,使用Js仅仅作为一个中间工具去定义或者运行Git钩子,对我来说,感觉有点诡异。就像用钳子夹着锤子去钉钉子....,你只是需要一把锤子。
感谢您的阅读,我理解,他非常新颖。假如您还不是很确定,请考虑给它五分钟。
我已经离开了工作岗位,去从事开源工作。非常感谢那些赞助这个版本的人或公司,以及那些开始使用它的项目。
2021年3月26日。
本文暂时没有评论,来添加一个吧(●'◡'●)