一文搞懂 ESLint、Prettier 和 Typescript 配置

3,816 阅读14分钟

每个 coder 写代码的习惯多多少少不太一样,因此只要项目开发的人一多,代码风格的统一就是一个很困难的事情。幸好有很多工具来帮我们进行代码格式化,Eslint 就是其中一种。

本文将详细介绍 ESLint 工具的使用,主要包含以下内容:

  • 如何配置 ESLint
  • 如何将 ESLint 和 Prettier 配合使用
  • 如何格式化 Typescript 代码

初始化项目

// 创建文件夹
mkdir eslint-learn && cd eslint-learn

// 初始化npm项目
yarn init -y

// 安装 eslint
yarn add eslint --dev 

自动生成配置文件

ESLint 运行时需要一个配置文件。这个配置文件可以自动生成,也可以手动配置。我们先看一个自动生成的案例,执行以下命令:

npx eslint --init

之后会让你进行各种选择,按下图选择对应的选项:

最后会问你是否安装一系列的依赖,你可以选择“Yes”,则将以npm来安装上述依赖;你也可以通过yarn安装上述依赖。主要是以下几个依赖:

  • @typescript-eslint/eslint-plugin
  • eslint-config-standard
  • eslint
  • eslint-plugin-import
  • eslint-plugin-node
  • slint-plugin-promise
  • @typescript-eslint/parser@latest
@typescript-eslint/eslint-plugin@latest eslint-config-standard@latest eslint@^7.12.1 eslint-plugin-import@^2.22.1 eslint-plugin-node@^11.1.0 eslint-plugin-promise@^4.2.1 || ^5.0.0 @typescript-eslint/parser@latest

最后,就会在项目根目录下生成一个配置文件:

{
    "env": {
        "commonjs": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "standard"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 13
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
    }
}

如果你完全可以看懂上面配置文件的意思,那就说明你已经明白 ESLint 的配置,没必要继续往下看;如果你还看不懂上面的配置信息,那看完本文肯定会让你明白上述的配置信息。

Step By Step

准备工作

  • 在根目录下创建一个 src 文件夹,在文件夹中创建一个 index.js 文件,这是我们写代码的地方。

  • package.json 文件中创建一个脚本"lint":"eslint src/index.js",对 index.js 文件执行 eslint 命令。
"scripts": {
  "lint": "eslint src/index.js"
}
  • 清空 .eslintrc 文件,我们自己一步步添加配置信息。

.eslintrc 可以有后缀,比如 .js 或者 .json。我本人不喜欢加后缀,因为如果是 js 文件,那就需要多个导出符,如果是 json 文件,就不能加注释。

空配置执行

src/index.js 文件中添加如下代码:

function foo(a,b) {
  return a+b
}
var a1 = "a1"
var b1 = 'b1'
var res = foo(a1,b1)
console.log(res)

执行 npm run lint,控制台没有任何输出,说明一切正常。

这里先简单提一下 ESLint 的工作原理:ESLint 会先通过词法分析器把你写的代码进行拆分(AST),然后再按某种规则组合起来,接着把新组合起来的代码和你写的代码进行比对,如果有差异,就会在控制台提示报错信息。

那么这个规则就很重要,而我们刚才的例子没有配置规则,所以也就没有报错。接下来我们配置一些规则。

Rules 配置

.eslintrc 文件中配置两条规则:

  • semi:是否要加分号
  • quotes:引号是单引号还是双引号

规则配置的形式是key-value,key 是规则,value 的第一个值表示规则的限制,取值范围为:

  • "off" or 0 - 关闭规则
  • "warn" or 1 - 启用规则,不符合会警告
  • "error" or 2 - 启用规则,不符合直接报错
// .eslintrc
{
    "rules": {
        /**
         * key 是规则
         * value 的第一个值表示规则的限制,取值范围为
         *  "off" or 0 - 关闭规则
            "warn" or 1 - 启用规则,不符合会警告
            "error" or 2 - 启用规则,不符合直接报错
         */
        "semi": ["error", "always"],
        "quotes": ["error", "double"]
    }
}

执行 npm run lint,控制台输出如下报错信息:

  2:13  error  Missing semicolon             semi
  4:14  error  Missing semicolon             semi
  5:10  error  Strings must use doublequote  quotes
  5:14  error  Missing semicolon             semi
  6:21  error  Missing semicolon             semi
  7:17  error  Missing semicolon             semi

✖ 6 problems (6 errors, 0 warnings)
  6 errors and 0 warnings potentially fixable with the `--fix` option.

根据上面的信息,我们可以看到,在第几行第几列不满足什么规则。

Extends 配置

通过规则,虽然可以指出代码风格问题,但是那么多的规则如果都要我们手写,那也太没人性了。因此就会有一些推荐的规则可以直接使用。官方自带的是 eslint:recommended,我们可以通过 extends 属性来扩展我们的配置。至于它对应的具体规则,可以参考:eslint.org/docs/rules/

除了 ESLint 自带的规则,还有很多开源的配置规则,只要在 GitHub 上搜索 eslint-config- 开头的基本上都是可扩展的规则,比如 eslint-config-standard、eslint-config-prettier 等。如果需要配置多个规则库,则可以将 extends 写成数组的形式。

接着在 .eslintrc 文件中增加扩展信息:

// .eslintrc
{
    "extends": "eslint:recommended",
    "rules": {
        /**
         * key 是规则
         * value 的第一个值表示规则的限制,取值范围为
         *  "off" or 0 - 关闭规则
            "warn" or 1 - 启用规则,不符合会警告
            "error" or 2 - 启用规则,不符合直接报错
         */
        "semi": ["error", "always"],
        "quotes": ["error", "double"]
    }
}

执行 npm run lint,控制台输出如下报错信息:

  2:13  error  Missing semicolon             semi
  4:14  error  Missing semicolon             semi
  5:10  error  Strings must use doublequote  quotes
  5:14  error  Missing semicolon             semi
  6:21  error  Missing semicolon             semi
  7:1   error  'console' is not defined      no-undef
  7:17  error  Missing semicolon             semi

✖ 7 problems (7 errors, 0 warnings)
  6 errors and 0 warnings potentially fixable with the `--fix` option.

Env 环境配置

相比之前,多了一条 no-undef 的报错信息,意思是 console 未定义。这是因为,我们未指定代码的运行环境,所以 ESLint 并不知道 console 是否是环境自带的。这个时候我们就需要配置运行环境。配置对应的 env 属性如下,表示代码可能会运行在浏览器和 node 环境中。另外,为了控制台干净一点,我们先不启动分号和引号两条规则。

{
    "extends": "eslint:recommended",
    "env": {
        "browser": true,
        "node": true
    },
    "rules": {
    }
}

这个时候,当我们再次执行 ESLint 检测时,控制台没有任何报错信息,也就是说 console 可以被使用。

ES 6 语法支持

我们现在回过头去看看被检测的代码,其实都是 ES5 的代码。现在我们把它改成 ES6 的代码。

function foo(a,b) {
  return a+b
}
let a1 = "a1"
let b1 = 'b1'
let res = foo(a1,b1)
console.log(res)

let p1 = new Set();
console.log(p1)

执行代码检测之后,控制台输出如下报错信息:Parsing error。

  4:5  error  Parsing error: Unexpected token a1

✖ 1 problem (1 error, 0 warnings)

这是因为 ESLint 默认只编译 ES5 的代码,无法编译 ES6 的代码。

ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax.

要编译 ES6 代码,就需要增加 parserOptions 属性的配置。

修改配置文件如下:

