前言
最近在做 monorepo 项目的前端工程化,其中有一个部分与 git commit 有关,因此参考了几个项目,总结出了相关的工具链,并写成文章。
整个工具链,都是围绕着 git commit message 的生成、校验,到最后的生成 changelog 的过程
下面是我调研的几个库在该过程所使用到的一些工具:
tdesign-vue-next-starter | ant-design-vue | vue | vite | |
commit 规范 | angular 规范 | angular 规范 | 基于 angular 规范的扩展规范 | 基于 angular 规范的扩展规范 |
生成 commit message | commitizen | × | × | × |
校验 commit message | commitlint | commitlint | 使用自定义脚本校验 | 使用自定义脚本校验 |
生成 changelog | × | 自定义脚本 | conventional-changelog | conventional-changelog |
除了使用项目中自定义的一些工具外,主要使用到的开源工具为以下几个:
- commitizen:生成 commit message
- commitlint:校验 commit message
- conventional-changelog:生成 changelog
在介绍这几个工具前,先介绍一下目前用得比较多的 commit 规范
angular commit 规范
网上就有很多文章,大家应该也是非常熟悉的,不了解的可以看看阮一峰的文章。该小节的内容都来源于该文章,这里我把一些重点的内容列一下
Commit message 包括三个部分:Header,Body 和 Footer。
<type>(<scope>): <subject> // 空一行 <body> // 空一行 <footer>
其中,Header 是必需的,Body 和 Footer 可以省略。
Header 中 type 的类别,有以下 7 种:
- feat:新功能(feature)
- fix:修补bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor:重构(即不是新增功能,也不是修改bug的代码变动)
- test:增加测试
- chore:构建过程或辅助工具的变动
- pref:提高性能的代码更改
Body 部分是对本次 commit 的详细描述,可以分成多行。
Footer 部分只用于两种情况:
- 不兼容变动,以
BREAKING CHANGE
开头,后面是对变动的描述、以及变动理由和迁移方法 - 关闭 Issue
另外,如果是回滚撤销 git commit,则 commit message 必须要以 revert:
开头,如:
revert: feat(pencil): add 'graphiteWidth' option This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
commit 工具链
- commitizen:生成 commit message
- commitlint:校验 commit message
- conventional-changelog:生成 changelog
这些工具,作用不同,那么要如何把这些工具的流程串起来?我们先来看看 git commit 的周期钩子
下面是简单的 git 钩子介绍,详情请查看官方文档,
pre-commit
钩子:在键入提交信息前运行,可以用于eslint
等 linter 的代码校验和修复prepare-commit-msg
钩子:在启动提交 commit message 编辑器之前运行。可以在该阶段生成 commit message(commitizen 在该阶段运行),这样就不会打开编辑器输入 commit message 了commit-msg
钩子:填写 commit message 之后运行,如果该钩子的脚本以非零值退出,则 Git 放弃提交。可用于校验 commit message 是否符合规范。(commitlint 在该阶段运行)post-commit
钩子:Git commit 提交过程完成后执行
changelog 的生成并没有在 git 钩子中,因为不是每次 commit 都需要生成 changelog,只需要再发布新的版本前生成即可
commitizen
通过命令行交互的方式,生成 git commit message
在使用 commitizen
前,首先得告诉 commitizen
我们用的是哪个 commit 规范,因此我们可以做以下配置:
- 安装依赖,假设我们使用
cz-conventional-changelog
作为配置,更多配置可以查看这里
npm install commitizen cz-conventional-changelog --save
- 在 package.json 中,加入
commitizen
的配置
{ "script":{ "cz": "cz" } "config": { "commitizen": { "path": "cz-conventional-changelog" } } }
这样 commitizen
就知道我们所使用的的规范了
- 生成 commit message 并提交 commit:
npx cz ## 运行结果如下: ? Select the type of change that you are committing: feat: A new feature ? What is the scope of this change (e.g. component or file name): (press enter to skip) ? Write a short, imperative tense description of the change (max 94 chars): test ? Provide a longer description of the change: (press enter to skip) ? Are there any breaking changes? No ? Does this change affect any open issues? No ## commit message: feat: test
命令行交互的内容,是来源于配置 cz-conventional-changelog
,因为不同的 commit 规范,有不同的命令行交互
命令行交互效果图如下:
如何与 git 钩子配合使用?
配置 prepare-commit-msg
的 git 钩子
# .git/hook/prepare-commit-msg exec < /dev/tty && node_modules/.bin/cz --hook || true
这样 git-commit
的时候,就会执行 cz,进行命令行交互式地生成 commit 信息
exec < /dev/tty
,默认情况下,git钩子不是交互式的。这个命令允许用户在钩子期间使用他们的终端与commizen交互。--hook
是告诉 commitizen,这是在 git 钩子中运行的(直接命令行执行 cz 会生成 commit message 并提交 commit,如果通过 git 钩子触发,则是只负责生成 commit message,传给 git commit 命令)
commitlint
类似 eslint,commitlint
则是对 commit message 进行规范校验。
同样的,commitlint 也需要一份配置,告诉它我们需要用的是哪个规范。配置 commitlint 的方式如下:
// 项目根目录创建配置文件 // commitlint.config.js module.exports = { extends: ['@commitlint/config-conventional'] };
这里是用的是配置 @commitlint/config-conventional
,其他配置可以查看官方文档。
commitlint
就必须得配合 git 钩子使用了,需要在 git commit 信息生成/填写之后,再执行校验。因此使用的 git 钩子是 commit-msg
# .git/hooks/pre-commit npx commitlint -e $1 • $1,传入命令行的第一个参数,在 commit-msg 的 git 钩子中,为 .git/COMMIT_EDITMSG,该文件存储的是当次 commit 的 message 文本 • commitlint -e 文件路径:从文件中读取 git commit message 文本
git 钩子的同步
我们的 git commit 钩子是写在 .git/hooks
目录下的,当运行 git commit
的时候就会被运行
但是,git 的客户端钩子(commit 的几个钩子都是),不会被提交到 git 仓库中,这就意味着,别人 clone 了这个仓库,钩子是没有被复制过去的
为了解决这个痛点,我们需要使用一些辅助工具去同步我们的 git 钩子,常用的工具有 husky
、simple-git-hooks
这里以 simple-git-hooks
为例,说明一下如何配置:
- 安装依赖
npm install simple-git-hooks --save-dev
- 配置 package.json
{ "simple-git-hooks": { "pre-commit": "npx lint-staged", "prepare-commit-msg": "exec < /dev/tty && node_modules/.bin/cz --hook || true", "commit-msg": "npx commitlint -e $1", } }
- 手动运行一下命令,安装钩子到
.git/hooks
npx simple-git-hooks
由于 package.json 会上传到 git 仓库,因此这些钩子配置也会被保存,clone 仓库之后,只需要执行安装钩子即可。
changelog 生成
生成 changelog 使用的是 conventional-changelog
,只需要运行以下命令,就能生成 changlog 文件
conventional-changelog -p angular -i CHANGELOG.md -s
-p
:angular 的 commit 规范-i
:选择输入的文件-s
:输入跟输出的文件一致
该命令会在 CHANGELOG.md 顶部,拼接上最新的一个版本的 changelog,不会修改之前的 changelog
changelog 单条内容就是单条的 git commit message,下面看看一个 changelog 的例子(节选自 @vitejs/plugin-vue),:
生成 changelog 的基本流程
- 根据 git tag 将 所有 commit message 进行分割,git tag 需要以 v 开头,如
v1.9.3
、v1.9.2
。 - 筛选出需要生成 changelog 的 commit。
- 并非所有类型的 message 都会生成 changelog,angular 规范中,只有
feat
、fix
、pref
,以及revert
回滚的 commit,会生成到 changelog 中。其他的类型,需要自己补充进去。不建议将docs
、chore
、style
, 、refactor
、test
加入到 changelog
- 根据筛选出来的 commit message,生成最新的 tag 版本的 changelog
- 由于允许手动修改 changelog 文件,因此默认只会生成最近一个 tag 的 changelog,因此需要参数
-i
,传入上一次的 changelog 文件,然后在它的顶部,拼接上最新生成的 changelog
monorepo 项目如何生成 changelog?
由于 monorepo 工程中,存在多个 package 包,它们的 changelog 是需要各自分开的。
而按照上文 changelog 的生成方式,是会读取所有 commit 然后生成,并没有对 commit 进行过滤(去掉不属于该 package 的 commit)
那因此,需要用到另外一个参数 --commit-path
:
conventional-changelog -p angular -i CHANGELOG.md -s --commit-path <替换为 package 目录>
这告诉 conventional-changelog
,在生成 changelog 时,需要对每个 git commit 的文件进行检查,如果 commit 中变更的文件,是 commit-path 目录中的文件,则该 commit 才被认为是有效 commit,才会用于生成 changelog
上图展示了 commit 是如何通过 --commit-path
过滤 commit 的。由于最后一个 commit 中,同时修改了 A 和 B 的包,因此 A 和 B 在生成 changelog 时,都会有该 commit 的信息
总结
commit 的工具链已经介绍完毕,每个工具都有它各自的作用,我们可以看自己的需要,选择是否使用这些工具。
就好像,tdesign-vue-next-starter
只是个业务模板,不是工具库,其实可以不需要 changelog(但要也行)
而 vite、vue 这些库,则没有使用 commitizen
生成 commit message,因为其实 commit message 直接手写可能更加方便(个人也认为手写更方便),因此也是不需要的。
而 commit 规范的校验,参考的这几个库,都有做到,不过方式不同,有的是 commitlint
,有的是自己写的脚本,这是因为 vue 和 vite 扩展了 angular 规范,因此校验脚本也是自己写的,当然也可以用 commitlint
实现,可能是时间原因没做,也没有那么高的优先级。
实际上,我们也不必全部照搬这些,我们更多的是要理解其过程。而对于自己开发的项目,只需要结合自身需要即可。
如果这篇文章对您有所帮助,请帮忙点个赞👍,您的鼓励是我创作路上的最大的动力