前端工程化之 commitlint + husky 实现 git 提交规范化

前言

对于编程语言进行「语法、书写」校验,能有效「归并」不同开发者的「不同风格」,还能检验出一些语法错误。

比如 eslint 就能校验 JS 代码的「鸡肋糟粕」,css 哪些东西需要校验?单纯从代码层面来说,CSS 校验的东西其实蛮少的。

比如:属性顺序、小于 1 的小数要不要去掉 0、选择器之间要不要加空格…
不过要细细的追究,校验的东西还是挺多的,比如 List of rules 列出了好多需要校验的规则。

规范的出现就是为了让这些每个人的编程风格变得统一

安装 husky 和 lint-staged

husky 可以用于实现各种 git Hook。这里主要用到 pre-commit 这个 hook,在执行 commit 之前,运行一些自定义操作

lint-staged 用于对 git 暂存区中的文件执行代码检测

1
npm i husky lint-staged -D

Prettier 配置

根目录下创建 .prettierrc.js 文件

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
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 不使用缩进符,而使用空格
useTabs: false,
// 使用 2 个空格缩进
tabWidth: 2,
tabSize: 2,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号 'es5' none
trailingComma: 'es5',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单个箭头函数参数周围加上括号<avoid|always>
arrowParens: 'avoid',
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 换行符使用 lf 结尾是 \n \r \n\r auto
endOfLine: 'lf',
vueIndentScriptAndStyle: true,
};

Eslint 配置

安装

1
npm i eslint eslint-plugin-html eslint-plugin-vue babel-eslint -D

根目录下创建 .eslintrc.js 文件

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
module.exports = {
root: true,
// parser: 'babel-eslint',
env: {
node: true,
},
// extends: ["plugin:vue/base", "plugin:vue/recommended"],
extends: ['plugin:vue/base'],
parserOptions: {
parser: 'babel-eslint',
},
plugins: ['html', 'vue'],
rules: {
/*
"off"或者0 //关闭规则关闭
"warn"或者1 //在打开的规则作为警告(不影响退出代码)
"error"或者2 //把规则作为一个错误(退出代码触发时为1)
*/

'no-var': 'error', // 禁止使用var
'prefer-const': 'error', // 建议使用const
'no-const-assign': 'error', // 禁止修改使用const(no-const-assign)声明的变量
'object-shorthand': 'error', // 方法属性值简写
'quote-props': ['error', 'as-needed'], // 只对那些无效的标示使用引号 ''
'no-array-constructor': 'error', // 数组要求字面量赋值
'no-new-object': 'error', // 对象使用字面值创建对象
'array-callback-return': 'error', // 在数组方法的回调中强制执行
'prefer-destructuring': [
'error',
{
array: true,
object: true,
},
{
enforceForRenamedProperties: false,
},
], // 用对象的解构赋值来获取和使用对象某个或多个属性值
quotes: ['error', 'single'], // string 统一用单引号 ''
'prefer-template': 'error', // 建议使用模板字符串
'no-eval': 'error', // 禁止使用eval
'no-useless-escape': 'error', // 不要使用不必要的转义字符
'func-style': 'error', // 用命名函数表达式而不是函数声明
'prefer-rest-params': 'error', // 建议使用rest参数而不是参数
'space-before-function-paren': ['error', 'never'], // 函数前不允许使用空格或
'space-before-blocks': ['error', 'always'], // 块前需要空格
'no-param-reassign': 'error', // 不允许重新分配函数参数
'prefer-spread': 'error', // 建议使用spread语法而不是.apply()
'prefer-arrow-callback': 'error', // 建议使用箭头函数
'arrow-spacing': 'error', // 箭头函数的箭头前后需要空格
// "arrow-parens": ["error", "always"], // 在arrow函数参数中需要paren
'arrow-body-style': ['error', 'always'], // 在箭头函数体中需要大括号
'no-confusing-arrow': ['error', { allowParens: true }], // 不允许箭头函数与比较混淆
'no-useless-constructor': 'error', // 不允许不必要的构造函数
'no-dupe-class-members': 'error', // 不允许在类成员中使用重复名称
'no-duplicate-imports': ['error', { includeExports: true }], // 不允许重复导入
// "import/no-mutable-exports": "error", // 不要导出可变的绑定
// "import/prefer-default-export": "error", // 在只有一个导出的模块里,用 export default 更好
// "import/first": "error", // import 放在其他所有语句之前
'dot-notation': 'error', // 访问属性时使用点符号
'no-restricted-properties': 'error', // 做幂运算时用幂操作符 **
'one-var': ['off', 'always'], // 强制在函数中单独声明变量
'no-multi-assign': 'error', // 不要使用连续变量分配
'no-plusplus': 'error', // 不要使用一元递增递减运算符(++, --)
'no-unused-vars': 'off', // 不允许有未使用的变量
eqeqeq: ['error', 'always'], // 使用 === 和 !== 而不是 == 和 !=
'no-case-declarations': 'error', // 不允许在case/default子句中使用词法声明
'no-nested-ternary': 'error', // 三元表达式不应该嵌套,通常是单行表达式
'no-unneeded-ternary': 'error', // 避免不需要的三元表达式
'no-mixed-operators': 'error', // 不允许不同运算符的混合
'nonblock-statement-body-position': ['error', 'beside'], // 强制单行语句的位置
'brace-style': 'error', // 需要大括号样式
'no-else-return': 'error', // 如果if语句都要用return返回,那后面的else就不用写了。如果if块中包含return,它后面的else if块中也包含了return,这个时候就可以把else if拆开
'spaced-comment': [
'error',
'always',
{
line: {
markers: ['/'],
exceptions: ['-', '+'],
},
block: {
markers: ['!'],
exceptions: ['*'],
balanced: true,
},
},
],
// "indent": ["error", 2, { "SwitchCase": 1}], // 强制2个空格
'keyword-spacing': ['error', { before: true }], // 在关键字前后强制使用一致的间距
'space-infix-ops': ['error', { int32Hint: false }], // 用空格来隔开运算符
'padded-blocks': ['error', 'never'], // 不要故意留一些没必要的空白行
'array-bracket-spacing': ['error', 'never'], // 方括号里不要加空格
'object-curly-spacing': ['error', 'always'], // 花括号 {} 里加空格
'comma-spacing': ['error', { before: false, after: true }], // , 前避免空格, , 后需要空格
'key-spacing': ['error', { beforeColon: false }], // 在对象的属性中, 键值之间要有空格
'no-trailing-spaces': 'error', // 行末不要空格
'no-multiple-empty-lines': 'error', // 避免出现多个空行。 在文件末尾只允许空一行
'no-new-wrappers': 'error', // 不允许基元包装实例
radix: ['error', 'as-needed'], // 需要基数参数
// "id-length": "error",
camelcase: ['error', { properties: 'always' }], // 要求驼峰式命名对象、函数、实例
'new-cap': 'off', // 要求构造函数名称以大写字母开头
'no-underscore-dangle': 'error', // 不要用前置或后置下划线
},
};

