一文搞懂 ESLint、Prettier 和 Typescript 配置
每个 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-undef
✖ 1 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-undef
✖ 2 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-prettier
和 eslint-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。常用的语法解析器有以下三种:
- Esprima
- @babel/eslint-parser - 在 Babel 编译器的基础上进行一层封装,以适用于 ESLint
- @typescript-eslint/parser - 用来转换 TypeScript 语法的编译器
对于一般的 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。
至此,全文完结!
转载自:https://juejin.cn/post/7021920332260835342