{

    "extends": "eslint:recommended",
    "env": {
        "browser": true,
        "node": true
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
    "rules": {
    }
}

重新执行代码检测之后,控制台输出如下报错信息:Set 未定义

  9:14  error  'Set' is not defined  no-undef1 problem (1 error, 0 warnings)

Set 虽然是 ES6 新增的,但是是属于新增的内置对象。对于这种内置对象,ESLint 也需要进行特殊配置,在环境属性中增加:"es6": true

{
    "extends": "eslint:recommended", // 扩展项
    "env": {
        "browser": true,
        "node": true"es6": true // 支持全局的 es6 语法 Set
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
    "rules": {  // 具体规则
    }
}

再次执行 npm run lint,控制台无报错信息。

Globals 配置

Globals 是一个不太常用的配置项,一般在框架开发中需要用到。因为框架的设计中往往需要在 window 上挂载一些变量,然后在插件或者项目中可能会用到这些变量。我们现在假设已经在 window 上挂载了 var1 和 var2,因此我们可以项目中直接使用这两个变量,如下所示:

function foo(a,b) {
  return a+b
}
let a1 = "a1"
let b1 = 'b1'
let res = foo(a1,b1)
console.log(res)

let p1 = new Set();
console.log(p1)

console.log(var1);
console.log(var2);

如果我们执行检测,就会出现如下报错信息:

  12:13  error  'var1' is not defined  no-undef
  13:13  error  'var2' is not defined  no-undef2 problems (2 errors, 0 warnings)

如果我们不想出现报错信息,就可以配置 globals 属性如下:

{

    "extends": "eslint:recommended", // 扩展项
    "env": {
        "browser": true,
        "node": true"es6": true // 支持全局的 es6 语法 Set
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
    "globals": {
        "var1": "writable",
        "var2": "readonly"
    },
    "rules": {  // 具体规则
    }
}

这样就相当于在全局声明了 var1 和 var2 两个变量,其中 writable 表示该变量可以被重新赋值;而 readonly 则表示变量是只读的。再次执行 npm run lint,控制台无报错信息。

至此,我们已经介绍了 ESLint 中最基本的几个配置项,相比于文章开头自动生成的配置文件,还剩下两个配置项:parser 和 plugins。

Go On

Plugins 配置

ESLint 支持第三方的插件。当然,在使用插件之前需要先安装插件。要配置一个插件也很简单,有点类似 extends 的配置。一般插件都是以 eslint-plugin-开头。

如下声明了两个插件,分别是 plugin1 和 plugin2,其中 eslint-plugin- 这个前缀可以省略。这样声明的意义其实就是让 ESLint 在运行时自动加载对应的插件: require('eslint-plugin-pluginName')

{
    "plugins": [
        "plugin1",
        "eslint-plugin-plugin2"
    ]
}

接下来,我们以 eslint + prettier 的配合方式为例来说明插件如何使用。

首先,简单介绍一下 prettier: prettier 是一款有确定(opinionated) 代码风格的格式化工具,本身它就可以单独使用来格式化代码。由于它拥有确定的规则,因此也可以和 eslint 配合使用,作为 eslint 的 rules 来检测代码。

另外,它有对应的 vscode 插件,安装之后就可以编边写代码边格式化代码风格,十分方便。

这里主要用到的插件就是:eslint-plugin-prettier。除此之外,我们还需要知道 prettier 的规则,因此也需要安装 prettier。

yarn add eslint-plugin-prettier prettier -D

安装完成之后,我们需要配置对应的插件和启用对应的配置项:

{
    // "extends": "eslint:recommended", // 扩展项
    "env": {
        "browser": true,
        "node": true"es6": true // 支持全局的 es6 语法 Set
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
  	"plugins": [
        "prettier"
    ],
    "globals": {
        "var1": "writable",
        "var2": "readonly"
    },
    "rules": {  // 具体规则
      	"prettier/prettier": "error" // 开启 prettier 规则
    }
}

这个时候我们执行 npm run lint,控制台报错信息:

   1:16  error  Insert `·`                   prettier/prettier
   2:11  error  Replace `+b` with `·+·b;`    prettier/prettier
   4:14  error  Insert `;`                   prettier/prettier
   5:10  error  Replace `'b1'` with `"b1";`  prettier/prettier
   6:18  error  Replace `b1)` with `·b1);`   prettier/prettier
   7:17  error  Insert `;`                   prettier/prettier
  10:16  error  Insert `;`                   prettier/prettier
  13:19  error  Insert `⏎`                   prettier/prettier

✖ 8 problems (8 errors, 0 warnings)
  8 errors and 0 warnings potentially fixable with the `--fix` option.

原本没有报错信息的控制台,又因为 prettier 规则的启用,多了很多报错信息。

如果你以为这样就完成了 prettier 的配置,那你就想得太简单了。我们来看下 prettier 和 eslint 的区别

简而言之,prettier 只完成了样式的格式化,但是不能做到代码质量的管控,而这就需要 eslint 来完成。

因此,在上面的配置中,还需要加上 eslint 的规则,而这就导致了一个新的问题:eslint 和 prettier 存在规则冲突和重复。这样就会导致样式在格式化的时候出现矛盾。因此就出现了 eslint-config-prettier,它的作用就是把 eslint 和 prettier 冲突的规则全部关闭。源码文件:链接

安装 eslint-config-prettier ,并修改配置文件:

{
    "extends": ["eslint:recommended","prettier"] // 扩展项
    "env": {
        "browser": true,
        "node": true"es6": true // 支持全局的 es6 语法 Set
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
  	"plugins": [
        "prettier"
    ],
    "globals": {
        "var1": "writable",
        "var2": "readonly"
    },
    "rules": {  // 具体规则
      	"prettier/prettier": "error" // 开启 prettier 规则
    }
}

既然 eslint-plugin-prettiereslint-config-prettier 往往需要成对出现,因此还有一种更简洁的写法:

{
    "extends": ["eslint:recommended","plugin:prettier/recommended"] // 扩展项
    "env": {
        "browser": true,
        "node": true"es6": true // 支持全局的 es6 语法 Set
    },
  	"parserOptions": {
        "ecmaVersion": "latest"  // 支持 es6 语法
    }, 
    "globals": {
        "var1": "writable",
        "var2": "readonly"
    },
    "rules": {  // 具体规则
    }
}

"plugin:prettier/recommended" 这个配置项,其实被扩展成:

{   
  "extends": ["prettier"],   
  "plugins": ["prettier"],   
  "rules": {     
    "prettier/prettier": "error",     
    "arrow-body-style": "off",     
    "prefer-arrow-callback": "off"   
  } 
}

至于为什么多了两个规则,可以参见:arrow-body-style and prefer-arrow-callback issue

至此,我们就完成了 eslint + prettier 的配置,也正好说明了 plugin 的用法。

Parser 配置

正如本文一开头提到的那样,ESLint 会先通过词法分析器把你写的代码进行拆分(AST)。而这个词法分析器就是 parser。不同的 parser 就会产生不同的转换结果。ESLint 默认的 parser 是 Espree 。用户可以通过 parser 属性指定自己想要的 parser。常用的语法解析器有以下三种:

对于一般的 javascript 项目,我们可以保持默认,或者使用 @babel/eslint-parser ;而对于 typescript 项目,一般就会使用 @typescript-eslint/parser。

此外,对于 TS 项目,会有对应的 TS 规则,一般我们选择 @typescript-eslint/eslint-plugin 这个插件。

首先,我们安装对应的依赖:

yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript -D

修改对应的配置文件:

{
    "extends": ["plugin:@typescript-eslint/recommended","plugin:prettier/recommended"],
    "env": {
        "browser": true,
        "node": true,
        "es6": true // 支持全局的 es6 语法Set
    },
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",  // 支持 es6 语法
        "sourceType": "module"
    },
    "rules": {
    }
}

如果 eslint-config-prettier 的 版本>= 8.0.0,则上面的 extends 配置项只需要设置成 "plugin:prettier/recommended" 即可。具体参见 CHANGELOG

同时,将 src/index.js 文件改为 src/index.ts:

function foo(a:string,b:string) {
  return a+b
}
let a1 = "a1"
let b1 = 'b1'
let res = foo(a1,b1)
console.log(res)

最后,执行 "lint": "eslint src/index.ts" 检测,控制台报错信息如下:

  1:16  error  Replace `string,b:` with `·string,·b:·`         prettier/prettier
  2:11  error  Replace `+b` with `·+·b;`                       prettier/prettier
  4:5   error  'a1' is never reassigned. Use 'const' instead   prefer-const
  4:14  error  Insert `;`                                      prettier/prettier
  5:5   error  'b1' is never reassigned. Use 'const' instead   prefer-const
  5:10  error  Replace `'b1'` with `"b1";`                     prettier/prettier
  6:5   error  'res' is never reassigned. Use 'const' instead  prefer-const
  6:18  error  Replace `b1)` with `·b1);`                      prettier/prettier
  7:17  error  Insert `;`                                      prettier/prettier

说明检测 Typescript 文件成功!

One More

到目前为止,ESLint 配置文件的内容我们已经讲得差不多了。执行检测命令,我们也能够在控制台得到对应的信息,但是如果每一个错误都需要我们手动更改,那也有很大的工作量。幸运地是,ESLint 有自动修复的功能,只需要在检测命令最后加上 --fix 就可以了。

我们在 package.json 加上一条新的脚本:"lint:fix": "eslint src/index.ts --fix",然后再执行 npm run lint:fix 重新检测并自动修复。这时,控制台没有任何报错信息。再看源文件,发现已经自动格式化:空格,分号等。

function foo(a: string, b: string) {
  return a + b;
}
const a1 = "a1";
const b1 = "b1";
const res = foo(a1, b1);
console.log(res);

最后

上面执行 ESLint 格式化代码需要手动执行 npm 命令,这就导致总会有些人有意或者无意地把未经 ESLint 检测过的代码提交到仓库。于是就出现了 husky + lint-stage 的方式来解决上述问题。husky 可以提供 git 命令对应的钩子函数,我们可以在代码提交前进行 ESLint 检测。但是如果每次都全量检测我们的代码就太费时间了,所以我们可以通过 lint-stage 这个库来只检测本次提交涉及修改的文件。

首先安装对应的依赖:

yarn add husky lint-staged -D

配置 package.json 文件:

"husky":{
  "hooks":{
  "pre-commit": "lint-staged"
  }
},
"lint-staged":{
  "src/*/**.{ts,js}":"eslint --fix"
}

以上配置只针对 husky v4 版本有效,在 v7 版本需要采用新的配置方法。

首先,配置 package.json ,使得应用安装依赖之后,自动执行 husky install

"scripts": {
  "prepare": "husky install"
}

此时,会在项目下会生成一个 .husky 文件夹,用户可以在其中配置相关 git hooks。

接着,添加 pre-commit 文件并写入配置,对应命令:

npx husky add .husky/pre-commit "npx lint-staged --allow-empty $1"

于是生成 .husky/pre-commit 文件:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged --allow-empty $1

这样就与 lint-staged 关联起来,在提交代码的时候就会按 lint-staged 配置去检测文件。详情请参考:husky

至此,全文完结!