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

背景

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

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

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

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

选取规则

eslint

eslint 规则可以单独一条条配置,也有一些规则的集合比如官方推荐的 eslint:recommended,框架相关的 plugin:vue/recommended,还有公司开源出来的整套规则比如 Airbnbeslint-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

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

当然 eslint 也可以配置样式相关的规则,但存在一些情况 eslint 无法胜任,因此格式化相关的我们都交给更专业的 Prettier ,安装 Prettiernode 包,并且根目录增加配置文件 .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 进行开发,可以安装 EslintPrettier 插件。

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

这里需要注意的一点是,保存的时候会同时进行 prettiereslint 的修复,如果 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 的时候就会运行 eslintprettier 进行代码的格式化。

image-20220925162909689

流水线卡控

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

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

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

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

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

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

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

  2. 卡控分支前 n 天的 commit

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

    当然还是无法完全避免遇到别人没有 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 并且提交一个 msglint auto fixcommit 。这样做有两个好处:

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

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

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

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

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

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

windliang wechat