前端使用工具强制实行代码规范(Vue)

前端使用工具强制实行代码规范(Vue)

场景

GitHub Demo

非强制性的规范都不会成为真正的规范。在之前,我们也会在团队内编写一些开发规范文档,却总是不能真正有效的执行下去,原因何在?

  • 其一,人是健忘的,就算真正看完了规范文档,也不可能记住每一条规范,并且在代码中正确实践。
  • 其二,人是懒惰的,即便有着规范,但只要写代码的时候没有强制性,那总是不会在意的。

那么,使用自动化的工具是为首选,而前端恰好有了一些可用的工具来帮助我们。

ESLint

ESLint 是一个对 JS 低级错误进行修复的工具,甚至于,现在连 TypeScript 官方也抛弃 TSLint 转向这个工具了,因为它的生态更大,实现的规则数量远远多于 TSLint,能把 JS 中的大部分低级错误检测出来,并能自动修复其中一部分。

安装依赖

1
npm i -D eslint babel-eslint eslint-plugin-vue @vue/cli-plugin-eslint

依赖解释

  • eslint: 本体包
  • babel-eslint: eslint 与 babel 整合包
  • eslint-plugin-vue @vue/cli-plugin-eslint: eslint 与 vue 整合包

进行配置

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
// .eslintrc.js
module.exports = {
root: true,
//环境
env: {
browser: true,
commonjs: true,
es6: true,
node: true,
},
extends: [
//继承 vue 的标准特性
'plugin:vue/essential',
'eslint:recommended',
],
rules: {
/**
* 禁止不需要的括号,例如 const i = (1 + 1),但该规则存在的问题是会认为类两侧的圆括号也是不合法的
* 例如: billId => (StringValidator.isBlank(billId) ? '否' : '是')
*/
// 'no-extra-parens': 'error',
/**
* 禁止魔法值,该规则的主要问题是很多误报
* 例如: offset / size + (offset % size === 0 ? 0 : 1)
*/
'no-magic-numbers': 'off',

//禁止使用 var,强制要求使用 const/let
'no-var': 'error',
//不使用未定义的变量
'no-use-before-define': 'error',
//不允许在循环中使用 await,请使用 Promise.all
'no-await-in-loop': 'error',
//不允许使用 return await,直接返回 Promise 就好
'no-return-await': 'error',
//不允许使用 console 对象,因为会打印到控制台上
'no-console': 'error',
//使用 class 中的方法必须使用 this. 前缀
// 'class-methods-use-this': 'error',
//禁止使用 alert, confirm, prompt,该 API 会阻断所有其他操作,但该规则存在的问题是有可能需要之后用上方便统一调用呢?
'no-alert': 'error',
//禁止使用 eval,该操作是危险的
'no-eval': 'error',
'no-implied-eval': 'error',
//禁止使用 new Function 创建函数
'no-new-func': 'error',
//禁止使用包装类 String, Number, Boolean
'no-new-wrappers': 'error',
//禁止把语句作为计算结果返回,请使用两条语句
'no-return-assign': 'error',
//禁止使用不应该的 concat 连接,字符串请使用 + 连接,数组则可以使用 [...arr1, ...arr2]
'no-useless-concat': 'error',
//禁止 yoda 比较,不要用 1 === i 而是用 i === 1 更加自然
yoda: 'error',
//禁止没有用的三元运算符,就算是 ⑨ 也知道这样做有问题 answer === 1 ? true : false
'no-unneeded-ternary': 'error',
//禁止无用的计算属性 const obj = { ['1']: 1 }
'no-useless-computed-key': 'error',
//如果可以使用解构,那就进行警告,例如 const name = user.name 就应该被替换为 const { name } = user,避免了重复声明,也能进行默认赋值等操作
'prefer-destructuring': 'warn',
//使用 rest 不定参数代替全局变量 arguments
'prefer-rest-params': 'error',
//使用扩展运算符代替 apply 调用
'prefer-spread': 'error',
//使用 Symbol 必须使用描述说明它要做什么
'symbol-description': 'error',
//如果可以使用反射,那就是用反射调用,Reflect 代替 delete 关键字删除对象属性
'prefer-reflect': 'warn',
},
parserOptions: {
//使用 babel 解析语法
parser: 'babel-eslint',
//使用 es2017 的语法
ecmaVersion: 2017,
},
}

如果有需要忽略的文件也可以在 .eslintignore 文件中进行配置

1
2
3
// .eslintignore
`// 忽略掉 TypeScript 类型定义文件
*/**/*.d.ts`

添加脚本

1
2
3
4
5
6
7
8
9
// package.json
{
// 其他配置。。。
"scripts": {
"lint:js": "vue-cli-service lint",
"fix:js": "vue-cli-service lint --fix"
}
// 其他配置。。。
}

运行脚本

1
npm run lint:js

现在,你可以检测到代码中的问题,并修复它了。

相关链接

WebStorm 配置 ESLint 即时检查:
WebStorm 使用 ESLint
参考: https://stackoverflow.com/questions/28808857

