前端代码规范-ESLint

394 阅读9分钟

前端代码规范-ESLint

ESLint 的作用是根据配置参数识别 ECMAScript/JavaScript 代码问题的工具,可以提前明确错误信息,并且可以使代码风格一致,减少错误的发生。本文介绍的是V8版本

自己总结的小文章

Husky+Lint-Staged+Commitlint+ESLint+Prettier

ESLint && Prettier

Prettier

.eslintrc 文件配置项的作用

1. plugins 中所定义得 plugin 作用是什么?

.eslintrc 文件中声明插件(如 plugins: ["myplugin"])主要是为了让 ESLint 知道你将使用哪些插件。不会做其他额外的操作。

这一步骤是必要的,因为插件提供了额外的功能,如自定义规则、解析器、处理器等。因为在.eslintrc 中是定义了 plugin 就会根据它的名称进行对应文件的查找,在 extendsrules 中直接使用 plugin 的 名称就能简写且 eslint就会按它定的规则来找目录。

2. 什么时候使用 extends

extends 继承插件的配置时,通常是引用插件中的 configs 对象。

示例配置

假设你想要定义一个只包含 rules 的简单配置,你可以像这样定义:

// 插件的 index.js 文件
module.exports = {
 configs: {
   simpleRules: {
     rules: {
       'myplugin/no-unused-variable': 'warn',
       'myplugin/other-rule': 'error',
     },
   },
  },
};

使用 extends

当你在 .eslintrc 文件中使用 extends 选项来继承这个简单的配置时,你需要指定配置的名称,而不是仅仅写 plugin:myplugin。正确的用法如下:

{
  "plugins": ["myplugin"],
  "extends": ["plugin:myplugin/simpleRules"]
}

3. 为什么要显示的配置 Plugin 中的规则

Plugin 中直接导出 rules 时,并需要用到 plugin 中的规则时,需要显示的定义,因为在定义 Plugins 时,并没有自动导入 rules

{
2  "plugins": ["myplugin"],
3  "rules": {
4    "myplugin/no-unused-variable": "warn", // 显式启用规则
5    "myplugin/other-rule": "error", // 显式启用规则
6    // 更多规则 ...
7  }
8}

4. parser 解析器的更换

ESLint 默认的解析器是 Esprima, 当使用 TS 时,需要设置成 typescript-eslint/parser 用 ts 的解析器来更好的进行代码的检测

5. processors 处理器的使用

在需要解析非标准格式的文件时,可以配置 processors 来处理文件。比如 .vue 格式的文件,在 eslint-plugin-vue 中就有定义处理器,专门处理 .vue 文件。

image.png

配置文件的优先级

  • ESLint V8 版本之前

    • .eslintrc.js
    • .eslintrc.cjs
    • .eslintrc.yaml
    • .eslintrc.yml
    • .eslintrc.json
    • package.json 下的 eslintConfig 字段 的顺序查找配置,相同目录下只有一个配置文件会生效。
  • 其中,常用的是eslintrc.[js|json]格式,下文采用 eslintrc.js 格式。

  • ESLint V9 版本

  • V9 版本中 需要导出一个数组对象配置

配置内容

执行环境配置 env

可以同时开启多个

{
    env:{
        nodetrue// Node.js 全局变量和作用域。
        browsertrue,// 浏览器全局变量。
        es6true // 支持 ES6 语法(不含 ES module),并开启 ES6 语法解析选项。
    }
}

开启环境后,ESLint 能正常解析代码中的全局变量,可能还会开启对应的语法解析选项。

全局变量 globals

env可以配置默认的全局变量, 但是一些模块也会注册一些自己的全局变量,如果不配置 globals 会识别失败,需要在配置中指出代码中用到的外部全局变量。

// 语法 {"var_name": "off"|"readonly"|"writable"}
{
    globals:{
        $: readonly,
        globalState: writable
    }
}

"off"的作用在于关闭env带来的全局变量,如在支持大部分 ES2015 语法,但不支持 Promise 的浏览器中,可以设置:

{
  "env":{
    "es6"true
  },
  "globals":{
    "Promise":"off"
  }
}
解析选项 parserOptions

在 ESLint 解析的时候提供一些语言特性的支持,如 ES 语法、JSX(不包含React的JSX语法)。ESLint 默认支持 ES5 语法。

