本文主要内容为阐述现在比较主流的 ESLint 配置方案。当然本文也有一定倾向性,主要是针对 TS 和 React 的生态。
独立的 eslint-config 包
无论是怎么样的项目统一风格是非常必要的;对于个人而言,名下所有的项目有统一的代码风格意义也类似。
所以我们通常需要将 ESLint 配置文件抽象成单独的包,可以作为 monorepo 的本地包使用或者发布到远程给到各个独立的工程使用,相对都很灵活。
由于我的项目都塞在一个由 rushjs 管理的大 monorepo 中,所以下文所有内容的都是在 monorepo 下进行的。
我们新建一个名为 eslint-config 的包,当然为了便于区分 monorepo 中的包名都会有一个 scope,比如我的包就会带上我的艺名叫 @july_cm/eslint-config。
由于 ESLint 配置文件支持多种语言,且可以通过 extends 字段继承。
这里顺便列一下 extends 配置的几种做法:
// 数组中的每个配置项继承它前面的配置
"extends": [
// eslint-config-standard
"standard",
// @standard/eslint-config
"@standard",
// eslint-plugin-react 中的 recommended 配置
"plugin:react/recommended",
// @typescript-eslint/eslint-plugin 中的 recommended 配置
"plugin:@typescript-eslint/recommended",
// 直接写路径也行
"./node_modules/coding-standard/.eslintrc-es6"
]
创建一个名为 .eslintrc.base.js 作为基础配置包方便后续共享衍生出多种适用于不同场景的包,JavaScript 要比 JSON 等纯配置文件灵活太多,所以用 JavaScript。
梭哈的艺术之 @babel/eslint-parser
市面上有众多 ESLint 解析器,我们首选 babel 作为通用且默认的解析器(没错就是一把梭哈)。
我们希望 babel 能把一些js、jsx等通常的文件解决掉。
module.exports = {
parser: '@babel/eslint-parser',
parserOptions: {
requireConfigFile: false,
babelOptions: {
babelrc: false,
configFile: false,
cwd: __dirname,
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
ignorePatterns: ['**/node_modules', '**/dist']
};
做到这步已经完成了百分之八十的工作,整个 eslint-config 已经可以 work 了。
高贵的 TS 之 @typescript-eslint/parser
简单来讲就是 babel 搞定不了 ts,咱们 typescript 有自己的解析器。
我们需要借助 ESLint config 中的 overrides 字段对 .ts 和 .tsx 文件做一些额外的解析
{
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
},
],
}
上面的代码只是一个简单的示例,想要把 ESLint 规则配置的“称心如意”还得细致的看看每个插件的 rules 配置项。
代码风格必备之 Prettier
Prettier 的话大家应该也不陌生,官方对他的定义是这样的:
有了 ESLint 为啥我们还需要 Prettier 呢?可以理解为对代码的格式化分为两个方面:
代码质量问题 and 代码风格问题
ESLint 主要处理的还是代码质量问题,比如 no-unused-vars、prefer-promise-reject-errors 等等。其实把这些搞定了基本上 cover 了程序的潜在风险,奈何敲代码的都是强迫症,对于换不换行这样一个小问题都可以讨论半天。所以 Prettier 就站出来了说:“你们别吵了,都听我的”。大家听了纷纷点头。
但 ESLint 和 Prettier 的标准并非完全对齐的,所以很容易出现冲突导致大家左右为难。所以我们依然以 ESLint 作为主体,而 Prettier 以 ESLint 插件的形式入场。
我们使用 eslint-config-prettier 这个配置 ban 掉 ESLint 所有和 Prettier 有冲突的配置,也就是说 ESLint 的一部分职责被 Prettier 接管了。
{
extends: ['prettier']
}
然后其实到此为止已经能用了,但我们还可以继续做些事情。我们可以引入 eslint-plugin-prettier 这个插件,它的主要作用是把 Prettier 推荐的格式问题的配置以 ESLint rules 的方式写入,这样可以统一报错来源,我们不再需要关系 Prettier 只需要知道 ESLint 有规则在报错即可。
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
如果觉得没问题,那我们还可以把上述两个步骤合起来,这也是官方推荐的配置
{
extends: ["plugin:prettier/recommended"],
rules: {
'prettier/prettier': [
'warn',
{
// 这里写 prettier 的配置
// 不读取 prettier 配置文件,统一走 eslint 配置
usePrettierrc: false,
},
],
},
}
当然别忘了我们需要安装相关的内容
pnpm i eslint-config-prettier eslint-plugin-prettier prettier -D
强迫症必备之 jsonc-eslint-parser
JSON 从来都不是 ESLint 的目标客户,因为 JSON 文件在项目中往往有多种作用。
它可能是某种 schema 或者是 i18n 文件甚至可能是数据文件,不方便一把去校验格式,一句话就是 JSON 的水很深 ESLint 把握不住。
但有一种 JSON 文件它非常规范,没错就是 package.json。乱七八糟的字段顺序和 dependencies 顺序足以逼死每一位强迫症。
如果我们能通过 ESLint 规范字段和依赖顺序,将大大降低协作时的代码冲突问题。
所以福音来了,我们在 overrides 数组末尾增加一项,专门检查和格式化 package.json。
{
overrides: [
{
files: ['package.json'],
parser: 'jsonc-eslint-parser',
extends: ['plugin:jsonc/base', 'plugin:jsonc/prettier'],
rules: {
'jsonc/sort-keys': [
'warn',
{
pathPattern: '^$',
order: ['name', 'version', 'author', 'scripts', 'dependencies', 'devDependencies'],
},
{
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
order: { type: 'asc' },
},
],
},
},
],
}
jsonc-eslint-parser 的 jsonc/sort-keys 可以对 JSON 的字段属性做排序,上面的代码只是个简单的例子,不是完全体,可以按照 npmjs docs 的顺序把规则补充完整,然后你就会得到一个十分有序的 package.json。
VSCode 配置
要让 validate 和 format 能力在 VSCode 中生效,还需要下载安装 VSCode ESLint Extension(其他 IDE 没用过不了解,非常卑鄙的无视)
{
// 指定 eslint 路径,这样其他 package 就不需要再安装 eslint 了
"eslint.nodePath": "packages/eslint-config/node_modules/eslint",
"eslint.probe": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.validate": [
"json"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
有一个要注意的坑,ESLint 插件的文档上说明了 eslint.probe 基本上是代替了 eslint.validate。
但实际测试下来 json 配置在 probe 字段上是无效的,请配置在 validate 字段上,否则 jsonc-eslint-parser 什么都做不了。
需要注意的是当配置 eslint.validate: [”json”] 时,ESLint 插件会校验所有的 .json 文件,所以我们需要在 .eslintrc 文件中补充 ignorePatterns 字段:
{
ignorePatterns: ['**/**.json', '!package.json']
}
它意为忽略所有的 .json 文件校验,但排除 package.json。同时还要注意该字段只能配置在顶层,不能放在 overrides 字段内哦。
配置使用
把写完的配置共享给其他包使用有多种方式,我个人比较推荐的方式是写成一个 defineConfig 函数,比较符合前端的直觉。
// 其他项目中的 .eslintrc.js 文件
const { defineConfig } = require('@july_cm/eslint-config');
module.exports = defineConfig({});
代价就是其他引用包也只能使用 JavaScript 作为配置文件语言,但这点代价相对于 JavaScript 灵活性带来的好处只不过是沧海一粟完全可以接受。