StyleLint

StyleLint 是一个用来对 CSS 进行校验/修复的工具,和 ESLint 类似,但却针对 CSS 方面。我们使用它用来避免一些不好的 CSS 写法,也能避免 code review 时被其他人吐槽。。。

安装依赖

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

依赖解释

  • stylelint: 本体包
  • stylelint-config-standard: stylelint 标准配置
  • stylelint-scss: stylelint scss 支持插件包
  • stylelint-order: stylelint 属性排序插件

进行配置

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// stylelint.config.js
module.exports = {
plugins: ['stylelint-scss', 'stylelint-order'],
extends: ['stylelint-config-standard'],
rules: {
/**
* 关键问题在于是否要忽略空检查
* 下面两种都会被判断为错误
* <style lang="scss" scoped></style>
* a {}
* 上面一种是很常见的
*/
// 'no-empty-source': null,
//禁止注释两侧出现空白(IDEA 默认就没有空白)
'comment-whitespace-inside': 'never',
//scss 的特定符号将被 css 检查时忽略而在 scss 检查时才会生效
'at-rule-no-unknown': null,
'scss/at-rule-no-unknown': true,
//CSS 声明定义顺序,自定义 class 放在默认元素上面
'order/order': ['custom-properties', 'declarations'],
/**
* CSS 属性顺序
* 1. 控制外部属性
* 2. 盒模型
* 3. 视觉
* 4. 其他
* 5. 未定义
*/
'order/properties-order': [
[
//指令
'composes',
'@import',
'@extend',
'@mixin',
'@at-root',
//盒模型相关
'display',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'grid',
'grid-area',
'grid-auto-rows',
'grid-auto-columns',
'grid-auto-flow',
'grid-gap',
'grid-row',
'grid-row-start',
'grid-row-end',
'grid-row-gap',
'grid-column',
'grid-column-start',
'grid-column-end',
'grid-column-gap',
'grid-template',
'grid-template-areas',
'grid-template-rows',
'grid-template-columns',
'gap',
'align-content',
'align-items',
'align-self',
'justify-content',
'justify-items',
'justify-self',
'order',
'float',
'clear',
'box-sizing',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
//定位相关
'position',
'top',
'right',
'bottom',
'left',
'z-index',
//边框
'border',
'border-color',
'border-style',
'border-width',
'border-top',
'border-top-color',
'border-top-width',
'border-top-style',
'border-right',
'border-right-color',
'border-right-width',
'border-right-style',
'border-bottom',
'border-bottom-color',
'border-bottom-width',
'border-bottom-style',
'border-left',
'border-left-color',
'border-left-width',
'border-left-style',
'border-radius',
'border-top-left-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-image',
'border-image-source',
'border-image-slice',
'border-image-width',
'border-image-outset',
'border-image-repeat',
'border-collapse',
'border-spacing',
//溢出
'object-fit',
'object-position',
'overflow',
'overflow-x',
'overflow-y',
//版式
'color',
'font',
'font-weight',
'font-size',
'font-family',
'font-style',
'font-variant',
'font-size-adjust',
'font-stretch',
'font-effect',
'font-emphasize',
'font-emphasize-position',
'font-emphasize-style',
'font-smooth',
'line-height',
'direction',
'letter-spacing',
'white-space',
'text-align',
'text-align-last',
'text-transform',
'text-decoration',
'text-emphasis',
'text-emphasis-color',
'text-emphasis-style',
'text-emphasis-position',
'text-indent',
'text-justify',
'text-outline',
'text-wrap',
'text-overflow',
'text-overflow-ellipsis',
'text-overflow-mode',
'text-orientation',
'text-shadow',
'vertical-align',
'word-wrap',
'word-break',
'word-spacing',
'overflow-wrap',
'tab-size',
'hyphens',
'unicode-bidi',
'columns',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'page-break-after',
'page-break-before',
'page-break-inside',
'src',
//视觉
'list-style',
'list-style-position',
'list-style-type',
'list-style-image',
'table-layout',
'empty-cells',
'caption-side',
'background',
'background-color',
'background-image',
'background-repeat',
'background-position',
'background-position-x',
'background-position-y',
'background-size',
'background-clip',
'background-origin',
'background-attachment',
'background-blend-mode',
//动画
'transition',
'transition-delay',
'transition-timing-function',
'transition-duration',
'transition-property',
'animation',
'animation-name',
'animation-duration',
'animation-play-state',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-fill-mode',
//其他
'appearance',
'content',
'clip',
'clip-path',
'counter-reset',
'counter-increment',
'resize',
'user-select',
'nav-index',
'nav-up',
'nav-right',
'nav-down',
'nav-left',
'pointer-events',
'quotes',
'touch-action',
'will-change',
'zoom',
'fill',
'fill-rule',
'clip-rule',
'stroke',
],
{
unspecified: 'bottom',
},
],
//CSS 属性值顺序
// 'order/properties-alphabetical-order': [],
},
}

添加脚本

