ESLint 这玩意儿用起来让人又爱又恨,一大堆配置能弄出一堆风格,稍不留神就整个红色波浪线恶心一下你,今天来好好瞧瞧这小可爱到底怎么配。
本文不讲解具体的规则内容,而是手把手告诉你 ESLint 有哪些配置项,都是什么意思,应该如何去配。建议点赞收藏,在开发项目时可以先看手册,再配合官方文档查看细节。
文件格式
常用的配置文件有 .js
和 .json
两种。其中,.js
的优先级更高。在 ESLint JSON 文件中,你也可以放心使用 JS 风格的注释(// 注释……),ESLint 会安全地忽略配置文件中的注释。
个人比较喜欢 .js
风格,可以在里面定义变量或者做一些额外的处理。
除了上述两种风格外,还有 cjs
、yaml
、yml
格式,甚至也可以直接在 package.json
中直接配置,有兴趣的可以参考配置文件。
配置对象
一套完整的 ESLint 文件大概长这样:
module.exports = {root: true,env: {},globals: {},extends: {},parser: 'xxxxxx'parserOptions: {},plugins: [],rules: {},// ......}module.exports = { root: true, env: {}, globals: {}, extends: {}, parser: 'xxxxxx' parserOptions: {}, plugins: [], rules: {}, // ...... }module.exports = { root: true, env: {}, globals: {}, extends: {}, parser: 'xxxxxx' parserOptions: {}, plugins: [], rules: {}, // ...... }
我们来介绍下这些常用的属性。
root 与层次结构
首先,ESLint 的配置文件可以有多份,允许放置在多个层次结构中。如果一个 .eslintrc
文件和一个被提示的文件在同一个目录下,那么该配置将被优先考虑(就近原则)。接着,ESLint 会继续沿着目录结构向上搜索,合并沿途发现的任何 .eslintrc 文件,直到到达 root: true
的 .eslintrc
文件或根目录。
也就是说,ESLint 会像贪吃蛇一样,从当前目录出发,一路吃掉沿途配置,所以,如要将这条蛇限制在一个特定的项目中,就需要在项目根层的 .eslintrc.*
中设置 root: true
。因为它一旦找到了 root,就会停止在父文件夹中寻找。
module.exports = {root: true,};module.exports = { root: true, };module.exports = { root: true, };
env 环境与变量
使用 env
指定环境,并通过将每个环境设置为 true
来启用想要的环境。
所以环境是什么?举个例子大家就一目了然了。
我们先在配置文件中添加一条规则:不允许使用未定义的变量。
module.exports = {rules: {'no-undef': 'error',},};module.exports = { rules: { 'no-undef': 'error', }, };module.exports = { rules: { 'no-undef': 'error', }, };
此时会发现这条规则对子自个儿报错了,真的是大义灭亲啊……
鼠标 hover 看下报错原因:
很明显,在 ESLint 看来 module
并没有被明确定义,所以触发了这条规则。但作为 Node 环境中内置的全局变量,它确确实实存在,所以我们必须在 env
中设置 node: true
来显示地告诉 ESLint 当前的运行环境为 Node。
module.exports = {env: {node: true}rules: {'no-undef': 'error',},};module.exports = { env: { node: true } rules: { 'no-undef': 'error', }, };module.exports = { env: { node: true } rules: { 'no-undef': 'error', }, };
此时,ESLint 就会添加 Node.js 的全局变量,module 就不会报错了。于此相似的还有浏览器中的 window
变量。
比如,我们创建一个 test.js
文件,随便写点东西:
window.test = '123';console.log(111);window.test = '123'; console.log(111);window.test = '123'; console.log(111);
会发现 ESLint 又报错了,原因也是变量未定义。
同理,我们需要在 env
中指定 浏览器的全局变量:
module.exports = {env: {node: true,browser: true}rules: {'no-undef': 'error',},};module.exports = { env: { node: true, browser: true } rules: { 'no-undef': 'error', }, };module.exports = { env: { node: true, browser: true } rules: { 'no-undef': 'error', }, };
ESLint 指定环境是为了避免我们潜意识中使用了与当前环境不符的变量,试想如果在浏览器环境中写了 Node.js 独有的语法,那么确实是会报错的。
另外,这些环境并不互斥,所以你可以一次定义多个环境。
globals 与 全局变量
globals
支持自定义扩展 env
中没有的全局变量。比如微信小程序中的 wx
全局变量,就需要我们自己添加了。
module.exports = {globals: {wx: 'writable', // readonly-只读 / writable-可改},rules: {"no-undef": "error"},}module.exports = { globals: { wx: 'writable', // readonly-只读 / writable-可改 }, rules: { "no-undef": "error" }, }module.exports = { globals: { wx: 'writable', // readonly-只读 / writable-可改 }, rules: { "no-undef": "error" }, }
writable
表示该变量允许被修改和覆盖,如果你觉得这样有风险,也可以设置为 readonly
标为只读。
rules 规则
ESLint 有大量的内置规则,当 ESLint 报错时,个人建议是先根据报错提示跳转到官网查看下规则,再决定是否需要修改。
规则的严重程度
一个规则可以指定三种严重程度
属性值 | 严重程度 | 说明 |
---|---|---|
error 或 2 | 错误 | 当使用 ESLint CLI 时,错误导致 CLI 以非零代码退出 |
warn 或 1 | 警告 | 当使用 ESLint CLI 时,在不改变退出代码报告警告内容。如仅报告警告内容,则退出代码为 0 |
off 或 0 | 无 | 彻底关闭规则 |
除严重程度外,如果规则有额外的选项,可以使用数组在后面进行追加,比如:
{"rules": {"quotes": ["error", "double"]}}{ "rules": { "quotes": ["error", "double"] } }{ "rules": { "quotes": ["error", "double"] } }
quotes
数组的第一个元素表示严重程度为 错误
,第二个元素表示 双引号
,说明必须使用双引号,否则 ESLint 会报错。
有些规则会有多个配置项,这时可以根据文档的说明,继续往后添加自己需要的options。比如 operator-linebreak
,它会为运算符强制执行一致的换行样式:
{"rules": {'operator-linebreak': ['warn','after',{overrides: {'?': 'before',':': 'before',},},],}}{ "rules": { 'operator-linebreak': [ 'warn', 'after', { overrides: { '?': 'before', ':': 'before', }, }, ], } }{ "rules": { 'operator-linebreak': [ 'warn', 'after', { overrides: { '?': 'before', ':': 'before', }, }, ], } }
参数一
: 警告级别的严重程度;
参数二
:要求在运算符之后换行;
参数三
:是一个对象,其中的 overrides
可以覆盖全局的规则,针对个别运算符单独配置。
所以基于该换行规则的正确写法就成了这样:
foo = 1 + 2;// 或者在运算符后面换行foo = 1 +2;// 三元运算符则需要在 '?' 和 ':' 之前换行answer = everything? 42: foo;foo = 1 + 2; // 或者在运算符后面换行 foo = 1 + 2; // 三元运算符则需要在 '?' 和 ':' 之前换行 answer = everything ? 42 : foo;foo = 1 + 2; // 或者在运算符后面换行 foo = 1 + 2; // 三元运算符则需要在 '?' 和 ':' 之前换行 answer = everything ? 42 : foo;
这些就是规则的基本配置。我们也可以直接使用插件内的规则,详情参见 plugins 插件 章节。
extends 扩展配置
配置 extends 后,就可以继承另一个配置文件的所有特征(包括规则、插件和语言选项)并修改所有选项。
它的值可以是一个指定配置的字符串或一个字符串数组。
可共享配置包
即发布到 npm 上的包,导出的是一个配置对象。当你在项目根目录下安装了这个包后,ESLint 就可以使用它了。
一个共享配置包会以 eslint-config-
作为前缀,我们在 extends
中使用时可以直接省略前缀。请看以下示例:
{"devDependencies": {"eslint-config-airbnb": "^19.0.4","@vue/eslint-config-prettier": "^7.0.0","@vue/eslint-config-typescript": "^11.0.2",}}{ "devDependencies": { "eslint-config-airbnb": "^19.0.4", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", } }{ "devDependencies": { "eslint-config-airbnb": "^19.0.4", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", } }
安装了3个共享配置包,在使用时都直接省略前缀。
{extends: ['airbnb','airbnb/hooks',"@vue/typescript","@vue/typescript/recommended","@vue/prettier",]}{ extends: [ 'airbnb', 'airbnb/hooks', "@vue/typescript", "@vue/typescript/recommended", "@vue/prettier", ] }{ extends: [ 'airbnb', 'airbnb/hooks', "@vue/typescript", "@vue/typescript/recommended", "@vue/prettier", ] }
需要注意的是,当 extends 是一个字符串数组时,每个额外的配置都会扩展前面的配置,也就是后面的 extends 会覆盖前面的 extends。
ESLint 内置的核心规则
eslint:recommended
:启用报告常见问题的核心规则子集;
eslint:all
:启用当前安装的 ESLint 版本中的所有核心规则。核心规则的集合会因 ESLint 的任何次要或主要版本改变而改变,因此不建议用于生产。
{extends: ["eslint:recommended","@vue/typescript/recommended","@vue/prettier",],}{ extends: [ "eslint:recommended", "@vue/typescript/recommended", "@vue/prettier", ], }{ extends: [ "eslint:recommended", "@vue/typescript/recommended", "@vue/prettier", ], }
最后,还有一种配置就是插件内的配置,详情参见 plugins 插件 章节。
plugins 插件
ESLint 插件是一个包含 ESLint 规则、配置、解析器和环境变量的集合的 npm 模块。通过插件包括自定义规则。插件可以强制使用某个风格指南并支持 JavaScript 扩展(比如 TypeScript)、库(比如 React)和框架(比如 Angular)。
插件的流行用例就是强制执行框架规定的最佳实践。比如 @angular-eslint/eslint-plugin 包括了使用 Angular 框架的最佳实践。
可以理解为插件就是别人写好并上传到 npm 上的一套 ESLint 包,里面定义了额外的规则、环境、配置、编译器等,开发者可以自行安装使用这些最佳实践。
插件的命名和使用规范
首先,插件名必须要含有 eslint-plugin-
前缀,最后才是插件的名字,比如 eslint-plugin-vue
。
像这种直接标明名字的包叫非范围包,还有一种 npm 包叫范围包,这种包会多出一个@org-name/
的前缀,比如 @typescript-eslint/eslint-plugin
,范围名称就是 @
和 斜杠/
之间的 typescript-eslint
,这种包一般是组织发布使用。对发包感兴趣的,可以参阅创建发布范围公共包。
无论是哪种包,在进行文件配置时,都可以省略 eslint-plugin-
前缀。事实上,我们也必须按照惯例来引用它们。以下是三种包的名称使用规范:
-
eslint-plugin-vue 简写 → vue
-
@typescript-eslint/eslint-plugin 简写 → @typescript-eslint
-
@tidio/eslint-plugin-rules 简写 → @tidio/rules
注册插件
在使用插件之前,你必须先使用 npm 安装它。然后使用 plugins
注册插件,它是由插件名称组成的数组列表:
{plugins: ['vue', '@typescript-eslint', '@tidio/rules'],}{ plugins: ['vue', '@typescript-eslint', '@tidio/rules'], }{ plugins: ['vue', '@typescript-eslint', '@tidio/rules'], }
接下来,我们就可以使用插件里定义的规则、环境或配置了。
使用插件配置
使用插件配置时,它的 extends
属性值由以下内容组成:
-
plugin:(区分 config 和 eslint:)
-
缩写报名
-
斜杠 /
-
插件中的配置名称(如 recommended)
请看以下示例:
{plugins: ['react', 'unicorn', 'promise'],extends: ['plugin:react/recommended','plugin:unicorn/recommended','plugin:promise/recommended',]}{ plugins: ['react', 'unicorn', 'promise'], extends: [ 'plugin:react/recommended', 'plugin:unicorn/recommended', 'plugin:promise/recommended', ] }{ plugins: ['react', 'unicorn', 'promise'], extends: [ 'plugin:react/recommended', 'plugin:unicorn/recommended', 'plugin:promise/recommended', ] }
使用插件环境
使用插件中的环境前,一定要在 plugins
数组中指定插件的名称,然后使用 插件简称
+ /
+ 环境名称
,比如说:
{"plugins": ["example"],"env": {"example/custom": true}}{ "plugins": ["example"], "env": { "example/custom": true } }{ "plugins": ["example"], "env": { "example/custom": true } }
使用插件规则
同理,要配置定义在插件中的规则,也必须在这条规则前加上 插件简称
和 /
。
{plugins: ['react', 'unicorn', 'promise'],rules: {'unicorn/better-regex': 'error','react/jsx-indent': ['error', 4],'promise/always-return': 'off',}}{ plugins: ['react', 'unicorn', 'promise'], rules: { 'unicorn/better-regex': 'error', 'react/jsx-indent': ['error', 4], 'promise/always-return': 'off', } }{ plugins: ['react', 'unicorn', 'promise'], rules: { 'unicorn/better-regex': 'error', 'react/jsx-indent': ['error', 4], 'promise/always-return': 'off', } }
parser 和 parserOptions
和 babel 一样,eslint 也是基于 AST 的。只是一个做代码的转换,一个做错误检查和修复。babel 插件和 eslint 插件都能够分析和转换代码。
默认情况下,ESLint 使用 Espree 作为其解析器(parser: '@/espree'
),而自定义解析器则可以让 ESLint 解析非标准的 JavaScript 语法。
parserOptions 则是用来设置解析器选项,并将直接传递给解析器的 parser() 方法。可选项有:
选项 | 说明 | 值 |
---|---|---|
ecmaVersion | 指定要使用的 ECMAScript 语法的版本 | 年份 或 “latest” |
sourceType | 文件资源类型 | “script” 或 “module”(ECMA 模块) |
allowReserved | 允许使用保留字作为标识符 | boolean |
ecmaFeatures | 表示想使用哪些额外的语言特性 | 具体如下 |
ecmaFeatures.globalReturn | 允许全局范围内的 return 语句 | boolean |
ecmaFeatures.impliedStrict | 全局严格模式 | boolean |
ecmaFeatures.jsx | 启用 JSX | boolean |
可用选项是基于解析器的,除上述公共的选项外,有些解析器还支持自定义的选项,比如 @typescript-eslint/parser
还支持 jsxPragma 选项,vue-eslint-parser
还支持parser选项。所以我们在使用时,应该先翻阅对应的文档。
TypeScript 解析器
如果你的项目使用了 TypeScript,可以安装并使用 @typescript-eslint/parser
来替代默认的 Espree
,它是一个将 TypeScript 转换为与 ESTree 兼容的形式的解析器,因此可以在 ESLint 中使用。
npm i @typescript-eslint/parser -Dnpm i @typescript-eslint/parser -Dnpm i @typescript-eslint/parser -D
{parser: '@typescript-eslint/parser',parserOptions: {ecmaFeatures: {jsx: true,},ecmaVersion: 11,sourceType: 'module',jsxPragma: 'React'},}{ parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 11, sourceType: 'module', jsxPragma: 'React' }, }{ parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 11, sourceType: 'module', jsxPragma: 'React' }, }
Vue 解析器
如果你的项目使用了 Vue + TypeScript,可以安装并使用 vue-eslint-parser
,它的 parserOptions
属性与默认的 Espree
所支持的属性相同。
npm i vue-eslint-parser @typescript-eslint/parser -Dnpm i vue-eslint-parser @typescript-eslint/parser -Dnpm i vue-eslint-parser @typescript-eslint/parser -D
{parser: 'vue-eslint-parser',parserOptions: {sourceType: 'module',ecmaVersion: 2018,ecmaFeatures: {globalReturn: false,impliedStrict: false,jsx: false}}}{ parser: 'vue-eslint-parser', parserOptions: { sourceType: 'module', ecmaVersion: 2018, ecmaFeatures: { globalReturn: false, impliedStrict: false, jsx: false } } }{ parser: 'vue-eslint-parser', parserOptions: { sourceType: 'module', ecmaVersion: 2018, ecmaFeatures: { globalReturn: false, impliedStrict: false, jsx: false } } }
你也可以使用 parserOptions.parser
指定一个自定义的解析器来解析 <script> 标记:
{parser: 'vue-eslint-parser',parserOptions: {parser: '@typescript-eslint/parser',sourceType: 'module',ecmaVersion: 2020,ecmaFeatures: {globalReturn: false,impliedStrict: false,jsx: true}}}{ parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', sourceType: 'module', ecmaVersion: 2020, ecmaFeatures: { globalReturn: false, impliedStrict: false, jsx: true } } }{ parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', sourceType: 'module', ecmaVersion: 2020, ecmaFeatures: { globalReturn: false, impliedStrict: false, jsx: true } } }
你还可以为<script lang=”…”>指定一个对象并单独更改解析器:
{parser: 'vue-eslint-parser',parserOptions: {parser: {// Script parser for `<script>`js: 'espree',// Script parser for `<script lang="ts">`ts: '@typescript-eslint/parser',// Script parser for vue directives (e.g. `v-if=` or `:attribute=`)// and vue interpolations (e.g. `{{variable}}`).// If not specified, the parser determined by `<script lang ="...">` is used."<template>": 'espree'},}}{ parser: 'vue-eslint-parser', parserOptions: { parser: { // Script parser for `<script>` js: 'espree', // Script parser for `<script lang="ts">` ts: '@typescript-eslint/parser', // Script parser for vue directives (e.g. `v-if=` or `:attribute=`) // and vue interpolations (e.g. `{{variable}}`). // If not specified, the parser determined by `<script lang ="...">` is used. "<template>": 'espree' }, } }{ parser: 'vue-eslint-parser', parserOptions: { parser: { // Script parser for `<script>` js: 'espree', // Script parser for `<script lang="ts">` ts: '@typescript-eslint/parser', // Script parser for vue directives (e.g. `v-if=` or `:attribute=`) // and vue interpolations (e.g. `{{variable}}`). // If not specified, the parser determined by `<script lang ="...">` is used. "<template>": 'espree' }, } }
更多配置参见vue-eslint-parser
overrides
通过 overrides
配置项覆盖配置中基于文件 glob 模式的配置,让我们能够更加精细地对某些文件进行检查。举个例子大家就很好明白了:
{overrides: [{files: ["*.ts", "*.vue"],rules: {"no-undef": "off"}},{files: ["*.vue"],parser: "vue-eslint-parser",parserOptions: {parser: "@typescript-eslint/parser",extraFileExtensions: [".vue"],ecmaVersion: "latest",ecmaFeatures: {jsx: true}},rules: {"no-undef": "off"}}]}{ overrides: [ { files: ["*.ts", "*.vue"], rules: { "no-undef": "off" } }, { files: ["*.vue"], parser: "vue-eslint-parser", parserOptions: { parser: "@typescript-eslint/parser", extraFileExtensions: [".vue"], ecmaVersion: "latest", ecmaFeatures: { jsx: true } }, rules: { "no-undef": "off" } } ] }{ overrides: [ { files: ["*.ts", "*.vue"], rules: { "no-undef": "off" } }, { files: ["*.vue"], parser: "vue-eslint-parser", parserOptions: { parser: "@typescript-eslint/parser", extraFileExtensions: [".vue"], ecmaVersion: "latest", ecmaFeatures: { jsx: true } }, rules: { "no-undef": "off" } } ] }
上述配置中,我们针对所有的 .ts
和 .vue
文件,关闭了 "no-undef"
检查。其次,针对 .vue
文件,我们还重新指定了解析器的配置。
总结
本期围绕 ESLint 的一些配置项,着重介绍了他们的含义以及如何使用,让我们更好的了解 ESLint 的面貌,至少在编辑器报红时,不至于摸不着头脑。
下篇我们将在项目搭建中,围绕以下几点展开讨论:
- 如何使用工具或命令行直接快速生成 ESLint 配置;
- 如何通过命令自动检查、定位和修复代码;
- 更友好的 VSCode 配置;
- ESLint 与 Prettier 的冲突以及如何解决。
敬请期待哈~?