【代码提交规范】Husky + Lint-staged + Commitlint
每一名开发人员都有自己独特的代码风格。但对于多人协作项目,保持统一的风格有利于项目维护。我们可以在项目中引入 ESLint、Prettier 来规范代码,但这无法约束 Git commit message,这时候可以借助 Husky 等工具来把好最后一关。
Husky
Git 能在特定的重要事件发生时触发自定义脚本,也称钩子(githooks - Hooks used by Git)。因为一个项目通常是团队合作,我们不能保证每个人在提交代码之前执行一遍 lint 校验,所以需要 Git Hooks 来自动化校验的过程,否则禁止提交。
Husky 是一款 Git Hooks 工具,可以在执行特定的 git 命令时(如: git commit
, git push
)触发对应的脚本。
一般可以定义 pre-commit钩子 和 commit-msg钩子,分别触发 提交前 和 提交信息时 要执行的脚本。
相关网址
安装Husky(以pnpm为例)
1
2# npm i husky -D
pnpm add husky -D生成
.husky
文件夹1
npx husky install
执行下方命令向增加一个执行脚本
1
npm pkg set scripts.prepare="husky install"
注意:比较老的教程中用的命令是
npm set-script prepare "husky install"
,但是新版 npm 的set-script
命令已经废弃了,所以建议使用npm pkg set
命令来代替。相当于手动向
package.json
中写入以下内容:1
2
3
4
5
6// package.json
{
"scripts": {
"prepare": "husky install"
}
}安装依赖后执行 prepare,执行 huskey 命令,安装钩子,确保每个 clone 项目的人一旦安装依赖就会将 husky 钩子初始化到本地,从而保证每个人在提交 git 之前都能执行 lint 验证。
接下来添加对应的pre-commit钩子脚本命令,使其执行lint操作
1
npx husky add .husky/pre-commit "npm run lint"
相当于手动在
.husky/pre-commit
文件写入以下内容:1
2
3
4
5
. "$(dirname -- "$0")/_/husky.sh"
npm run lint测试钩子是否生效
1
2git add .
git commit -m 'test husky'从命令行可以看出来,在commit前是成功触发了
npm run lint
脚本的,并且无报错1
2
3
4
5
6
7
8
9
10
11
12
13$ git commit -m 'test husky'
> vuepress2-component-demo@0.1.0 lint
> npm run lint:style && npm run lint:script
> vuepress2-component-demo@0.1.0 lint:style
> stylelint packages/**/*.{vue,css,less,scss,sass} --fix
> vuepress2-component-demo@0.1.0 lint:script
> eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix
[main 448bfaa] test husky
6 files changed, 85 insertions(+), 35 deletions(-)
Commitlint
检测 git commit 内容是否符合定义的规范
commit message 是程序员开发的日常高频操作,自然状态下commit message 呈现五花八门的书写风格,不利于阅读和维护,规范的 commit message 有助于团队做 code review, 输出清晰明了的 CHANGELOG, 有利于项目的长期维护。
commitlint 需要结合 Husky 使用。定义 commit-msg 钩子,检查提交信息是否符合规范。使用 commitlint 可以规范我们提交信息,可以用来自动生成 changeLog 等文件,更清晰的查看每一次代码提交记录。为什么需要 Commitlint,除了在后续的生成changelog文件和语义发版中需要提取commit中的信息,也利于其他同学分析你提交的代码,所以我们要约定commit的规范。
安装依赖包
1
2# npm i -D @commitlint/cli @commitlint/config-conventional
pnpm add -D @commitlint/cli @commitlint/config-conventional其中这几个依赖包分别是:
@commitlint/cli
:是 Commitlint 命令行工具@commitlint/config-conventional
:强制执行常规提交的可共享commitlint配置。
执行命令创建一个提交校验配置文件
commitlint.config.cjs
1
echo "module.exports = {extends: ['@commitlint/config-conventional'],rules: {},};" > commitlint.config.cjs
相当于手动在
commitlint.config.cjs
文件中写入了如下内容:1
2
3
4module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {},
};可以看出来,配置文件的写法和eslint的配置文件写法比较类似,都是通过extends字段加载社区共享的校验规则,这里我们用的是@commitlint/config-conventional(基于Angular约定),官方文档还列出了一些其他的可共享配置。
接下来就是完善配置文件,通过rules字段进行规则自定义。可以参考 commitlint官网 - Rules规则 写入自定义校验规则:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// commitlint提交检验规则 https://commitlint.js.org/#/reference-rules
// Commit message 包括三个部分:Header(必需)、Body(可选)和 Footer(可选)
// Header包括三个字段:type(必需)、scope(可选)和 subject(必需)
// <type>(<scope>): <subject>
// 提交类型(影响范围): 简短描述
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
// 限制Header最多200字
// 'header-max-length': [2, 'always', 200],
// 提交类型<type>枚举
'type-enum': [
2,
'always',
[
'build', // 改变构建流程、或者增加依赖库、工具等 如webpack.config.js,package.json yarn.lock
'chore', // 构建过程或辅助工具的变动,各种配置文件的修改,如.gitignore,tsconfig.json,.vscode,.tenone, eslint/stylelint,envConfig
'ci', // 对CI自动化流程配置文件或脚本进行了修改
'docs', // 只修改了项目说明文档
'feat', // 新增功能,一个新的特性
'fix', // 修复一个Bug
'perf', // 性能优化
'refactor', // 代码重构,既不是修复Bug(fix)也不是添加功能(feat)
'revert', // 版本回滚,代码回退
'test', // 修改或添加一个测试用例
'clean', // 清理过时无用文件
'merge', // 合并代码分支
'style', // 只修改了样式文件(包括css/less/sass,图片,字体文件)
'format', // 格式化,不影响代码含义的修改,比如空格、格式缩进、缺失的分号等
],
],
// 'type-case': [0, 'always', 'start-case'],
// 'type-empty': [0],
// 'scope-empty': [0],
// 'scope-case': [0],
// 'subject-full-stop': [0, 'never'],
// 'subject-case': [0, 'never'],
// 'header-max-length': [0, 'always', 72],
},
}package.json
中添加commitlint指令1
2
3
4// package.json文件
scripts:{
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
}最后添加到 commit-msg hooks钩子中,在每次 git commit 提交信息时 触发执行Commitlint校验
1
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
这行命令相当于手动创建
.husky/commit-msg
文件并添加下方内容:1
2
3
4
5
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"接下来测试钩子是否生效,我们先提交一个不规范的commit是否规范:
1
2git add .
git commit -m 'add commitlint'稍等片刻,待它执行完lint后才会检查commit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21$ git commit -m "add commitlint"
> vuepress2-component-demo@0.1.0 lint
> npm run lint:style && npm run lint:script
> vuepress2-component-demo@0.1.0 lint:style
> stylelint packages/**/*.{vue,css,less,scss,sass} --fix
> vuepress2-component-demo@0.1.0 lint:script
> eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix
⧗ input: add commitlint
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky - commit-msg hook exited with code 1 (error)果然,由于提交不规范导致 commit-msg 钩子退出代码执行
接下来我们提交一个规范的commit,根据规范,我们此次是对CI自动化流程配置文件或脚本进行了修改。根据提交公式
<type>(<scope>): <subject>
,提交类型选择ci,影响范围省略,并添加简短描述。1
git commit -m 'ci: add commitlint'
再次稍等片刻,待它执行完lint后才会检查commit。执行结果如下,可以看到提交成功:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17$ git commit -m "ci: add commitlint"
> vuepress2-component-demo@0.1.0 lint
> npm run lint:style && npm run lint:script
> vuepress2-component-demo@0.1.0 lint:style
> stylelint packages/**/*.{vue,css,less,scss,sass} --fix
> vuepress2-component-demo@0.1.0 lint:script
> eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix
[main 1311f51] ci: add commitlint
4 files changed, 614 insertions(+), 1 deletion(-)
create mode 100644 .husky/commit-msg
create mode 100644 commitlint.config.cjs
但是有个问题大家发现没有,我们每次commit,都会由Husky触发Git pre-commit钩子,对所有满足条件的文件执行lint检查。这导致每次都要等待很长时间,即便不久前刚刚对所有文件检查了一遍,并且大部分文件并没有被改动,但依然要浪费时间等待所有文件格式检查完毕。
我们只修改了个别的文件,就没有必要检测所有的文件代码格式。那么想要处理这个问题,就需要使用另外一个插件 lint-staged
!
lint-staged
能够在 Git commit 提交之前,获取上次 commit 到现在所有发生变动的文件。也就是说,它可以过滤出 git add 后的暂存区文件,只对那些文件执行脚本进行代码审查和格式化。这样就不会影响到本次未更改的文件,限制了处理范围,缩短了等待时长。
我们已经实现了在提交代码之前自动运行Linter(eslint 、stylelint)来检查代码规范,但检查代码过程会很慢,因为是检查整个项目。而 lint-staged
可以使 linter 只检查将要提交的文件(即 git add
后, 在staged暂存区的文件)。配合 Husky
定义 pre-commit 钩子,在其中执行 lint-staged ,就可以在提交代码之前检查代码。如果代码不合规范,则会中断提交。
相关网址
首先安装依赖
1
2# npm i -D lint-staged
pnpm add -D lint-staged接下来修改pre-commit钩子,也就是
.husky/pre-commit
文件,之前我们往里面添加的内容是:1
2
3
4
5
. "$(dirname -- "$0")/_/husky.sh"
npm run lint现在我们要修改成如下内容,使得每次提交前执行
npx lint-staged
命令:1
2
3
4
5
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged你可以手动修改,也可以将
.husky/pre-commit
文件删掉,通过命令重新生成:1
npx husky add .husky/pre-commit "npx lint-staged"
配置lint-staged以运行 linters 和其他任务。lint-staged 同样使用 cosmiconfig 支持配置(也就是可以读取像commitlint,stylelintrc,prettierrc,eslintrc那样的独立配置)。或者如果你觉得所需配置内容较少,可以直接在
package.json
中添加lint-staged
属性进行设置,它按照文件匹配指定任务,比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
"scripts": {
"lint:style": "stylelint packages/**/*.{vue,css,less,scss,sass} --fix",
"lint:script": "eslint --ext packages/**/* .js,.jsx,.ts,.tsx,.vue --fix",
"lint": "npm run lint:style && npm run lint:script",
"prepare": "husky install",
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
},
+ "lint-staged": {
+ "packages/**/*.{js,jsx,ts,tsx,vue}": [
+ "stylelint --fix"
+ ]
+ },
"devDependencies": {
"husky": "^8.0.3",
"lint-staged": "^13.1.0"
}
}但我感觉将配置作为单独文件更加直观,所以我没写在
package.json
中,而是在项目根目录创建了.lintstagedrc.cjs
文件,来配置所需要的规则。关于配置文件的使用,可以前往官方文档。在 lint-staged 中支持node-glob通配符的配置,同时也支持配置多个,若发生变动的文件路径满足配置,则触发后面的命令。1
2
3
4
5
6
7
8
9
10
11
12
13// Lint-staged针对暂存的 git 文件运行 linters
// 配置文档 https://github.com/okonet/lint-staged#Configuration
module.exports = {
"*.{css,less,scss,sass}": [
"prettier --write",
"stylelint --fix",
],
"*.{js,jsx,ts,tsx,vue}": [
"prettier --write",
"eslint",
],
};随便修改一个在代码检查范围内的文件,然后提交,测试钩子是否生效
1
2git add .
$ git commit -m "add lint-staged"可以看到是成功触发了暂存区匹配成功的文件检查的,但我故意没规范书写commit msg所以没有提交成功:
1
2
3
4
5
6
7
8
9
10
11
12
13$ git commit -m "add lint-staged"
√ Preparing lint-staged...
√ Running tasks for staged files...
√ Applying modifications from tasks...
√ Cleaning up temporary files...
⧗ input: add lint-staged
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky - commit-msg hook exited with code 1 (error)再正确规范提交一次就可以了:
1
2
3
4
5
6
7
8$ git commit -m "ci: add lint-staged"
√ Preparing lint-staged...
√ Running tasks for staged files...
√ Applying modifications from tasks...
√ Cleaning up temporary files...
[main 5eb7299] ci: add lint-staged
7 files changed, 234 insertions(+), 10 deletions(-)
create mode 100644 .lintstagedrc.cjs
在今后commit时尽量规范化,不要写的很简单、很随意,但也不要很冗余。要使别人看到commit信息就可以获知该commit用意,让commit msg与你提交的的变更代码建立联系。在此发出倡议:
- One Thing,One Commit;
> 在提交 commit 的时候尽量保证这个 commit 只做一件事情,比如实现某个功能或者修改了配置文件。
- 不要commit一半的工作;
> 当开发任务没有完整的完成的时候,不要commit。这不是说每次commit都需要开发完成一个非常完整的大功能,而是当把功能切分成许多小的但仍然具备完整性的功能点的时候,开发人员需要完整完成这个功能点之后才能commit。必要时可以使用stash命令对修改进行记录。
- 经常commit;
> 经常使用commit能够使你的commit(里的修改内容)越小,并且能使你commit相关的修改,多次commit允许你推送自己代码到远程分支上的频率增加,能有效的减少merge代码时出现的代码冲突问题,因为多次 commit能使你的同事的代码库得到及时的更新。
- commit之前的测试;
> 保证你所开发的功能是完整无误的。在commit代码之前的对代码充分测试是非常重要的,可以避免有问题的代码被其他开发人员使用。
- 编写规范的commit message;
> 不规范的Commit Message,就是依托答辩!我们要提交规范的commit msg,加油奥利给!
【参考内容】:
[1] 前端代码格式处理-概述(Prettier + Stylelint + ESlint + Husky + lint-staged + commitlint )
[2] 前端工程化配置指南 Eslint、Prettier、Commitlint、Husky、Jest、GitHub Actions、Semantic Release
[3] 使用 Husky + Commitlint + Lint-staged 约束每一次 Git 提交
[4] stylelint最佳实践
[6] 前端代码规范化实践eslint+prettier+stylelint+editorconfig+commitlint
[7] 前端规范化实践(一)
[8] 关于 lint 的一些思考
[9] commit规范方案探讨
[10] git commit 提交规范
[11] 前端代码规范化:EditorConfig + Prettier + ESLint
[12] 项目中的 git message 规范
[13] 如何使用 jest 和 lint-staged 只检测发生改动的文件
[14] 六、标准化编程规范解决方案之自动修复 lint-staged
[15] 使用 Vue3+TS+Vite+PNPM 搭建前端工程基础工作流配置
[16] [前端工程化配置] 使用commitlint校验git commit message
[17] vue2项目配置prettier + eslint + commitlint + stylelint
[18] Vue3+Vite+Ts 项目实战 01 Vite 创建项目、ESLint+TS+GitCommit配置、Vue3新特性介绍
[19] 深入理解npm命令,npm i之前可被自动执行的命令prepare
[20] 规范提交git commitizen conventional-changelog-cli
[21] Git Commit 规范(Conventional Commit) Commit Message 规范格式、Commitizen(规范 commit 命令行工具)、Commitlint & Husky(规范 commit message 验证)、Conventional-changelog-cli (自动生成 CHANGELOG 文件信息)