1
2
3
4
5
6
7
8
9
// package.json
{
// 其他配置。。。
"scripts": {
"lint:css": "stylelint src/**/*.{vue,html,css,scss,sass}",
"fix:css": "stylelint --fix src/**/*.{vue,html,css,scss,sass}"
}
// 其他配置。。。
}

运行脚本

1
npm run lint:css

相关链接

WebStorm 使用
WebStorm 配置 StyleLint 即时检查:
WebStorm 配置即时检查
参考 https://stackoverflow.com/questions/54304313/

添加外部工具以进行快速修复
添加外部工具以进行快速修复
然后添加一个快捷键即可

Prettier

Prettier 是一个代码格式化工具,但并非针对一种语言,对 HTML/CSS/JavaScript/Vue/SCSS 都有效果。可以通过配置文件在不同项目间统一代码格式化,以修正不同编辑器/IDE 之间格式化不同的问题。

安装依赖

1
npm i -D prettier eslint-plugin-prettier eslint-config-prettier prettier-eslint-cli stylelint-config-prettier stylelint-prettier

依赖解释

  • prettier: 本体包
  • eslint-plugin-prettier eslint-config-prettier prettier-eslint-cli: prettier 与 eslint 整合包
  • stylelint-config-prettier stylelint-prettier: prettier 与 stylelint 整合包

进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .prettierrc.js
module.exports = {
// 缩进宽度
tabWidth: 4,
// 单行最大宽度
printWidth: 120,
// 去掉代码结尾的分号
semi: false,
// 使用单引号替代双引号
singleQuote: true,
// 尽量在所有地方都添加尾逗号
trailingComma: 'all',
// 换行符
endOfLine: 'crlf',
}

还需要修改 eslint 与 stylelint 的一些配置

1
2
3
4
5
6
7
8
9
10
11
12
// .eslintrc.js
module.exports = {
// 其他配置。。。
extends: [
//继承 vue 的标准特性
'plugin:vue/essential',
'eslint:recommended',
//避免与 prettier 冲突
'plugin:prettier/recommended',
],
// 其他配置。。。
}
1
2
3
4
5
6
// stylelint.config.js
module.exports = {
// 其他配置。。。
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
// 其他配置。。。
}

添加脚本

1
2
3
4
5
6
7
8
// package.json
{
// 其他配置。。。
"scripts": {
"format": "prettier-eslint --write \"src/**/*.{js,vue,html,scss,css}\""
}
// 其他配置。。。
}

运行脚本

1
npm run format

相关链接

WebStorm 配置使用 Prettier 快速格式化:
WebStorm 配置使用 Prettier 快速格式化
建议修改为全局使用 Prettier 格式化,避免记忆两个格式化快捷键

husky 与 lint-staged

强制使用 linter 检查代码,不通过检查则无法提交代码,以使 linter 真正得到有效执行。

安装依赖

1
npm i -D husky lint-staged

依赖解释

  • husky: 在项目中添加 git 钩子,在 git 各个生命周期(姑且这样称呼吧)中执行一些自定义操作。我们这里主要是用在 git 提交之前执行 linter 操作,不通过则提交无效。
  • lint-staged: 简而言之,就是只针对 git 提交的文件进行一些操作,而非整个项目的所有文件。我们这里主要是用在 git 提交之前进行 linter 时只针对提交的文件,以进行渐进式的重构。

进行配置

1
2
3
4
5
6
7
8
9
// .huskyrc.js
module.exports = {
hooks: {
// git commit 前的钩子
'pre-commit': 'lint-staged',
// 修复 IDEA 的一些奇怪问题 <https://youtrack.jetbrains.com/issue/IDEA-135454>
'post-commit': 'git update-index --again',
},
}
1
2
3
4
5
6
7
8
9
// lint-staged.config.js
module.exports = {
'src/**/*.{js,vue}': ['eslint --fix', 'git add'],
'src/**/*.{vue,html,css,scss,sass}': ['stylelint --fix', 'git add'],
'src/**/*.{js,vue,html,css,scss,sass}': [
'prettier-eslint --write',
'git add',
],
}

有人说 lint-staged 并行运行多个命令可能会有问题,因为 nodejs 本身在写入文件时不会加锁,导致多线程下可能存在问题(吾辈目前还没遇到过.JPG)

之后,在我们使用 git commit 时就会触发 lint 操作了!

相关链接

总结

基本上,这些工具初次配置起来还是非常麻烦的,但这是一件一劳永逸的事情,所以还是值得花时间去做的。

注:
目前还存在的问题是:
ESLint 检测出来的部分错误能使用 A-Enter 修复
Prettier 与 WebStorm 自身格式化不能共存(自动切换)
Prettier 在 WebStorm 中无法直接配置导致上面问题存在的必要性
StyleLint 不能使用 A-Enter 修复且不能与 WebStorm 共存
可能的解决方案是找一下是否有一种方式能够让 WebStorm 一个快捷键执行多条命令,或者,写一个可用的插件。

参考