一、前言
Element Plus 中有严格的提交信息规范,commitlint
是作为它的首选的提交信息工具,这个工具通常和 husky
工具共同使用,以确保所有的提交信息符合指定的提交规范。这篇文章应该和 ESlint 配置那篇文章是一起的,但由于篇幅太长单独写在这篇了。
这篇文章你可以收获,在项目开发中搭建一套完美的提交校验,让你知道更改项目代码应该如何书写提交信息。
二、一般项目中的 commitlint 基本使用
一)、初始化 commitlint
1. 安装 commitlint
项目内安装下面依赖:
bash
npm install @commitlint/config-conventional @commitlint/cli --save-dev
2. 配置 commitlint
在项目根目录创建 commitlint.config.js
文件,用于定义 commitlint
的规则配置:
javascript
module.exports = {
extends: ['@commitlint/config-conventional'],
};
这里的 @commitlint/config-conventional
是一种标准化的提交规范,基于 Angular的提交规范。
3. 提交信息格式解释
通过配置后,commitlint
会按照上面的格式校验提交信息,例如要求以下格式:
plain
<type>(<scope>): <subject>
type
:提交类型,表示提交的目的或类别。
feat
:新功能(feature)fix
:修复 bugdocs
:文档修改style
:代码格式修改(不影响代码逻辑,如空格、格式化等)refactor
:重构(既不是新功能也不是修 bug)test
:增加测试chore
: 用于不修改源代码或测试代码的更改,通常是项目维护中的一些杂项任务,比如修改配置文件、更新依赖、清理代码 。
scope
:影响的范围,例如模块或功能名称(可选)
subject
:对提交内容的简要描述
参照下面提交格式
plain
feat(user-auth): add login functionality
fix(api): resolve issue with timeout error
docs(readme): update installation guide
二)、自定义 commitlint.config.js 配置
1.常用自定义规则配置
如果需要自定义提交信息规则,可以在 commitlint.config.js
中修改默认配置。
javascript
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']],
'scope-empty': [2, 'never'], // scope 不可为空
'subject-case': [2, 'never', ['start-case', 'pascal-case']], // subject 不允许使用大写开头
'header-max-length': [2, 'always', 72], // 限制提交信息的长度
},
};
type-enum
:指定type
的枚举值scope-empty
:不允许scope
为空subject-case
:subject
不允许使用PascalCase
或StartCase
2.提交信息示例
新增功能 (feat
)
plain
feat(auth): add support for two-factor authentication
修复bug ( **fix**
)
plain
fix(api): handle network errors in data fetch function
更新文档 (docs
)
plain
docs(contributing): update code of conduct section
代码格式调整 (style
)
plain
style(header): adjust header spacing and font size
重构代码 (refactor
)
plain
refactor(user-service): split large function into smaller ones
增加测试 (test
)
plain
test(auth): add unit tests for login component
构建/工具变动 (chore
)
plain
chore(deps): update dependencies to latest versions
3. 配置 husky
与 commitlint
集成
为了确保每次提交时都自动进行校验,可以使用 husky
配合 commitlint
。安装 husky
:
bash
npm install husky --save-dev
npm husky init
npm husky init
初始化 husky
会创建一个 pre-commit
脚本在 .husky/
文件夹下,而且还会再 package.json
中更新一个脚本 prepare
。
如果是需要配置 commitlint
自动化校验提交信息,需要再.husky/
目录下添加一个 commit-msg
文件名来执行 commitlint
校验:
bash
commit-msg
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
这会在每次提交信息时,自动执行 commitlint
检查。
三、Element Plus 中的 commitlint 的使用
一)、项目初始化 commitlint
1.安装 commitlint
bash
pnpm npm install @commitlint/config-conventional @commitlint/cli --save-dev -w
2.创建配置文件
在跟目录下创建 commitlint.config.js
文件和 commitlint.config.ts
文件。
🤷♂️🤷♂️为什么需要创建者两个文件而不是创建这其中的一个?
commitlint.config.ts
在开发环境下,我们需要 TypeScript
提供更加安全的类型校验,他可以更好的维护项目。
commitlint.config.js
是一个 JavaScript
文件,通常在运行时执行,因为 JS
不需要类型检查,执行更快。在执行 钩子需要在终端中运行,因为在 Node.js 环境下 **JavaScript
**文件可以直接执行不需要额外的转译,这样可以提高运行速度。
所以保留两个配置文件是:保留类型校验,提高 husky
运行速度。
3.配置 commitlint 文件
配置 commit.config.js
文件,默认导入 commit.config.ts
文件,这里使用 esbuild-kit/cjs-loader
来代替 ts-node
来加载 typescript
配置。这个加载速度更快。
javascript
require('@esbuild-kit/cjs-loader')
module.exports = require('./commitlint.config.ts').default
1)、配置 commitlint.config.ts 文件
这个配置主要需要实现以下功能:
- 自动获取提交范围 :动态获取
packages
和internal
目录下的文件夹名,生成提交信息的scope
列表。 - **自动匹配默认
scope
和 **subject
:通过 Git 状态检测当前修改的文件,自动提取默认的scope
(文件夹名)和subject
(组件名),帮助生成符合规范的默认提交信息。 - 规范提交信息格式 :定义了提交信息的多项格式规则(如
type
、scope
、subject
的大小写、长度限制等),确保提交信息遵循统一的风格。 - 个性化提示 :在提交信息编辑时,使用提取的
scope
和subject
作为默认值,提供提示以便快速填写符合规范的提交信息。
typescript
import { execSync } from 'child_process'
import fg from 'fast-glob'
const getPackages = (packagePath) =>
fg.sync('*', { cwd: packagePath, onlyDirectories: true })
execSync
是 Node.js 提供的一个用于执行系统命令的同步方法。fg
是 fast-glob
包的导入,用于快速处理文件和目录的匹配。
getPackages
这个函数主要利用 fast-glob
工具来实现获取指定路径下的所有文件夹名称。**onlyDirectories: true**
** 选项使得只匹配文件夹**。
如果 packages
目录下有 components
和 theme-chalk
两个文件夹,则 getPackages('packages')
返回 ['components', 'theme-chalk']
数组。
typescript
const scopes = [
...getPackages('packages'),
...getPackages('internal'),
'docs',
'play',
'project',
'core',
'style',
'ci',
'dev',
'deploy',
'other',
'typography',
'color',
'border',
'var',
'ssr',
]
获取提交信息格式里的 <type>(scope): subject
中的 scope
数组列表。 如果 packages
目录包含 button
和 input
两个文件夹,则 scopes
可能变成 ['button', 'input', 'docs', 'play', ...]
。
typescript
const gitStatus = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
execSync('git status --porcelain || true')
命令执行 git status
,并将其输出以字符串形式返回 。.toString()
将结果转换为字符串,.trim()
去除空白,.split('\n')
将结果按行分割成数组 。它返回的是修改过的文件 , 这段代码将返回一个包含这些文件路径的数组,如 ['M packages/button/index.ts', 'M packages/input/style.css']
。
typescript
const scopeComplete = gitStatus
.find((r) => ~r.indexOf('M packages'))
?.replace(/\//g, '%%')
?.match(/packages%%((\w|-)*)/)?.[1]
const subjectComplete = gitStatus
.find((r) => ~r.indexOf('M packages/components'))
?.replace(/\//g, '%%')
?.match(/packages%%components%%((\w|-)*)/)?.[1]
遍历 gitStatus
返回的数组,找到这个数组中第一条包含 M packages
的记录,**~r.indexOf('M packages')**
会返回找到这个字符串在数组中的某项所在的下标的取反值。比如返回下标为 2
,则这个取反返回 -2
。如果没找到为 indexOf
的值为-1
,取反则为 0
则 find
函数获取的就是 false
。这样就能找到通过 indexOf()
方法找到的包含的那项。主要作用就是将下标值转换成布尔值。
在将数组中的子项如果存在/
则用 %%
来取代。'M packages/example-path/file.ts'
** 会被替换为 **'M packages%%example-path%%file.ts'
。
最后通过 **match**
** 来获取正则表达式的捕获项。比如 'M packages%%example-path%%file.ts'.match(/packages%%((\w|-)*)/)
这个首先找到 packages%%
,所以这一部分符合正则表达式的开始部分。然后 ((\w|-)*)
开始匹配 example-path%%file.ts
中的 example-path
。example-path
符合 (\w|-)
的规则(字母和 -
组成的组合)。匹配到 %%
时停止,因为后面的%%
不符合 (\w|-)
的要求。最终,捕获组 ((\w|-)*)
匹配到了 example-path
。match
执行这段代码将返回一个数组 **['packages%%example-path', 'example-path']
。
typescript
export default {
rules: {
/**
* type[scope]: [function] description
* ^^^^^
* 'scope-enum' 限定了 scope(范围)的值必须在 `scopes` 数组中,以确保提交信息的 scope 有效。
*/
'scope-enum': [2, 'always', scopes],
/**
* type[scope]: [function] description
*
* ^^^^^^^^^^^^^^ 空行.
* - 提交信息的 body 部分之前必须有一个空行。
*/
'body-leading-blank': [1, 'always'],
/**
* type[scope]: [function] description
*
* - something here
*
* ^^^^^^^^^^^^^^
* - 提交信息的 footer 部分之前必须有一个空行。
ep:
fix: 修复用户登录问题
增加了登录失败时的错误提示
关闭了连接泄漏的问题
->这里要有空行
Related to issue #123
*/
'footer-leading-blank': [1, 'always'],
/**
* type[scope]: [function] description [不超过 72 个字符]
* ^^^^^
* - 限制提交信息的标题(header)的最大长度为 72 个字符。
*/
'header-max-length': [2, 'always', 72],
/**
* 限制 scope 的格式为小写。
*/
'scope-case': [2, 'always', 'lower-case'],
/**
* 限制 subject 的格式,不能为以下格式:
* - sentence-case(首字母大写)
* - start-case(每个单词首字母大写)
* - pascal-case(首字母大写的驼峰命名)
* - upper-case(全大写)
*/
'subject-case': [
1,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
/**
* 确保 subject(描述)部分不为空。
*/
'subject-empty': [2, 'never'],
/**
* 确保 subject 部分不以句号 (.) 结尾。
*/
'subject-full-stop': [2, 'never', '.'],
/**
* 限制 type 的格式为小写。
*/
'type-case': [2, 'always', 'lower-case'],
/**
* 确保 type(类型)部分不为空。
*/
'type-empty': [2, 'never'],
/**
* type[scope]: [function] description
* ^^^^
* 限定 type 的值必须在以下选项中,以确保提交类型统一:
* - build、chore、ci、docs、feat、fix、perf、refactor、revert、release、style、test、improvement
*/
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'release',
'style',
'test',
'improvement',
],
],
},
prompt: {
/**
* 设置提交信息的默认 scope(范围),使用 scopeComplete 的值。
*/
defaultScope: scopeComplete,
/**
* 自定义 scope 的位置:如果 scopeComplete 存在,则将自定义 scope 选项放在底部,否则放在顶部。
*/
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
/**
* 设置默认的 subject(主题),如果 subjectComplete 存在则在其前面加上 `[` 和 `]`。
*/
defaultSubject: subjectComplete && `[${subjectComplete}] `,
/**
* 禁止自定义 issue 前缀。
*/
allowCustomIssuePrefixs: false,
/**
* 禁止空的 issue 前缀。
*/
allowEmptyIssuePrefixs: false,
},
}
2)、type 的使用场景介绍
build
: 与构建系统或外部依赖相关的更改。例如,更新编译工具、打包配置或版本控制的设置。chore
: 与代码库维护相关的常规更改,不影响代码运行和功能,比如更新构建脚本或清理文件 。ci
: 修改了持续集成(CI)配置和脚本,例如更新 GitHub Actions、Jenkins 或 Travis 配置文件。docs
: 仅限文档的更改,比如添加、更新或删除文档内容。feat
: 增加新功能或特性,通常会直接影响用户体验。fix
: 修复问题或 bug,改善当前功能的稳定性。perf
: 提升性能的更改,比如优化算法或减少加载时间。refactor
: 重构代码,但不影响外部行为的更改,通常用于提升代码可读性和可维护性。revert
: 撤销以前的提交,通常是在发现更改带来问题后将其还原。release
: 用于版本发布的更改或标记。style
: 与代码格式、样式、注释相关的更改,不会影响功能和逻辑,比如代码缩进或删除多余空行。test
: 添加或修改测试代码,以确保代码功能的正确性。improvement
: 对现有功能的改进或增强,但不引入全新功能。
3)、commitlint 和 husky 结合
在 .husky/
目录下创建 commit-msg
文件,配置以下配置,就可以完成自动提交校验提交信息。
typescript
pnpm exec commitlint --config commitlint.config.js --edit "${1}"
4.配置执行脚本
在 项目根目录下的package.json
文件中配置执行脚本。
json
"lint:commit": "commitlint --from $(git merge-base origin/dev HEAD) --to HEAD > ./commit-lint.txt",
检查从特定起点到当前 HEAD
之间的所有提交信息是否符合 commitlint
的规则,并将结果输出到 commit-lint.txt
文件中。
在合并代码前,可以执行这个脚本,确保从开发分支分离后创建的所有提交信息符合规范。
四、总结
- commitlint 的基本使用
- Element Plus 中 commitllint 的使用
看完这一篇,相信你应该可以给自己的项目配置提交校验了。 愿诸君越来越好,一起加油。