# 十二.Git Hooks

前言

Git 在代码版本管理之外,也提供了类似 npm script 里 prepost 的钩子机制,叫做 Git Hooks (opens new window),钩子机制能让我们在代码 commit、push 之前(后)做自己想做的事情

通过 npm script 为本地仓库配置了 pre-commit、pre-push 钩子检查,远程仓库(Remotes (opens new window))配置 pre-receive 钩子检查。两种钩子的检查目的各不相同,本地检查是为了尽早给提交代码的同学反馈,哪些地方不符合规范,哪些地方需要注意;而远程检查是为了确保远程仓库收到的代码是符合团队约定的规范的,因为如果没有远程检查环节,熟悉 Git 的同学使用 --no-verify(简写为 -n) 参数跳过本地检查时,本地检查就形同虚设。

那么增加 Git Hooks 的必要性聊清楚了,我们应该在 Git Hooks 里面做哪些事情呢?通常来说:检查编码规范,把低级错误趁早挖出来修好;运行测试,用自动化的方法做功能回归,测试本身就包含很多话题,且按下不表。

前端社区里有多种结合 npm script 和 git-hooks 的方案,比如 pre-commit (opens new window)husky (opens new window),相比较而言 husky 更好用,它支持更多的 Git Hooks 种类,再结合 lint-staged (opens new window) 试用就更溜。

# 1. 安装项目依赖

使用如下命令安装 husky、lint-staged 到项目依赖中:

npm i husky lint-staged -D
# npm install husky lint-staged --save-dev
# yarn add husky lint-staged -D
1
2
3

husky 的基本工作原理可以稍作解释下,翻看 husky 的 package.json (opens new window),注意其中的 scripts 声明:

  "scripts": {
    "test": "jest",
    "format": "prettier --single-quote --no-semi --write **/*.js",
    "install": "node ./bin/install.js",
    "uninstall": "node ./bin/uninstall.js"
  },
1
2
3
4
5
6

这里面的 install 就是你在项目中安装 husky 时执行的脚本(所有的魔法都藏在在这里了,哈哈)。

然后再检查我们仓库的 .git/hooks 目录,会发现里面的钩子都被 husky 替换掉了,注意下图中三个红色框中的内容:

# 2. 添加 npm script

接下来需要在 scripts 对象中增加 husky 能识别的 Git Hooks 脚本:

   "scripts": {
+    "precommit": "npm run lint",
+    "prepush": "npm run test",
     "lint": "npm-run-all --parallel lint:*",
     "lint:js": "eslint *.js",
1
2
3
4
5

这两个命令的作用是在代码提交前运行所有的代码检查 npm run lint;在代码 push 到远程之前,运行 lint 和自动化测试(言外之意,如果测试失败,push 就不会成功),虽然运行的是 npm run test,但是 lint 也配置在了 pretest 里面。

然后尝试提交代码:git commit -am 'add husky hooks',能看到 pre-commit 钩子已经生效:

# 3. 用 lint-staged 改进 pre-commit

如上的配置乍看起来没有任何问题,但是在大型项目、遗留项目中接入过 lint 工作流的同学可能深有体会,每次提交代码会检查所有的代码,可能比较慢就不说了,接入初期 lint 工具可能会报告几百上千个错误,这时候估计大多数人内心是崩溃的,尤其是当你是新规范的推进者,遇到的阻力会增大好几倍,毕竟大多数人不愿意背别人的锅,坏笑。

好在,我们有 lint-staged 来环节这个问题,每个团队成员提交的时候,只检查当次改动的文件,具体改动如下:

   "scripts": {
-    "precommit": "npm run lint",
+    "precommit": "lint-staged",
     "prepush": "npm run test",
     "lint": "npm-run-all --parallel lint:*",
   },
+  "lint-staged": {
+    "*.js": "eslint",
+    "*.less": "stylelint",
+    "*.css": "stylelint",
+    "*.json": "jsonlint --quiet",
+    "*.md": "markdownlint --config .markdownlint.json"
+  },
   "keywords": [],
1
2
3
4
5
6
7
8
9
10
11
12
13
14

接下来我们故意在 index.js 中引入错误:

-  return NaN;
+  return NaN
1
2

然后尝试提交这个文件:git commit -m 'try to add eslint error' index.js,结果如下图:

上图中带有 Running Tasks 字样的列表就是 lint-staged 根据当前要提交的文件和 package.json 中配置的检查命令去执行的动态输出。红色框里面提示 husky 的 pre-commit 钩子执行失败,提交也就没有成功。

关于 lint-staged 还有些高级的用法,比如对单个文件执行多条命令,对单个文件动态自动修复,自动格式化等等,留待大家自己去探索好了。

撤销掉有错误的修改,提交之后,我们往远程 push 新分支,结果如下图: