彻底掌握 ESLint

1,298 阅读9分钟

ESLint 是什么

  • Lint是一种静态代码分析工具,用于检查代码的 语法是否正确、风格是否符合要求
  • ESLint是ECMAScript/JavaScript的Lint工具,用来确保团队代码风格的一致性和避免错误。JSLint和JSHint也是类似的工具。

JSLint,JSHint和ESLint的对比

JSLint

优点

  • 配置是已经定好的,开箱即用。

不足

  • 有限的配置选项,很多规则不能禁用
  • 规范严格,并且遵循的规范比较久远
  • 扩展性差
  • 无法根据错误定位到对应的规则
  • 文档不是很友好,看了官网就懂了

JSHint

优点

  • 有了很多参数可以配置
  • 支持配置文件,方便使用
  • 支持了一些常用类库
  • 支持了基本的ES6

不足

  • 不支持自定义规则
  • 无法根据错误定位到对应的规则

ESLint

优点

  • 默认规则里面包含了JSLint和JSHint的规则,易于迁移
  • 可配置为警告和错误两个等级,或者直接禁用掉
  • 支持插件扩展
  • 可以自定义规则
  • 可以根据错误定位到对应的规则
  • 支持ES6
  • 唯一一个支持JSX的工具

不足

  • 需要进行一些自定义配置(当然也有社区现成的规范)
  • 慢 (它比其他两个都要慢)

热度对比

ESLint的使用

本文环境

  • Node.js: 14.3.0 (官方要求>=6.14)
  • npm: 6.14.5 (官方要求>3)
  • "eslint": "^7.19.0"

安装

注意:由于Node的require功能行为,全局安装的ESLint实例只能使用全局安装的ESLint插件,本地安装的版本只能使用本地安装的插件。不支持混合本地和全局插件。

// 全局安装
// 但是,你使用的任何插件或可共享配置都必须安装在本地
npm i eslint -g

// 本地安装(推荐)
npm i eslint -D

生成配置文件

// 使用本地eslint
npx eslint --init

image.png

image.png

自定义配置

  1. 使用 JavaScript 注释把配置信息直接嵌入到一个代码源文件中
  2. 使用配置文件 .eslintrc.* 文件为根目录和子目录指定配置信息,或者直接在 package.json 文件里的 eslintConfig 字段指定配置,ESLint 会查找和自动读取它们
  3. 使用配置文件 .eslintignore,ESLint 去忽略特定的文件和目录

源文件(.js)中通过注释配置规则

/* eslint-disable */  // 取消eslint检测
/* global var1:false, var2:false */  // 指定全局变量
// ...

使用配置文件.eslintrc.*

  • 默认的配置文件名为 .eslintrc.*, 可以使用 eslint -c customESLint.js 指定配置文件
  • 支持三种格式的设置:javascript(.eslintrc.js)、yaml(.eslintrc.yml)、json(.eslintrc.json)
  • 存在多个配置文件的优先级
    • 子目录中的配置文件 > 根目录中的
    • .eslintrc.js > .eslintrc.yml > .eslintrc.json > package.json中的 eslintConfig
  • 使用 eslint cli自动生成配置文件.eslintrc.*
// 全局安装的eslint可以直接--init
eslint --init

// 本地安装的eslint,通过npx
npx eslint --init

// 选择规则
  To check syntax only // 检查语法
> To check syntax and find problems // 检查语法、发现问题
  To check syntax, find problems, and enforce code style // 检查语法、发现问题并强制执行代码样式
  // ...
  • 也可以直接手动新建.eslintrc.*配置文件(不推荐)

使用配置文件.eslintignore

/node_modules/
vue.js
vue.min.js
jquery.js
jquery.min.js
// ...

ESLint 配置项

配置文件中的设置都会覆盖默认设置

// .eslintrc.js
module.exports = {
    "root": true, // 停止在父级目录中寻找
    // 指定环境
    "env": {
        "browser": true, // 浏览器全局变量
        "es6": true, // 启用除模块外的所有ECMAScript 6功能(这会自动将ecmaVersion解析器选项设置为6)
        "node": true, // Node.js全局变量和Node.js范围
        // ...
    },
    // 指定全局变量
    // 1. 将每个全局变量名称设置为等于以true允许覆盖变量或false禁止覆盖
    // 2. 还支持在单独的js文件中通过注释配置全局变量 `/* global var1:false, var2:false */`
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly",
        "ENV": true
    },
    // 指定解析器 - 可以自定义解析器
    "parser": "esprima", // 默认情况下,ESLint使用Espree作为其解析器
    // 配置解析器选项
    "parserOptions": {
        "ecmaVersion": 2018,  // ECMAScript的版本,2018(与9相同)。默认为ECMAScript 5
        "sourceType": "module" // 设置为"script"(默认)或者"module"您的代码位于ECMAScript模块中
        "ecmaFeatures": {
            "globalReturn": true, // 允许return在全球范围内发表声明
            "impliedStrict": true, // 启用全局严格模式(如果ecmaVersion是5或更高)
            "jsx": true, // 启用JSX 
        }
    },
    // 扩展配置文件,主要用来使用第三方的eslint的完整配置
    // 一般输出的是整个eslint配置,也可以是别的形式
    "extends": [
      "eslint:recommended",  // eslint官方扩展
      "plugin:react/recommended", // 插件类型的扩展,也可以直接在 plugins 属性中进行设置
      "eslint-config-standard", // 第三方扩展,eslint-config-*
    ]
    // 配置插件,使用第三方的eslint的rules,命名格式eslint-plugin-*
    // 插件一般输出的是规则
    "plugins": [
    	"plugin1", // eslint-plugin-前缀可以从插件名称被省略。
        "eslint-plugin-plugin2"
    ],
    // 禁用一组文件的配置文件中的规则
    "overrides": [
      {
        "files": ["*-test.js","*.spec.js"],
        "rules": {
          "no-unused-expressions": "off"
        }
      }
    ],
    // 配置规则
    // 1. 可以使用配置注释或配置文件来修改项目使用的规则
    // 2. 每个规则必须是以下的值
    // "off"或者0 关闭规则
    // "warn"或者1 将规则打开为警告(不影响退出代码)
    // "error"或者2 将规则打开为错误(触发时退出代码为1)
    "rules": {
    	"plugin1/rule1": "error", // 重新定义上面插件中的规则
        "quotes": [
          "error",
          "single"
        ],
        'no-unused-vars': 0, // 定义了变量,未使用。可能有全局变量,别人使用的
        'global-require': 0, // 不能使用require
        'comma-dangle': 0, // 结尾逗号
        'no-console': 0, // 不能有console,警告
        'no-param-reassign': 0, // 不允许函数参数重新赋值
        'no-unused-expressions': 0, // 不允许  this.toast.finally && this.toast.finally();形式
        'func-names': 0, // 不允许使用 const getList = function() {...},是个警告
        'no-prototype-builtins': 0, // 不能使用prototype操作
        'no-restricted-syntax': 0, // 禁止使用for in
        'prefer-const': 0, // 如果没改变的话,必须是const
        'no-undef': 0, // 使用了未定义的变量
        'arrow-parens': 0, // 箭头函数用小括号括起来
        'object-shorthand': 0, //强制对象字面量缩写语法、
        'guard-for-in': 0, // for in循环要用if语句过滤
        'import/no-dynamic-require': 0 // 不能使用动态require
        // ...
    }
};

社区规范

代码风格并不是一个强制性的标准,每个团队都有自己的最佳实践。但是维护一套规则太费精力,所以出现了一些开源的代码规范

Airbnb 规范

Airbnb 是其中一个最流行的 JavaScript 代码规范,它差不多包含所有 JavaScript 的角度。校验比较严格。

Airbnb 规范的eslint实现

  • eslint-config-airbnb
    • 包含 ECMAScript 6 + 以及 React 的 ESLint 代码规范。
    • .eslintrc 加入 "extends": "airbnb"
  • eslint-config-airbnb-base
    • 包含 ECMAScript 6 + 代码规范。
    • .eslintrc 加入 "extends": ["airbnb-base"]

Standard 规范

标准的 JavaScript 代码规范,自带 linter & 代码自动修正

  • 无须配置。 史上最便捷的统一代码风格的方式,轻松拥有。
  • 自动的代码格式化。 只需运行 standard --fix 从此和脏乱差的代码说再见
  • 提前发现风格及程序问题

Standard 规范的eslint实现

Prettier