{
  "parserOptions":{

    /* ecmaVersion: 指定 ECMA 语法版本
    *  取值:
    *      - "latest": 使用最新版本,现在 (2021) 等同于 12
    *      - 版本号:3, 5(默认), 6, 7, 8, 9, 10, 11, 12
    *      - 年份命名法:2015(=6), 2016(=7), 2017(8) ...
    */ 
    "ecmaVersion":  "latest",

    /* sourceType: 脚本类型,普通脚本 或 ES 模块脚本
    *  取值:"script"(默认) | "module"(ES 模块脚本)
    */
    "sourceType":"module",

    /* ecmaFeatures: 支持的特性语法*/
    "ecmaFeatures": {
      
      // 支持在全局使用 return 
      "globalReturn": true,

      // 默认使用严格模式(ES 5 或 以上)
      "impliedStrict": true,

      // 支持 JSX 语法
      "jsx": true 
    }
  }
}

其他解析器 parser

ESLint 默认使用 Espree 解析器,可以通过 parser 字段使用其他解析器。 解析器需要满足的条件:

  • 必须是一个Node模块, 并且在配置文件的项目目录下可以找到
  • 满足ESLint解析器接口

如果项目中用到一些语言增强工具(TypeScript、Babel)或者框架(React、Vue),就需要使用与之对应的解析器。

// Typescript
// 1. 在项目下 install @typescript-eslint/parser
// 2. 配置 parser 
{
    parser: '@typescript-eslint/parser'
}

注意: 使用其他插件时,parserOptions 仍然有效,它会作为参数传递给解析器.

规则配置 rules

ESLint 提供了大量的 内置规则 并且有自己的 extends eslint:all"eslint:recommended" 规则的配置语法是{"rules": {"rule_name": state | [state, ...options] }}, 其中,state 代表枚举值:

  • "off" 或者 0 :关闭规则,常用于关闭 extends 中的规则
  • "warn" 或者 1 :规则校验不通过时发出 warning 提示。
  • "error" 或者 2 :规则校验不通过时发出 error 提示,返回 1,表示 lint 检查不通过。
// 例子
{
  "rules":{
    // 使用 "off", "warn", "error"
    "no-console": "warn",
    // 使用数字(不推荐,语义不明确)
    "for-direction": 1,
    // 数组语法,但没有额外配置项
    "no-else-return": ["error"],
    // 数组语法,一个配置项
    "eqeqeq":["error","always"],
    // 数组语法,多个配置项
    "quotes": ["error", "double", { "avoidEscape": true }]
  }
}

使用插件 plugin

下载依赖插件之后,就可以在 ESLint 的 plugins 数组中添加该配置文件需要使用的插件。不过,插件的各项规则配置都是默认关闭的,所以 plugins 只是使用插件功能的前提,你必须在 rules extends env等配置项中开启你需要的规则特性。

{
  "plugins":["jest"],

  "extends": ["plugin:jest/recommended"],

  "env":{
    "jest/global":true
  },
  
  "rules":{
    "jest/valid-expect": "error"
  }
}
插件的命名

ESLint 对插件的命名做了规定:

  • eslint-plugin-<plugin-name> 缩写 <plugin-name>: 如eslint-plugin-jquery 缩写 jquery
  • @<scope>/eslint-plugin-<plugin-name> 缩写 @<scope>/<plugin-name> : 如 @jquery/eslint-plugin-jquery 缩写 @jquery/jquery
  • @<scope>/eslint-plugin 缩写 @<scope>: 如 @jquery/eslint-plugin 缩写 @jquery

一般使用不含 eslint-plugin 的简写形式

plugins 属性值可以省略包名中的 eslint-plugin- 前缀。

extends 属性值由以下内容组成:

  • plugin:
  • 包名(可以省略其前缀,如 react 是 eslint-plugin-react 的缩写)
  • /
  • 配置名称: 在自定义插件中导出的 configs 定义的 rules 组(如 recommended),在下文中还有提到