Stylelint 配置

stylelint-config-standard 是 stylelint 的推荐配置

stylelint-order css 属性排序插件(先写定位,再写盒模型,再写内容区样式,最后写 CSS3 相关属性)

stylelint-config-recess-order stylelint-order 插件的第三方配置

安装依赖

1
npm i stylelint stylelint-config-standard stylelint-order stylelint-config-recess-order -D

根目录新建一个 .stylelintrc.js 文件

1
2
3
4
5
6
module.exports = {
processors: [],
plugins: [],
extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
rules: {}, // 可以自己自定一些规则
};

Commitlint 配置

@commitlint/cli 可以检查提交信息

@commitlint/config-conventional 是提交规范的配置包

安装

1
npm i @commitlint/cli @commitlint/config-conventional -D

根目录下新建 commitlint.config.js 文件

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
/*
规范commit日志
https://commitlint.js.org
*/

const types = [
'build', // 主要目的是修改项目构建系统(例如glup,webpack,rollup的配置等)的提交
'ci', // 修改项目的持续集成流程(Kenkins、Travis等)的提交
'chore', // 构建过程或辅助工具的变化
'docs', // 文档提交(documents)
'feat', // 新增功能(feature)
'fix', // 修复 bug
'pref', // 性能、体验相关的提交
'refactor', // 代码重构
'revert', // 回滚某个更早的提交
'style', // 不影响程序逻辑的代码修改、主要是样式方面的优化、修改
'test', // 测试相关的开发,
],
typeEnum = {
rules: {
'type-enum': [2, 'always', types],
},
value: () => {
return types;
},
};

module.exports = {
extends: ['@commitlint/config-conventional'],
/*
Level [0..2]: 0 disables the rule. For 1 it will be considered a warning for 2 an error.
https://commitlint.js.org/#/reference-rules
*/
rules: {
'type-enum': typeEnum.rules['type-enum'],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
},
};

配置 lint-staged 规则

package.json 里 添加

1
2
3
4
5
6
7
8
9
"lint-staged": {
"*.{js,vue}": [
"prettier --write",
"eslint --fix",
],
"*.less": [
"stylelint --fix"
]
}

配置 husky 检验钩子

package.json 里 添加

1
2
3
4
5
6
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}

这个时候来试下随便提交个 commit 看会发生什么

1
2
git add .
git commit -m "asdasd"

上面那个 commit 不符合提交规范 所以报错了

1
2
3
4
5
6
7
8
9
husky > commit-msg (node v14.16.0)
⧗ input: asdasd
✖ 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 failed (add --no-verify to bypass)

现在在来提交一个符合规范的 commit

1
2
git add .
git commit -m "feat: 新增校验commit信息、eslint规范提示、自动格式化代码"

出现这些信息就表示已经通过校验并提交了

1
2
3
4
husky > commit-msg (node v14.16.0)
[feat_infrastructure 78aefc7] feat: 新增校验commit信息、eslint规范提示、自动格式化代码
5 files changed, 2416 insertions(+), 774 deletions(-)
create mode 100644 commitlint.config.js

这通流程跑下来后,你的项目就可以支持提交时校验 commit 信息、eslint/stylelint 规范提示、提交前自动校验&格式化代码了

备注

本文使用的依赖版本号如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"husky": "^4.2.5",
"lint-staged": "^11.0.1",
"eslint": "^6.8.0",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-vue": "^6.2.2",
"babel-eslint": "^10.1.0",
"stylelint": "^13.13.1",
"stylelint-config-recess-order": "^2.4.0",
"stylelint-config-standard": "^22,
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
}