前端老项目接入eslint从配置到上线的一些思考

背景

在日常需求迭代中,代码的规范与质量是编码的重要一环。Eslint 作为规则扫描器,能够对前端代码进行有效管控,避免出现低级错误,对于前端项目或多或少肯定都会看到 eslint 的相关配置。

但目前存在一些老项目, eslint 的配置仅仅停留在了多年前加的一些 eslint 规则上,没有任何其他动作,导致平常开发中有如下痛点:

  1. 本地不方便开启保存自动格式化,因为对于老页面,如果开启的话会造成大量的无关 diff。
  2. 提交代码的时候会受到莫名其妙的卡控。
  3. 过 pr 的时候分号、空格、换行各个地方不对齐,逼死强迫症系列。

基于此,前段时间对老项目的 eslint 进行了一次完善,分享一下整个配置和思考的过程。

选取规则

eslint

eslint 规则可以单独一条条配置,也有一些规则的集合比如官方推荐的 eslint:recommended,框架相关的 plugin:vue/recommended,还有公司开源出来的整套规则比如 Airbnb 的 eslint-config-airbnb,腾讯的 eslint-config-alloy。

选取什么规则不是非常重要,大部分规则集也是类似的,此外本地也可以定义相同的规则名对规则集进行覆盖。

以 alloy 的规则为例,按照 eslint-config-alloy 中的文档安装完相应的 node 包以后,在本地根目录中新建 .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
module.exports = {
extends: ['alloy', 'alloy/vue'],
env: {
// 你的环境变量(包含多个预定义的全局变量)
},
globals: {
// 全局变量
moment: 'readonly',
},
rules: {
// 自定义你的规则 0-关闭,1-warn,2-error
'vue/component-tags-order': 0,
'vue/no-deprecated-slot-attribute': 0,
'vue/no-deprecated-slot-scope-attribute': 0,
'vue/no-duplicate-attributes': [
'error',
{
allowCoexistClass: true,
allowCoexistStyle: true,
},
],
'no-param-reassign': 1,
'no-console': 1,
'no-magic-numbers': 1,
'default-case': 2,
},
};

如上所示,我们可以在 rules 中定义或者覆盖一些规则。

Prettier

Prettier 是一个代码格式化工具,相比于 eslint 中的代码格式规则,它提供了更少的选项,却更加专业。

image-20220923090302423

相比于 eslint, Prettier 主要格式样式相关的,比如有没有分号、空格数、一行最大字符数等等,而 eslint 通过解析出代码的 AST ,可以自动格式化或者检测出一些潜在的问题,比如是否允许使用 console、变量声明但未使用、switch 缺少 defaut 等。

当然 eslint 也可以配置样式相关的规则,但存在一些情况 eslint 无法胜任,因此格式化相关的我们都交给更专业的 Prettier ,安装 Prettier 的 node 包,并且根目录增加配置文件 .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
33
34
35
36
37
38
39
40
41
42
// .prettierrc.js
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
bracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
// 格式化内嵌代码
embeddedLanguageFormatting: 'auto',
};

编辑器自动修复

这一步我认为是推动 eslint 最重要的一步,大家抗拒项目添加 eslint 一个很大的原因就是本地没有开启实时检查和自动修复,当提交 commit 的时候遇到 eslint 规则卡控就很难受了。

团队内都使用的 VSCode 进行开发,可以安装 Eslint 和 Prettier 插件。

image-20220925113210716

image-20220925113256543

在本地新增 .vscode/settings.json 文件进行插件的配置,并且该文件不忽略 git ,所有人共享。

1
2
3
4
5
6
7
{
"editor.defaultFormatter": "esbenp.prettier-vscode", // 用 Prettier 格式化
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true // 保存时自动进行 eslint 的修复
},
"editor.formatOnSave": true // 保存时自动格式化
}

这个文件是 VSCode 针对当前工程的配置,配置后保存文件的时候插件会自动帮助我们格式化,同时有实时的错误提示。

Kapture 2022-09-25 at 15.41.13

这里需要注意的一点是,保存的时候会同时进行 prettier 和 eslint 的修复,如果 eslint 也配置了样式相关的规则,此时可能发生冲突,导致自动格式化后会有 eslint 的报错,此时可以将相应的 eslint 规则手动关闭,也可以引入 eslint-config-prettier 这个规则集批量关闭。

commit 卡控

为了保证 eslint 规则的有效,需要在提交 commit 的时候进行检查,如果存在没有修复的 eslint 问题直接终止提交。

