我正在参加「掘金·启航计划」
这是一篇长文,提供项目规范方面的配置指南。
概述
简介
团队开发时,为了维护代码质量和可读性,可以使用两类工具:linter & formatter。
- linter:代码检查工具,对代码进行静态分析,发现代码中的潜在错误
- formatter: 格式化工具,用于统一编码风格
linter 也可用于检查代码格式,一定程度上可以替代 formatter,但配置起来可能没有 formatter 那么方便。
常见的 linter 工具如下:
- ESLint:用于检查 js 代码
- stylelint:用于检查 css 代码
- commitlint:用于检查 git 提交信息
常见的 formatter 工具如下:
- Prettier:用于格式化文件,支持多种格式的文本文件
集成方式
当我们在项目中使用时,一般会通过如下方式进行集成:
- scripts 脚本:在
package.json文件中新建脚本,手动执行 - 编辑器集成:安装相关的编辑器插件,获得最好的代码检查体验
- 在编写代码时自动提示代码中存在的问题
- 保存时自动格式化和修复错误
- 更改配置文件时自动提示配置项
- 打包工具集成:安装相关的打包工具插件,在编译和构建时执行代码检查,确保所有人在开发时都能进行代码检查
- git 集成:利用 git hook,在提交代码时自动执行代码检查命令,确保团队成员提交的代码符合规范
配置方式
这些工具拥有类似的配置方式:
| 工具 | 配置文件 | 配置字段 | 忽略文件 |
|---|---|---|---|
| ESLint | .eslintrc.* | eslintConfig | .eslintignore |
| stylelint | .stylelintrc.* | stylelint | .stylelintignore |
| prettier | .prettierrc.* | prettier | .prettierignore |
| commitlint | .commitlintrc.* | commitlint | 不支持 |
配置文件的
*号需要换成后缀名。
配置文件支持以下格式:
json格式的后缀名为json,优点是支持配置项提示(需进行编辑器集成)js格式的后缀名为js,优点是灵活,比如在不同环境下使用不同的配置yaml格式的后缀名为yml,优点是可读性好
部分工具支持其他的配置文件名和配置文件格式,具体可参看官网文档。
配置字段指的是在 package.json 文件中使用特定字段来进行配置,这样就不需要使用单独的配置文件了。
忽略文件用于忽略特定的文件路径,书写规则同 gitignore 语法规则。
Prettier
集成 Prettier
Prettier 作为格式化工具,比较方便的使用方式是与编辑器集成,让编辑器通过 Prettier 的规则进行代码格式化。集成或配置的方式参考 Prettier 官网。
配置 Prettier
Prettier 有一组可配置的规则,配置方式是在配置对象的最外层直接指定规则选项和选项值,比如:
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
所有可用的规则可参考:www.prettier.cn/docs/option…,表格中的 API Override 就是规则在配置对象中的键名。
每个规则都有默认值,如果不进行配置,仍会使用规则的默认值进行格式化。
prettier 命令
也可以使用命令行来格式化代码。首先在项目中安装,以确保有 prettier 命令。
npm i -D prettier
使用以下命令进行代码格式化:
prettier --write .
命令中的点号代表当前目录。以上命令会格式化所有支持的文件,并将格式化的结果回写到文件中。如果只想知道是否所有文件都已格式化,可以使用如下命令:
prettier --check .
可以将命令放到 package.json 的 scripts 脚本中,以便手动运行:
{
"scripts": {
"prettier": "prettier --write ."
}
}
这样就能通过 npm run prettier 来执行格式化操作了。
ESLint
配置 ESLint
rules 规则
和 Prettier 不同,你必须在 rules 字段声明要使用的规则,才能让 eslint 进行代码检查。属性名为要配置的规则,值可以是这几个:
- 字符串
"off"或数字0代表不使用规则 - 字符串
"warn"或数字1代表违反时抛出警告 - 字符串
"error"或数字2代表违反时抛出错误
抛出的警告会在控制台打印,而抛出错误会导致程序或编译进程终止。
{
"rules": {
"semi": "error",
"quotes": "error"
}
}
ESLint 内置了一套规则,可参看:eslint.cn/docs/rules/。
有些规则有额外的选项,要配置这些选项,可以使用数组传入。
{
"rules": {
"semi": ["error", "never"],
"quotes": ["error", "single", { "avoidEscape": true }]
}
}
如果只是启用规则,而不配置选项,则会使用选项的默认值。
extends 继承
一个一个配置规则非常麻烦,所以可以通过 extends 字段来继承其他的配置。extends 字段的值主要有以下几种字符串:
- 其他配置文件的路径,以
.或/开头 - 共享配置名,即省略前缀的 npm 包名,比如
standard代表eslint-config-standard这个包,这种包以eslint-config-开头,导出一个 ESLint 配置对象 - ESLint 内置规则集,以
eslint:开头,比如eslint:recommended代表启用所有推荐的内置规则
{
"extends": "eslint:recommended"
}
如果要继承多个配置,可以使用数组。
{
"extends": ["eslint:recommended", "standard", "./default.js"]
}
plugins 插件
ESLint 内置的规则有时候会不够用,插件可以额外的规则。ESLint 插件是以 eslint-plugin- 开头的 npm 包,主要作用就是提供自定义规则。
要使用插件提供的规则,首先在 plugins 字段中添加插件名,即省略前缀的包名,然后在 rules 中以 插件名/规则名 的形式配置。
{
"plugins": ["plugin1"],
"rules": {
"plugin1/rule1": "error"
}
}
插件除了可以提供规则,还可以提供配置。以 eslint-config- 开头的包可以提供一个配置,但一个插件可以提供多个配置。要使用插件提供的配置,在 extends 字段中以 plugin:插件名/配置名 的形式引入。
{
"extends": ["eslint:recommended", "plugin:vue/recommended"]
}
使用插件提供的配置时,可以不用在 plugins 字段声明插件。
overrides 覆盖
overrides 字段可以为特定文件使用不一样的配置。
{
"overrides": [
{
"files": ["*-test.js", "*.spec.js"],
"rules": {
"no-unused-expressions": "off"
}
}
]
}
env 和 globals 全局变量
有时你需要告诉 ESLint 当前的运行环境,以免在使用相关的全局变量时,ESLint 认为你使用了未定义的变量。ESLint 内置了很多的环境,插件也可以提供额外的运行环境。使用 env 字段来声明代码的运行环境,属性名代表环境名,值固定为 true。
{
"plugins": ["example"],
"env": {
"browser": true,
"node": true,
"example/custom": true
}
}
你也可以直接在 globals 字段中声明全局变量。
{
"globals": {
"var1": "writable",
"var2": "readonly",
"Promise": "off"
}
}
globals 中的值有以下几种:
writable代表此全局变量可写readonly代表此全局变量只读off代表禁用此全局变量
root 停止搜索
当 ESLint 检测某个文件时,会从文件所在目录开始,一直往上级目录搜索配置文件,然后将所有找到的配置合并。将 root 字段设置为 true 可以使 ESLint 停止往上查找。所以,项目根目录中的配置最好加上这个字段。
{
"root": true
}
parser 和 parserOptions 解析器
ESLint 的核心是规则,而规则本质上是一个函数。ESLint 将 js 代码解析为 AST(抽象语法树),而规则函数通过分析 AST 发现代码中的问题,并可能给出修复的方法。
ESLint 将代码解析为 AST 的工具叫做 espree,但有时遇到其他格式的语法,或者过于超前的 ES 版本的语法,可能会导致解析失败,这时就需要使用其他的解析器。比如解析高版本的 ES 语法可以使用 @babel/eslint-parser,解析 vue 文件 <template> 部分的语法需要使用 vue-eslint-parser。
parser 字段用来配置要使用的解析器,parserOptions 字段配置解析器的选项。
eslint 命令
eslint 命令的使用方式为:
eslint --fix [file/dir/glob]
--fix 参数表示自动修复可以修复的错误并回写到文件中,如果不加这个参数,则不会自动修复错误。
可以把这个命令添加到 npm 脚本中:
{
"scripts": {
"lint": "eslint --fix src/**/*.{vue,js}"
}
}
然后只需要执行 npm run lint 即可。
ESLint 配置实例
如果你使用 Vue CLI,则可以在初始化时选择一种 ESLint 的配置方案。如果你需要手动配置 ESLint,以下将给出一些参考。
ESLint 集成 Prettier
ESLint 的规则分为两类,一类用于检测代码质量,另一类用于检测代码风格。所以 ESLint 和 Prettier 都可以用来约束代码风格。
使用 ESLint 来约束代码风格,相比 Prettier 更加灵活,体现在以下方面:
- Prettier 配置选项比较少,很多风格是定死的,而 ESLint 几乎可以完全定制任何风格
- ESLint 的规则一般比 Prettier 中的对应规则拥有更多的选项,可配置项更高
但是,Prettier 作为专业的格式化工具,相比 ESLint 具有以下优势:
- 配置方便,Prettier 几乎不用配置就能直接使用,而 ESLint 需要将相关规则一条一条打开
- 约束性更强,同样的代码不管格式化之前是什么样子,用 Prettier 格式化之后基本上都一个样,但用 ESLint 修正的代码风格可能并不会约束得很严格
- 支持的文件格式更多,ESLint 几乎只能用于 js 代码,而 Pretter 可以用于各种格式的代码,还能通过插件支持更多格式
所以,有时候你可能会想要用 Prettier 来约束代码风格,而 ESLint 仅仅用于检测代码质量。Prettier 官网给出了几个 npm 包用于完成这件事,它们的详细用法参考:www.prettier.cn/docs/integr…。
eslint-config-prettier这个 ESLint 配置关闭了和 Prettier 重复的规则eslint-plugin-prettier这个 ESLint 插件将 prettier 当成了 eslint 的一条规则,想要先执行 eslint 后执行 prettier 可以使用它prettier-eslint将 prettier 格式化后的代码交给 eslint,想要先执行 prettier 后执行 eslint 可以使用它
后面两个工具相当于把两条命令合成了一条,我们也可以不使用它们,手动执行两条命令。
假设想要先执行 prettier,再执行 eslint,只需执行如下命令:
prettier --write src & eslint --fix "src/**/*.{vue,js}"
命令中的 & 代表前面的命令执行成功时,才执行后面的命令。
如果想先执行 eslint,然后再用 prettier 来格式化,会遇到一个问题,就是 prettier 格式化之后的代码可能会在 ESlint 中引起报错。所以我们需要使用 eslint-config-prettier 来禁用 ESLint 中和 Prettier 冲突的规则。
{
"extends": ["eslint:recommended", "prettier"]
}
因为 prettier 只负责代码风格的部分,所以我们用 eslint:recommended 这个内置的配置来启用推荐的规则,再使用 eslint-config-prettier 的配置来关闭其中和 prettier 冲突的规则。此时需要注意的是:
pretter配置必须放在 extends 的最后面,以保证最后会将冲突的规则关闭- 不能在
rules字段重新开启那些和 prettier 冲突的规则,否则还是会冲突
接着就可以执行如下命令:
eslint --fix "src/**/*.{vue,js}" & prettier --write src
standard 规范
一个比较著名的编码规范是 standard 规范,它的详细介绍可以看这里:standardjs.com/readme-zhcn…。
这套规范规定了一套代码风格,同时检查代码中的问题。standard 规范认为争论代码风格是没有意义的,因此制定了一套"标准"的代码风格,并且不推荐修改,比如:使用两个空格缩紧,字符串使用单引号,不使用分号。
如果要在 eslint 中使用 standard 规范,可以安装 eslint-config-standard,然后在配置中继承它。
{
"extends": "standard"
}
airbnb 规范
另一套著名的规范是 Airbnb 规范,它的具体规则和使用每个规则的原因可以参看这里:github.com/lin-123/jav…。
Airbnb 规范的特点是非常的严格,比如:
- 不应该使用一元自增
++或自减--运算符,因为自动添加分号时可能会出现意料之外的结果 - 不应该使用
for-in和for-of,而应该使用数组方法进行遍历,因为处理起来更加容易 - 使用分号,以避免执行时在错误的地方自动添加分号
- 在数组字面量和对象字面量的最后一项后面加逗号,这样在版本对比时更加清晰
引入 airbnb 比引入 standard 复杂得多。首先 eslint-config-airbnb 是为 react 设计的,所以不能使用这个,而应该使用 eslint-config-airbnb-base。
npm i -D eslint-config-airbnb-base
这个包有一个 peerDependency 是 eslint-plugin-import,你可以手动安装它,也可以使用 install-peerdeps 来安装。
npx install-peerdeps --dev eslint-config-airbnb-base
Airbnb 会使用这个依赖来检查 import 语句,确保导入的文件存在。但它不能识别路径别名,比如使用 @ 来代替 src 时是会报错的。这时可以使用额外的解析器来解析别名路径,比如 eslint-import-resolver-alias。
{
"extends": "airbnb-base",
"settings": {
"import/resolver": {
"alias": [
["@", "./src"],
["util", "./src/util"]
]
}
}
}
eslint-plugin-vue
在 vue 项目中,一般使用 eslint-plugin-vue 来检查 vue 代码,这个插件提供了许多规则来检查代码是否符合 vue 官网的风格指南。你也可以用这些规则来定制 vue 文件 <template> 部分的代码风格。
这个插件还提供了一些配置,来对应风格指南中的特定优先级的规则,比如要启用 优先级C:推荐 的规则,可以使用配置 plugin:vue/recommended。
{
"extends": ["plugin:vue/recommended"],
"rules": {
"vue/require-default-prop": "off",
"vue/max-attributes-per-line": [
"error",
{
"singleline": {
"max": 3,
"allowFirstLine": true
},
"multiline": {
"max": 1,
"allowFirstLine": true
}
}
]
}
}
三个实用的配置文件
.editorconfig 统一编辑器配置
虽然可以使用 ESLint 和 Prettier 来约束代码风格,但这只在执行校验时生效,在编辑过程中,编辑器可能并不遵守你设置的规则。比如虽然在 ESLint 中设置了 2 个空格缩进,但由于编辑器的设置是 4 个空格缩进,所以按回车时仍会缩进 4 个空格。
EditorConfig可以用来覆盖编辑器的默认设置,为文件设置缩进方式和编码方式等。只需为编辑器安装这个插件,然后在项目根目录添加配置文件 .editorconfig 即可。
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
charset = utf-8
.gitattributes 处理换行符转换
不同的操作系统默认的换行符是不一样的:
- Windows 系统的默认换行符是
CRLF(即\r\n) - Linux 系统的默认换行符是
LF(即\n) - MacOS 系统的默认换行符是
LF或者CR(即\r)
如果团队成员使用不同的操作系统来编写代码,可能会导致问题,比如:
- 文件中混用了不同形式的换行符
- 格式化之后把每一行的换行符都替换了,导致版本对比时不清晰
为了应对这种情况,git 在代码检出和提交时可能会进行换行符转换。比如 windows 上安装 git 时,有一页是让用户选择换行符的转换方式,默认的转换方式是:
- 代码检出时将换行符换成
CRLF - 代码提交时将换行符换成
LF
然而这可能会导致问题,比如 ESLint 中校验了换行符必须为 LF 时,就会产生警告或错误。可以使用命令来改变默认设置,禁用这种自动转换行为,但你无法为每一个项目成员都设置一遍。这时可以使用 gitattributes来改变 git 的行为,只需要在项目根目录下创建文件 .gitattributes。
* text=auto eol=lf
* 表示该行配置针对所有文件,text=auto 表示在换行符转换时自动判断哪些文件是文本文件,eol 是 end of line 的缩写,eol=lf 表示代码检出时将换行符转换为 LF。你也可以这样配置:
* -text
这表示在换行符转换时,所有文件都不当成文本文件,那么自然就不会对任何文件进行换行符转换了。
jsconfig.json 让 VSCode 识别路径别名
当使用 import 引入模块时,编辑器可能会在你输入路径时自动补全,点击路径或引入的内容时可以跳转过去。但如果使用了路径别名,编辑器可能就识别不了了。如果你使用的是 VSCode,可以通过在项目根目录创建 jsconfig.json 来让编辑器识别路径别名,这个文件和 TypeScript 的配置文件 tsconfig.json 是一样的。如果你使用的是 typescript 项目,可以直接用 tsconfig.json。
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
这样就能使 VSCode 识别路径别名了。这个文件还有许多其他的用法,比如 Vite 使用它来引入类型定义文件,从而使编辑器拥有更智能的代码提示。关于这个文件的更多介绍可以看这里:www.tslang.cn/docs/handbo…。
stylelint
ESLint 只能检测 js 代码,而 stylelint 是用来检测 css 代码的。
配置 stylelint
stylelint 和 ESLint 一样,默认不进行任何校验,你需要在 rules 字段中指定要校验的规则。
{
"rules": {
"block-no-empty": null,
"indentation": 2,
"max-empty-lines": 2,
"unit-allowed-list": [
["px", "em"],
{
"ignoreProperties": {
"rem": ["line-height", "/^border/"],
"%": ["width"]
}
}
]
}
}
内置的规则名是很有讲究的,可以参考官网。规则的值有以下 3 种:
- 设置为
null代表关闭规则 - 直接设置为规则的选项值
- 有些规则有额外的选项,可以使用数组的第 2 项传入
数组第二项的选项一定是一个对象,并且可以通过这个对象传入一些共有的选项:
message违反规则时的提示信息severity错误级别,可选值为warning或error,默认是error
{
"indentation": [
2,
{
"except": ["block"],
"message": "Please use 2 spaces for indentation.",
"severity": "warning"
}
]
}
stylelint 同样支持使用 extends 字段来继承配置,但是对共享配置的包名并没有规定,所以并不能像 ESLint 那样省略前缀。stylelint 也没有内置可继承的配置,比较著名的配置是 stylelint-config-standard,它启用了一些错误检查和风格相关的规则。
{
"extends": ["stylelint-config-standard", "./my-stylelint-config"]
}
另一个比较著名的配置是 stylelint-config-primer,这个配置对 css 代码的限制更加严格。
styelint 也同样支持插件,插件可以提供规则和配置,一个比较著名的插件是 stylelint-order,它提供了规则来规定 css 中各种属性的顺序。
{
"plugins": ["stylelint-order"],
"rules": {
"order/properties-order": ["width", "height"]
}
}
更多的 stylelint 共享配置和插件可以查找 awesome-stylelint。
stylelint 命令
stylelint 命令和 eslint 命令类似,直接接上要检查的文件或者路径即可,使用 --fix 参数可以修复那些支持自动修复的错误。
{
"scripts": {
"lint:style": "stylelint --fix src/**/*.{vue,css}"
}
}
stylelint 注意事项
stylelint 不仅支持原生的 css 语法,也支持一些 css 预处理器的语法,比如 scss 和 less。但是,如果你使用 PostCSS 来定制了 css 的语法,可能就识别不了了。
还有一些语法本身不被 stylelint 支持,但可以通过插件来支持。比如 stylus 语法可以使用插件 stylelint-plugin-stylus 来支持。
stylelint 同样可以和 Prettier 集成,方法和 ESLint 类似。
browserslist
很多时候,我们需要考虑浏览器兼容性问题,browserslist 是一个 npm 包,用来设置项目支持的浏览器列表。它本身没有太多的作用,你甚至不需要在项目依赖中安装它,但它可以和其他工具配合使用,比如可以结合 babel 插件来决定 babel 编译的结果代码。
官方文档中列举了许多可以和它配合使用的工具,有些可以根据目标浏览器来编译代码或引入 Polyfill,有些可以检测和目标浏览器不兼容的代码。
接下来介绍如何用它和 ESLint 以及 stylelint 配合,来避免写出和目标浏览器不兼容的代码。
配置 browserslist
首先需要配置需要兼容的目标浏览器,方法是在项目根目录下创建配置文件 .browserslistrc,在文件中一行一行列出想要兼容的浏览器。支持的配置方式有许多种,比如:
>5% in CN在中国的使用比例大于 5%last 2 versions每种浏览器最新的 2 个版本supports es6-module支持特定的语法特性的浏览器not IE <= 10排除部分浏览器版本not dead排除"已死"的版本,即官方两年以上没有更新和维护的版本defaults默认规则,即> 0.5%, last 2 versions, Firefox ESR, not dead
可以使用 and 来连接多个条件,表示同时满足多个条件。所有支持的写法可以查看官方文档。另外,VSCode 上可以安装插件来支持这个文件的语法高亮。
最后,可以通过 npx browserslist 命令查看最终要兼容的浏览器列表。
eslint-plugin-compat
使用 eslint-plugin-compat 来检查编写的 js 代码是否被目标浏览器支持。这个插件还支持指定引入的 Polyfills,这样在检测到这些 API 时就不会报错了。
{
"extends": ["plugin:compat/recommended"],
"env": {
"browser": true
},
"settings": {
"polyfills": [
"Promise",
"WebAssembly.compile",
"fetch",
"Array.prototype.push"
]
}
}
其实,这个插件的意义可能并不太大,因为项目中一般会使用 babel 进行语法转换,所以即使写出一些不被目标浏览器兼容的语法或 API,也不会出现兼容性问题。
stylelint-no-unsupported-browser-features
真正容易出现兼容性问题的是 css 的部分,所以可以通过 stylelint-no-unsupported-browser-features 这个 stylelint 插件来检测 css 代码中的兼容性问题。
{
"plugins": ["stylelint-no-unsupported-browser-features"],
"rules": {
"plugin/no-unsupported-browser-features": [
true,
{
"severity": "warning"
}
]
}
}
当出现存在兼容性问题的代码时,会提示你哪个特性在哪些浏览器上有兼容性问题。这时你可以使用它给出的特性名称去 CanIUse 上查询,看看具体会有什么兼容问题。
git 集成
执行代码检测的时机有三个:
- 编辑器保存代码时检测,可以自动修复代码中的问题
- 打包工具编译代码时检测,可以提示代码中发现的问题
- 使用 git 提交代码时检测,可以确保提交的代码符合规范
接下来讲讲如何将检测工具和 git 集成,在 git 提交时自动执行代码检测。
husky
git 提供了一些钩子来在各个阶段执行特定代码,这些钩子叫做 git hook。我们可以使用 husky 这个 npm 包来帮我们管理 git hook。
官方推荐的安装方式是使用以下命令:
npx husky-init && npm install
这条命令会配置 git,指定 git hook 脚本所在目录为 .husky,并且默认安装了一个 hook 叫做 pre-commit,对应的脚本文件是 .husky/pre-commit,里面会执行一条 npm test 命令。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test
这个 hook 正好指的是在 git 提交前执行,也就是说在 git 提交代码的时候会自动执行 npm test 命令。所以我们直接把它替换成我们想要的命令即可。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx eslint --fix "src/**/*.{vue,js}"
npx stylelint --fix "src/**/*.{vue,css}"
你也可以使用 husky add 命令来添加其他的钩子脚本。
lint-staged
虽然利用 git hook 可以在提交时自动执行 eslint 和 stylelint 命令,但是却检测了 src 文件夹下所有的文件。但其实并不应该检测所有的代码,而应该只检测我们要提交的代码。所以我们可以借助一个叫做 lint-staged 的工具,这也是一个 npm 包。
从名字可以看出,这个工具用于检查 git 暂存区的代码,也就是准备提交的代码。你可以使用官方推荐的安装方式:
npx mrm lint-staged
这个命令会帮你安装并配置 husky 和 lint-staged,并根据你安装的代码检测工具自动配置,在提交时运行它们。确切的说,它会使用 husky 创建 pre-commit 钩子,在提交代码前运行 lint-staged。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
而 lint-staged 会根据 package.json 中的 lint-staged 字段的配置执行相关的 lint 工具。你可以查看 package.json 中的配置,如果不满意的话,可以手动更改配置。
{
"lint-staged": {
"*.{vue,js}": "eslint --fix",
"*.{vue,css}": "stylelint --fix"
}
}
属性名代表要检查的文件,但只会对 git 暂存区的文件生效。属性值代表要执行的命令,要检查的文件路径将会被拼接在命令的最后。如果要执行多条命令,使用数组即可。
{
"lint-staged": {
"*.{vue,js}": ["prettier --write", "eslint --fix"]
}
}
提交日志
好的 git 提交信息如同好的代码注释一样赏心悦目,让人快速了解这次提交改动了什么。但好的提交信息如同好的变量命名一样让人纠结,写起来颇为费劲。在我看来,好的提交需要满足以下条件:
- 一次提交只做一件事,这会让你更容易描述这次提交做了什么,因此更容易写出好的提交信息
- 提交信息具有一定的格式,这样看起来会更加清晰
比如,如果有几个不相关的 bug 需要修复,即使都是很小的 bug,也应该分成多次提交。如果改动代码前需要格式化整个文件,那么也应该分成两次提交,这样可以在对比代码时看清代码的改动。
虽然分成多次提交会降低你的效率,认真写提交信息会消耗你的脑力,但查看提交日志时却会非常清晰,更容易看清代码改动的历史,这在我看来是值得的。
提交规范 Conventional Commits
一个比较规范的提交方式叫做约定式提交(Conventional Commits),它的介绍文档在这里:www.conventionalcommits.org/zh-hans/v1.…。
约定式提交的提交信息格式如下:
<type>(<scope>): <description>
// 空一行
<body>
// 空一行
<footer>
只有类型和描述是必需的,所以一个最简单的示例为:feat: 添加中文语言支持。
常见的**类型(type)**如下:
feat添加功能,引入新特性fix错误修复,修复 bugdocs文档变更,添加或者更新文档style格式调整,不会影响代码含义的更改(空格,格式,缺少分号等)refactor代码重构,既不是修复 bug 也不是添加特性的代码更改perf性能优化,更改代码以提高性能test更新测试,添加或者更新测试build依赖调整,影响构建系统或外部依赖项的更改(示例作用域:gulp, broccoli, npm)ci脚本变更,对 CI 配置文件和脚本的更改(示例作用域:Travis, Circle, BrowserStack, SauceLabs)chore杂务处理,其他不会修改源文件或测试文件的更改revert恢复版本,恢复到上一个版本
feat 和 fix 分别对应语义化版本中的 MINOR 和 PATCH,它们会出现在自动生成的更新日志中。其他的类型不是必需的,你可以配置在项目中允许使用哪些类型。
**作用域(scope)**代表影响的范围,比如改动了哪一个功能模块。
**描述(description)**就是对本次提交内容的简短描述,结尾不要加句号。
**正文(body)**是对本次提交内容的详细描述,可以多行,可以有空行。
**脚注(footer)**用于两种情况:
-
不兼容变更(BREAKING CHANGE)
代表这个版本的代码和上一个版本不兼容,对应语义化版本中的
MAJOR。一般指 npm 包的用法与先前版本产生了不一致,直接升级可能会导致错误。这种情况下的 footer 需要以
BREAKING CHANGE:开头,后面接变动的描述、理由和迁移方法,可以有多行。 -
关闭 issue
fix类型的提交,如果有关闭 issue,可以在 footer 中记录关闭的 issue 编号,比如:closes issue #12, #15。
revert 类型是一种特殊的类型,它的描述是目标版本提交信息的第一行,正文格式固定为:This reverts commit <hash>,其中 hash 是被撤销的版本的 SHA 标识符,可以用 git log 查看。
revert: feat(pencil): add 'graphiteWidth' option
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
提交检查 commitlimt
懒惰是程序员和人类的天性,总有人不愿意每次都编写符合规范的提交信息。所以,我们可以利用 husky 在提交时检查编写的提交信息,只有符合规范的提交才能成功。
检查提交信息的工具是 commitlint,它对应的 npm 包是 @commitlint/cli,提供了 commitlint 命令。它的配置方式和 ESLint 以及 stylelint 类似。
rules 字段配置要检测的规则,属性名是规则名,属性值是一个数组,包含 3 项:
- 错误级别,数字
012分别代表关闭规则、警告和错误 - 字符串
alwaysnever分别代表必须满足规则和必须不满足规则 - 规则的具体配置值
如果使用 js 格式的配置文件,属性值可以是一个函数,返回一个数组或 Promise<Array>。
extends 字段列出要继承的配置,官方提供了符合约定式提交的配置,我们可以安装并继承它:
{
"extends": ["@commitlint/config-conventional"]
}
其实这样配置就已经足够了,但你也可以通过 rules 去更改其中一些规则,比如规定项目中可以使用的所有 scope。
然后用 husky 添加 git hook,由于需要拿到提交信息,所以使用的钩子是 commit-msg:
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'
提交工具 commitizen
对于新手来说,手动编写符合约定式提交的提交信息可能有点困难,我们可以使用一些工具来帮助编写提交信息。
首先是一款 VSCode 插件,它的名字叫做 Conventional Commits,它可以引导你写提交信息的每一个部分。
但不是所有人都用 VSCode,所以再推荐一个命令行工具,是个 npm 包,叫做 commitizen。commitizen 是一个专门帮助生成提交信息的工具,安装后可以使用 git-cz 命令替代 git commit 命令,就会引导你编写提交信息了。如果是在项目中安装,而不是全局安装,可以把这条命令写在 npm scripts 中:
{
"scripts": {
"commit": "git-cz"
}
}
以后提交时就执行 npm run commit 即可。
你还需要一个适配器来告诉这个工具提交信息的编写规则,commitlint 官网上有一些官方适配器,比如 @commitlint/cz-commitlint。你需要在 package.json 文件的 config 字段中指定适配器的路径:
{
"scripts": {
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "@commitlint/cz-commitlint"
}
}
}
接着再执行 npm run commit 提交时,commitizen 就会按照 commitlint 的配置文件,来引导编写符合规范的提交信息了。
日志生成 standard-version
当我们使用约定式提交时,可以根据提交记录生成更新日志文件。有许多工具可以做到这一点,其中一个比较傻瓜操作的是 standard-version。直接在 npm scripts 中配置脚本:
{
"scripts": {
"release": "npx standard-version"
}
}
当准备好发布的时候,执行 npm run release 命令,将会根据 git 提交记录生成一个用于发布的版本:
- 提取类型为
feat和fix的提交记录,生成CHANGELOG.md文件 - 根据提交记录中的提交类型更新
package.json中的版本号 - 提交一个新的版本并用新的版本号打上 git 标签