发布于 

前端工程化配置(上) 构建代码检查工作流:husky + lint-staged 配置

前言

在团队开发时,为了保证每个人提交的代码格式统一,采用 husky + lint-staged 配置 git hooks,自动触发格式化操作,对通过 git add 命令添加到暂存区的代码进行格式化。

概念

什么是 git hook

在介绍 husky 之前,我们先来看什么是 git hook,也就是常说的 Git 钩子。

和其它版本控制系统一样,Git 能在特定的重要动作发生时触发自定义脚本。有两组这样的钩子:客户端的和服务器端的。 客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。 你可以随心所欲地运用这些钩子。

其中,客户端钩子我们可能用的比较多,客户端钩子通常包括了提交工作流钩子、电子邮件工作流钩子和其它钩子。这些钩子通常存储在项目的 .git/hooks目录下,我们需要关注的主要是提交工作流钩子。提交工作流钩子主要包括了以下四种:

  • pre-commit:该钩子在键入提交信息前运行。 它用于检查即将提交的快照。如果该钩子以非零值退出,Git 将放弃此次提交,你可以利用该钩子,来检查代码风格是否一致。

  • prepare-commit-msg:该钩子在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。

  • commit-msg:该钩子接收一个参数,此参数存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。

  • post-commit:该钩子一般用于通知之类的事情。

在上面的钩子中,我们需要关注 pre-commitcommit-msg 钩子。

什么是 husky

husky 是常见的 git hook 工具,使用 husky 可以挂载 Git 钩子,当我们本地进行 git commitgit push 等操作前,能够执行其它一些操作,比如进行 ESLint 检查,如果不通过,就不允许 commitpush

什么是 Lint-staged

pre-commit hook 中,一般来说都是对当前要 commit 的文件进行校验、格式化等,而不是对全局文件校验,因此在脚本中我们需要知道当前在 Git 暂存区的文件有哪些,而 Git 本身也没有向 pre-commit 脚本传递相关参数,Lint-staged这个包为我们解决了这个问题。简单说就是当我们触发 pre-commit hook 中的脚本命令后,配合 Lint-staged 的配置可以只检查暂存区的文件从而避免我们每次检查都把整个项目的代码都检查一遍的尴尬情况。其次,Lint-staged允许指定不同类型后缀文件执行不同指令的操作,并且可以按步骤再额外执行一些其它 shell 指令。

lint-staged 是如何知道当前暂存区有哪些文件的?

事实上,lint-staged 内部也没有什么黑魔法,它在内部运行了 git diff --staged --diff-filter=ACMR --name-only -z 命令,这个命令会返回暂存区的文件信息,类似如下所示的代码:

1
2
3
4
5
6
7
const { execSync } = require('child_process');
const lines = execSync('git diff --staged --diff-filter=ACMR --name-only -z')
.toString()

const stagedFiles = lines
.replace(/\u0000$/, '')
.split('\u0000')

开始

安装 husky

  1. 安装 husky
1
pnpm install -D husky
  1. 启用 Git 挂钩
1
pnpx husky install

执行完之后项目根目录会多一个 .husky 文件夹

  1. 要在安装后自动启用 Git 挂钩,请编辑 package.json
    TIP

    husky install 命令告诉 Git 改为使用 .husky/Git hooks 目录。

    1
    npm pkg set scripts.prepare="husky install"

此时你的 package.json 应该有:

1
2
3
4
5
6
// package.json
{
"scripts": {
"prepare": "husky install"
}
}

安装 lint-staged

  1. 安装 lint-staged
1
pnpm install -D lint-staged
  1. 添加 lint-staged 配置

首先明确一下,lint-staged 仅仅是文件过滤器,不会帮你格式化任何东西,所以没有代码规则配置文件,需要自己配置一下,如:.eslintrc.stylelintrc等,然后在package.json中引入。

其中这段的意思是对特定的文件进行特定的格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// package.json
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"package.json": [
"prettier --write"
],
"*.vue": [
"eslint --fix",
"prettier --write",
"stylelint --fix"
],
"*.{scss,less,styl,html}": [
"stylelint --fix",
"prettier --write"
],
"*.md": [
"prettier --write"
]
},
  1. scripts 中增加如下命令
1
2
3
4
5
6
// package.json
{
"scripts": {
"lint:lint-staged": "lint-staged",
}
}

创建钩子

创建 pre-commit hook

1
2
3
pnpx husky add .husky/pre-commit "npm run lint:lint-staged"

git add .husky/pre-commit

执行完之后在 .husky 目录下会多一个 pre-commit 脚本文件,在 git commit -m "xxx" 时就会触发 lint:lint-staged,如果 lint:lint-staged 命令失败,提交将自动中止。

1
2
3
4
5
6
# pre-commit

#!/usr/bin/sh
. "$(dirname -- "$0")/_/husky.sh"

lint:lint-staged

添加新的命令

后期添加其它命令请直接:

1
pnpx husky add .husky/pre-commit "xxxx"

比如 添加vue-tsc,来对你的 *.vue 文件做类型检查。

  1. 安装 vue-tsc
1
pnpm install -D vue-tsc
  1. scripts 中增加如下命令
1
2
3
4
5
6
// package.json
{
"scripts": {
"ts:check": "vue-tsc --noEmit --skipLibCheck",
}
}
  1. ts:check 命令设置到 hook 里去
1
pnpx husky add .husky/pre-commit "npm run ts:check"

同样 pre-commit 钩子会新增一行 npm run ts:check

1
2
3
4
5
6
#!/usr/bin/sh
. "$(dirname -- "$0")/_/husky.sh"

# 根据 package.json 中的 lint-staged 配置格式化并提交代码
npm run lint:lint-staged
npm run ts:check

工作流程

当我们进行一次 git 提交时 =>

触发 husky 配置的 pre-commit 钩子 =>

执行 npm run lint:lint-staged 命令 =>

触发 lint-staged 对暂存区的文件进行格式化 =>

使用 eslint/prettier/stylelint 进行格式化

补充

前面在 pre-commit 钩子中已经设置了 tsc 检查,发现在代码提交时会自动全局检测,这个 google 了一下似乎还没有解决办法,如何只检测暂存区的 ts 规范。

关于 tsc 的问题参考了以下文章:

添加 tsc 的检查
Vue & TypeScript 增量编译

测试提交

它会读取 package.jsonlint-staged 配置项帮你格式化。

但是 ts 类型有问题的,它并不会帮你修改(因为你没告诉它怎么做😆),它会直接阻断你的提交,直到你修改完所有类型错误。

如果 git hooks 脚本运行失败(进程结束时返回的状态码不为 0),那么会终止后续操作。比如上例中 tsc 检查报错,那么会直接终止 commitgit commit 命令失败。