直接使用 "husky": "^1.3.1" 和 "lint-staged": "^8.1.5" 两个 node 包,需要注意下版本号,最新的配置有些不同了,下边是该版本下的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
],
"src/**/*.{js,vue,html,css,scss,sass}": [
"prettier --write",
"git add"
]
}
},

husky 提供了 pre-commit 的钩子,然后 lint-staged 对暂存区代码自动进行格式化,如果出错的话会直接退出。

这样当我们提交 commit 的时候就会运行 eslint 和 prettier 进行代码的格式化。

image-20220925162909689

流水线卡控

虽然上一步对 commit 进行了卡控,但如果 git commit 的时候添加了 -n 参数,卡控检查也就直接跳过了。

如果想彻底的卡控,我们可以在打包流水线上增加一个 lint 的插件进行检查。

这里实现卡控有两种思路:

  1. 发布分支和 master 做 diff,仅仅对 diff 出的 commit 进行 eslint 的检查。

    但这里可能存在两个问题需要注意:

    如果本地合并 master 的时候产生了冲突,然后解决冲突会新提交一个 commit 。 此时 diff 出来的 commit 可能会包含其他人的代码,如果之前的代码没有 lint ,此时就需要自己 lint 了。

    如果上线流程是先合并 master ,那么上线的时候 master 已经有了自己的代码,此时上线分支和 master 就没有任何 diff 了,所以也就起不到卡控的作用了。

  2. 卡控分支前 n 天的 commit 。

    理想情况下,前 n 天只包含自己的 commit 和已经 lint 过的 commit , merge master 的 commit 可以自动过滤掉,因此可以很好的对新加的代码进行卡控。

    当然还是无法完全避免遇到别人没有 lint 过的代码,此时还是需要自己进行修复了。

    具体逻辑可以参考这个 node 包。

不管是哪种方法,因为是在老项目引入的 lint ,前期如果在流水线加 lint 卡控的话一定会遇到明明不是自己代码,却被 lint 卡控拦截的情况。

我个人看法是流水线 lint 其实不加也可以,如果编辑器自动修复添加了、commit 卡控也添加了,这已经足够了,如果真有人通过 -n 绕过卡控,那肯定是有理由的,也没必要走流水线再卡控。

上线

因为老项目中会有大量的不符合 eslint 规则的代码,因此上线有两种方案。

  1. 本地进行全量文件的 eslint --fix 后上线:

    优点:未来开发时原有文件的 lint 问题不用关心,开发者只需关注原有 error 和自己当前的 lint 问题即可。

    缺点:由于改动文件数较多,eslint 不可完全信任,贸然上线可能会造成线上问题。

  2. 仅仅上线 eslint 的卡控和保存时自动 lint 的配置:

    优点:未改动代码逻辑,不会存在引发线上问题的隐患。

    缺点:当开发者修改、保存老文件后,会自动触发 lint 修复,从而污染混淆本身的修改,增加后续 code review 工作负担。

我是偏向于第 2 个方案的,虽然 eslint 自动修复一般不会引起问题,但程序肯定是不能 100% 相信的,如果造成了线上问题反而得不偿失。

如果采用第 2 个方案,后续开发老页面保存的时候一定会出现大面积的自动 lint,我们可以在添加新代码前先保存一下触发 lint 并且提交一个 msg 为 lint auto fix 的 commit 。这样做有两个好处:

  1. 后续其他人遇到问题代码排查的时候看到 lint fix 就知道了这行代码不是你写的,他需要再往前找一个 commit 的提交人。
  2. 过 pr 的时候我们可以按 commit 看,第一个 lint 的 commit 如果没什么问题可以直接跳过,减轻 cr 的负担。

总

在业务迭代繁忙的时候,想在老项目中引入 eslint 其实还挺难的,毕竟业务价值很难讲清楚,一个反向逻辑就是现在项目没有 eslint 也运行的好好的,但加入 eslint 有什么收益呢?

另一方面,当有人推动项目 eslint 的规则的时候仅仅添加规则和卡控,其他的步骤不去推动,当越来越多人遇到需要手动修复 eslint 或者因为 eslint 的问题被卡控提交,内心就会不断地增加对 eslint 的抗拒。

在安装相关插件、node 包的时候需要注意下版本号,找到匹配自己包的版本号的配置,不然可能会遇到配置了但不生效的问题。

当有新项目开发的时候,一定要把 eslint 的自动修复、相关配置都搞好,这样开发的时候也舒服,未来也不用再进行 eslint 的治理了。

未来也可以结合平时开发的经验和发生的线上问题,逐步完善 eslint 中的 rules 规则,使得项目代码质量越来越高。

windliang wechat