Prettier 是一个代码格式化插件。它并不关心你的语法是否正确,只关心你的代码格式,比如是否使用单引号,语句结尾是否使用分号等等。但是有部分规则和Eslint的冲突。

Prettier 的eslint实现

集成

@vue/cli集成ESLint

@vue/cli中预设了三种代码风格的校验,正好是我们上面主要对比的规范。不得不说vue官方对于不同口味的开发者还是很懂的。感兴趣的可以看下vue官方

ESLint + Airbnb

// .eslint.js
module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    'plugin:vue/essential',
    '@vue/airbnb',
  ],
  parserOptions: {
    parser: 'babel-eslint',
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },
};

ESLint + Standard

// .eslint.js
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}

ESLint + Prettier

// .eslint.js
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
  parserOptions: {
    parser: "babel-eslint"
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
  }
};

webpack 集成 ESLint

虽然已经在项目中配置了ESlint,但是还没有和webpack关联起来,所以要安装插件让webpack可以自动的读取ESLint规则

npm install eslint-webpack-plugin --save-dev

// webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
  // ...
  plugins: [new ESLintPlugin(options)],
  // ...
};

rollup 集成 ESLint

同上webpack的插件配置

npm i rollup-plugin-eslint -D

// rollup.config.js
import { rollup } from "rollup";
import { eslint } from "rollup-plugin-eslint";
 
export default {
  input: "main.js",
  plugins: [
    eslint({
      /* your options */
    })
  ]
};

vscode 集成 ESLint

项目已经配置了 Eslint, 但是 vscode 并没有在开发阶段给出对应的提示(应该出现红色波浪线)。 image.png

安装eslint 提供的 vscode 插件

image.png

手动开启eslint(默认不开启)

image.png

在团队中如何使用

怎么确保团队成员使用统一的配置?

  • 使用中心化配置,团队内部使用统一的 eslint-config-xxx
  • eslint-config-xxx 其实将本地的 .eslintrc.* 配置提取成公共配置,方便维护
  • 本地项目通过 extends 使用

image.png

推荐文章

开发eslint 规则

使用ESLint 官方提供的 Yeoman 的模板(generator-eslint)快速搭建项目结构,具体的规则主要是操作 AST

快速创建模板

// 安装模板
npm install -g yo generator-eslint

// 创建目录
mkdir eslint-plugin-checkhost
cd eslint-plugin-checkhost

// 创建模板
yo eslint:plugin // 生成开发插件的项目结构

image.png

yo eslint:rule // 生成具体开发的规则文件

image.png

最终的项目结构

image.png

使用 astexplorer

使用 astexplorer 在线分析 AST 结构

image.png

开发自定义规则

检测 .vue 文件 <template></template> 中是否包含指定的域名,并给出提示

项目地址

/**
 * @fileoverview 域名是否切换为jdl.cn
 * @author tianlu21
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
function defineTemplateBodyVisitor(
    context,
    templateBodyVisitor,
    scriptVisitor
  ) {
    if (context.parserServices.defineTemplateBodyVisitor == null) {
      const filename = context.getFilename()
      if (path.extname(filename) === '.vue') {
        context.report({
          loc: { line: 1, column: 0 },
          message:
            'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
        })
      }
      return {}
    }
    return context.parserServices.defineTemplateBodyVisitor(
      templateBodyVisitor,
      scriptVisitor
    )
  }

function searchText(childrens, context) {
    if(childrens) {
        for(let i = 0; i < childrens.length; i++) {
            if(childrens[i].children) {
                searchText(childrens[i].children, context)
            } else {
                let text = childrens[i].value
                if(/jd\.com/.test(text)) {
                    context.report({
                        node: childrens[i],
                        message: '域名是否切换到jdl.cn,请确认'
                    })
                }
            }
        }
    }
}

module.exports = {
    meta: {
        type: "suggestion",
        docs: {
            description: "域名是否切换为jdl.cn",
        },
        // or "code" or "whitespace"
        fixable: null,  
    },

    create: function(context) {
        return defineTemplateBodyVisitor(context, {
            "VElement[name='template']"(node) {
                searchText(node.children, context)
            }
        })
    }
};

单测

npx mocha .\tests\lib\rules\checkhost.js

image.png

使用

npm i -D @vtian/eslint-plugin-checkhost

image.png

image.png

参考资料