// JSON 格式的配置文件的示例:
{
 "plugins": [
        "react"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "rules": {
       "react/no-set-state": "off"
    }   
}
插件能带来什么

一个插件可以执行许多功能,包括但不限于添加新的规则和导出可共享的配置

ESLint 使用 <plugin-name>/XXX 的方式指定插件内的规则或其他配置。根据 ESLint Docs: Working with Plugins, 一个插件能带来:

  • 额外的规则,如 {"rules": {"react/boolean-prop-naming": "warning"}}
  • 环境,如 {"env": {"jest/global": true}}
  • 配置,如 {"extends": ["plugin:react/recommended"]}
  • 预处理器,如 {"process": "a-plugin/a-processor"}

通过 Glob 匹配应用配置 overrides

overrides 支持通过 Glob 模式 匹配特定文件集合,额外应用不同的配置。比如,我们经常需要在项目中根据文件类型应用不同的规则。

overrides 是一个配置对象数组,里面的对象支持大部分的 ESLint 配置,而且多了用于匹配文件的 files 数组和 excludedFiles 数组。当对文件进行 lint 检查时,目标文件相对于配置文件的相对路径会与这两组 glob 模式进行匹配,如果路径满足files中任何一个,且不满足 excludedFiles 中任何一个模式,则会被应用该配置。

/* ./src 目录下除了 a.js 下的 js 文件应用 no-alert 规则 */
{
  "overrides": [
    {
      "files": ["src/*.js"],
      "excludedFiles": "a.js",
      "rules": {
        "no-alert": "warn"
      }
    }
  ]
}

忽略文件 ignorePatterns

ignorePatterns 数组包含了一组 glob 模式,当文件路径匹配其中任意一个时被忽略,作用类似 .gitignore

一般都不希望对打包产物进行 lint 检查:

{
  "ignorePatterns": ["**/dist/**", "**/output/**"]
}

使用注释进行配置

ESLint 注释区分 ///**/, 可以在注释的同时说明原因,原因放在配置内容之后,用两个或两个以上 - 隔开。

  • 大部分的时候
/* 
*  单行关闭 
*/
alert('foo'); // eslint-disable-line

// eslint-disable-next-line -- I don't want eslint 
alert('foo');

/* eslint-disable-next-line */
alert('foo');

alert('foo'); /* eslint-disable-line */

// eslint-disable-next-line no-alert, quotes, semi
alert('foo');

foo(); // eslint-disable-line plugin/rule-name

/* 
*  块关闭 
*/
/* eslint-disable */
console.log("bar")
alert('foo');
/* eslint-enable */

/* eslint-disable no-alert, no-console */
alert('foo');
console.log('bar');
/* eslint-enable no-alert, no-console */

/*
*  整个文件关闭,在第一行使用  eslint-disable 
*/
/* eslint-disable */
alert('foo');
//...

/* eslint-disable no-alert */
alert('foo');
//...
  • 可以直接进行规则配置
/* eslint quotes: ["error", "double"], curly: 2 
  -----
  字符串内容含有'', 不想用转移和模版字符串
*/
const foo="'bar'"
  • 全局变量
// var1 只读,var2 读写
/* global var1, var2: writable */
  • 环境
/* eslint-env node, mocha */

配置层级与规则合并

配置文件层级

尽管一个目录下只有一个配置文件会生效,但 ESLint 支持在不同目录层级下放置多个配置文件。执行文件 lint 时,会从当前文件的目录开始,逐级往上查找配置文件,直到根目录或 遇到 {"root": true} 的配置,并合并不同层级的配置。

注:应该在项目根目录下设置 {"root": true} 避免不必要的查找和影响。

配置的优先级

由于 ESLint 支持多处配置,配置之间可能会冲突,需要规定优先级:

  • 配置类型上: 注释 > 命令行参数 > 配置文件
  • 文件层级上:(相对目标文件)近 > 远同一目录内:(只会采用一个配置文件)js > cjs > yaml > yml > json > package.json
  • 同一配置内:overrides > rule > extends
  • 同一选项内:后者 > 前者
规则的覆盖

当多个地方的rules配置出现相同规则时,

  • 如果该规则没有配置选项(只支持错误等级),则简单应用最高优先级。

  • 如果该规则支持额外配置选项,且高优先级的规则配置没提供选项,则继承低优先级的选项;否则,只应用高优先级的配置和选项。如:

    • "eqeqeq": ["error", "allow-null"] + "eqeqeq": "warn"(高优先级) = "eqeqeq": ["warn", "allow-null"]

    • "quotes": ["error", "single", "avoid-escape"] + "quotes": ["error", "single"](高优先级) = "quotes": ["error", "single"]

小小结

本文就是介绍一下 eslint 的配置说明,就以 .eslintrc 文件配置项的作用为代表,可以了解到为什么不同的 plugin 部分用来 extends 部分用来重写 rules. 这样大家在就了解了什么样的插件该如何使用的问题,它的执行就是这样执行的。