前言
在开发团队协作中,“开发规范” 是经常被讨论的话题。当然,除了代码上的规范,还有一个很重要的规范就是“提交规范”。
规范化提交的目的:
- 提交统一的、有规则的信息;而不是混乱的、看不懂是什么意思的信息
- 可以提供更加明朗的历史信息,便于后续快速定位问题、代码回滚等的操作
- 可以自动化生成changelog
husky
husky
是一个 Git-Hooks 工具. 那么 hooks 是什么呢 ?
"hooks" 直译是 “钩子”,它并不仅是 react,甚至不仅是前端界的专用术语,而是整个行业所熟知的用语。通常指:系统运行到某一时期时,会调用被注册到该时机的回调函数。
规范化提交
第一步就是要在 git commit
之前先做一次Lint
校验,限制不规范代码的提交,那 husky
这个工具就能做到。husky
继承了Git
下所有的钩子,在触发pre-commit
钩子的时候,阻止不合法的 commit、push 等等。
husky 配置
1、初始化 git 目录
如果没有先初始化 git, 需要重新装husky。
git init -y
2、安装 husky
# pnpm 安装
pnpm add husky -D -w
# or npm 安装
npm install husky -D
3、添加 husky 脚本
在 package.json
文件 scripts 中手动添加
"prepare": "husky install"
或者直接执行命令添加
npm set-script prepare "husky install"
prepare 脚本会在npm install(不带参数)之后自动执行。也就是说当我们执行npm install安装完项目依赖后会执行 husky install命令,该命令会创建.husky/目录并指定该目录为git hooks所在的目录。
4、执行 npm run prepare
执行之后会发现项目根目录多了个.husky的目录及文件。
在该目录下添加 pre-commit
和 commit-msg
文件。
lint-staged
如果直接在 pre-commit
里执行 eslint --fix
,可能只是修改了一个文件,但依然会检查项目中所有需要检验的文件,体验非常的不友好。导致的问题就是:每次提交代码,无论改动多少,都会检查整个项目下的文件,当项目大了之后,检查速度也会变得越来越慢。
解决这个问题就需要用到lint-staged
,lint-staged能够让lint只检测暂存区的文件
# pnpm 安装
pnpm add lint-staged -D -w
# or npm 安装
npm install lint-staged -D
1、在 package.json
文件添加配置
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": "eslint --fix"
}
2、在 pre-commit
添加脚本
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm exec lint-staged
# 当然也可以配合 prettier pretty-quick 这个两个插件来做代码格式化的修复,不需要可不用
# pnpm exec pretty-quick --staged
这样在使用 git commit
之前,会先执行 ./.husky/pre-commit 下的脚本,实现提交前的拦截修复
commitlint
在多人协作的项目中,每个人的 Commit message
可能都会不同,没有很明确的限定哪些是新增功能,哪些修复bug,哪些优化代码等等,那这时候就需要装一些工具来规范Commit message
。
需要使用的插件:
- @commitlint/cli
- @commitlint/config-conventional
- czg
# pnpm 安装
pnpm add @commitlint/cli @commitlint/config-conventional czg -D -w
# or npm 安装
npm install @commitlint/cli @commitlint/config-conventional czg -D
1、在 package.json
文件添加配置
"config": {
"commitizen": {
"path": "./node_modules/cz-git"
}
}
2、在 package.json
文件添加脚本
"cz": "git add . && czg",
3、在根目录加上 commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
// 'scope-enum': [2, 'always', scopes],
'body-leading-blank': [1, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 72],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [
1,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'style',
'improvement',
'perf',
'build',
'chore',
'ci',
'docs',
'test',
'refactor',
'revert',
],
],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
}
}
4、在 commit-msg
添加脚本
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm exec commitlint --config commitlint.config.js --edit "${1}"
5、执行 npm run cz
就能看到效果了
自动生成 CHANGELOG
1、全局安装 conventional-changelog-cli
插件
npm install conventional-changelog-cli -g
2、在 package.json
文件添加脚本
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 -n ./changelog-option.js"
参数说明:
-
-p angular
,表示changelog标准为angular,现在有angular, atom, codemirror, ember, eslint, express, jquery 等项目的标准可供选择 -
-i CHANGELOG.md
,表示指定输出的文件名称 -
-s
输出到infile,这样就不需要指定与outfile相同的文件 -
-r
从最新的版本的生成,默认值为1。如果为0,则将重新生成整个更新日志并覆盖输出文件 -
-n ./changelog-option.js
表示指定自定义配置文件
3、执行 npm run changelog
就可以在根目录下看到 CHANGELOG.md 文件了
4、如果想自定义输出模板,可以在配置 changelog-option.js
文件
在根目录新建 changelog-option.js
文件 和 templates/commit.hbs
文件
changelog-option.js
文件:
const readFileSync = require('fs').readFileSync
const join = require('path').join
module.exports = {
gitRawCommitsOpts: {
format: '%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci%n-authorName-%n%an%n-authorEmail-%n%ae',
},
writerOpts: {
commitPartial: readFileSync(
join(__dirname, 'templates/commit.hbs'),
'utf-8'
),
// mainTemplate: readFileSync(join(__dirname, 'templates/template.hbs'),'utf-8'),
// headerPartial: readFileSync(join(__dirname, 'templates/header.hbs'),'utf-8'),
// footerPartial: readFileSync(join(__dirname, 'templates/footer.hbs'),'utf-8'),
...getWriterOpts(),
},
}
function getWriterOpts() {
return {
transform: (commit, context) => {
let discard = true
const issues = []
commit.notes.forEach((note) => {
note.title = 'BREAKING CHANGES'
discard = false
})
if (commit.type === 'feat') {
commit.type = '✨ Features | 新功能'
} else if (commit.type === 'fix') {
commit.type = '🐛 Bug Fixes | Bug 修复'
} else if (commit.type === 'perf') {
commit.type = '⚡ Performance Improvements | 性能优化'
} else if (commit.type === 'revert' || commit.revert) {
commit.type = '⏪ Reverts | 回退'
} else if (commit.type === 'improvement') {
commit.type = '💩 Improvement | 优化改进'
} else if (commit.type === 'style') {
commit.type = '💄 Styles | 风格'
} else if (discard) {
return
} else if (commit.type === 'docs') {
commit.type = '📝 Documentation | 文档'
} else if (commit.type === 'refactor') {
commit.type = '♻ Code Refactoring | 代码重构'
} else if (commit.type === 'test') {
commit.type = '✅ Tests | 测试'
} else if (commit.type === 'build') {
commit.type = '👷 Build System | 构建'
} else if (commit.type === 'ci') {
commit.type = '🔧 Continuous Integration | CI 配置'
} else if (commit.type === 'chore') {
commit.type = '🎫 Chores | 其他更新'
}
if (commit.scope === '*') {
commit.scope = ''
}
if (typeof commit.hash === 'string') {
commit.hash = commit.hash.substring(0, 7)
}
if (typeof commit.subject === 'string') {
let url = context.repository
? `${context.host}/${context.owner}/${context.repository}`
: context.repoUrl
if (url) {
url = `${url}/issues/`
// Issue URLs.
commit.subject = commit.subject.replace(
/#([0-9]+)/g,
(_, issue) => {
issues.push(issue)
return `[#${issue}](${url}${issue})`
}
)
}
if (context.host) {
// User URLs.
commit.subject = commit.subject.replace(
/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g,
(_, username) => {
if (username.includes('/')) {
return `@${username}`
}
return `[@${username}](${context.host}/${username})`
}
)
}
}
// remove references that already appear in the subject
commit.references = commit.references.filter((reference) => {
if (issues.indexOf(reference.issue) === -1) {
return true
}
return false
})
return commit
},
groupBy: 'type',
commitGroupsSort: 'title',
commitsSort: ['scope', 'subject'],
noteGroupsSort: 'title',
}
}
templates/commit.hbs文件:
- {{header}}
{{~!-- commit link --}} {{#if @root.linkReferences~}}
([#{{hash}}](
{{~#if @root.repository}}
{{~#if @root.host}}
{{~@root.host}}/
{{~/if}}
{{~#if @root.owner}}
{{~@root.owner}}/
{{~/if}}
{{~@root.repository}}
{{~else}}
{{~@root.repoUrl}}
{{~/if}}/
{{~@root.commit}}/{{hash}}) by @{{authorName}})
{{~else}}
{{~hash}}
{{~/if}}
{{~!-- commit references --}}
{{~#if references~}}
, closes
{{~#each references}} {{#if @root.linkReferences~}}
[
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}#{{this.issue}}](
{{~#if @root.repository}}
{{~#if @root.host}}
{{~@root.host}}/
{{~/if}}
{{~#if this.repository}}
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}
{{~else}}
{{~#if @root.owner}}
{{~@root.owner}}/
{{~/if}}
{{~@root.repository}}
{{~/if}}
{{~else}}
{{~@root.repoUrl}}
{{~/if}}/
{{~@root.issue}}/{{this.issue}})
{{~else}}
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}#{{this.issue}}
{{~/if}}{{/each}}
{{~/if}}
5、生成效果如下
流程说明
- commit(规范化提交)
- release(发布版本)
- tag(创建tag,提交代码到远程仓库)
- npm run changelog (生成 CHANGELOG)
- 提交 CHANGELOG.MD