eslint 是什么
eslint 是用来规范 js 代码的工具,它提供了几个个重要功能
- 代码质量检查。会推荐使用更高可靠性的代码,如用 === 代替 ==
- 代码错误问题。可以粗略检查代码 bug,如使用未定义的变量
- 代码格式化 。如给所有代码都加上
;
来统一代码风格等。
为什么使用 eslint
- 能在前期快速察觉到一些小bug,诸如O写成0
- 统一团队代码风格,比如换行符与空格等,并且能快速修复不符合风格的问题。大大降低了团队内的代码阅读难度和维护成本,简直是强迫症的福音
eslint 常规用法
使用步骤
一般来说分为如下几步,由于不同的构建工具需要不同的方式,具体情况就不多赘述。
- 安装本地 vscode 的 eslint 插件并启用
- 在项目中安装 eslint 包
- eslint 初始化生成 .eslintrc.cjs 配置文件
- 将 eslint 加入构建工具中
完成这几步即可启用 eslint 的默认能力,当代码出现问题的时候就会爆红。
我在这个过程中查阅了一些掘金资料,大多数 react eslint 教程都是直接
npm i eslint
然后就开用了。其实我是有点疑惑的。理论上这种检测工具是需要 nodejs 来扫描代码实现的,但我们既没有将这个检测步骤加入脚手架,又没有手动 node 执行 eslint,那么它为什么能自己跑起来?
抱着这个想法去重新去创了个 react 项目来测试,发现 cra 与 vite 建出来的项目 是自带 eslint 的,也就是 eslint 已经集成进脚手架了,连 install eslint 都不需要 🤧
常用配置
在 .eslintrc.cjs 下,我们可以看到如下内容(以 vite-react 为例)
js
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
我们来对关键属性进行分析
#root
由于 .eslintrc.cjs 文件可以不止存在一个,eslint 会逐级进行 .eslintrc.cjs 文件的扫描,添加该属性可提示 eslint 不再向上扫描,从而节约性能。
#env
主要包括 browser、node、es系列等,开启后,eslint 会开启对应的特殊语法解析
#rules
eslint 的核心,其中包含了大量规则配置,内容过多,这里提供两个链接
- eslint 官网规则:eslint.org/docs/latest...
- eslint 中文网规则:eslint.nodejs.cn/docs/latest...
举个例子,如果希望代码中只能使用 === 而不是 ==,那么首先要去规则中找到对应的规则名
接着将其添加到 rules 中即可
java
module.exports = {
...
rules: {
...
eqeqeq: ['error', "always", { null: "ignore" }], // 数组第一项表示 eslint 检测到该规则后的状态(0关闭,1警告,2错误)。后几项均为规则的参数,可查阅对应规则
},
};
#plugins
虽然 eslint 规则五花八门,但总有不够用的时候。比如说需要 react 的一些定制化规则,eslint 默认规则里并没有,这时候就可以使用 plugins 了。
plugins 相当于拓展的规则集,仍需要你手动添加 rules,而不是添加一个 plugins 就结束了。
比方说,eslint-plugin-react 这个包里面有一些 react 规则,那么可以先使用 plugins 引入
js
plugins: [
'eslint-plugin-react'
],
此时我们的 rules 就能检查到这个包里的所有规则了,这时候通过 rules 来启用这个包其中某条规则,这样一来即可实现追加自定义规则了。
js
rules: {
'eslint-plugin-react/jsx-boolean-value': 2
}
#extends
不过相信你也能发现上述 plugins 不方便的地方,假如有很多规则,那岂不是要一个一个输入名字,一个一个进行配置,有没有打包好的整套规则集呢?
extends 相当于他人的整个 .eslintrc.cjs 文件,直接与你本地 .eslintrc.cjs 进行融合,这样你的项目中自然就得到了它其中所有的 rules。
js
extends: [
'eslint-plugin-react/recommended'
]
如果部分规则出现了冲突,也可以使用本地的 rules 来进行单规则覆盖
保存自动格式化(vscode)
如果使用的是vscode,有个简单的自动格式化可以配置,具体步骤如下
- vscode 安装 prettier 插件
- 在项目中新建 .vscode 文件夹,在其下新建 settings.json 并输入
js
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
这样即可不出现 prettier 和 eslint 冲突,eslint 负责校验代码,而格式化的工作交给 prettier
书写自己的 eslint 规则
现在回归正题,如何通过 eslint 来优雅的规范代码呢。
在大部分情况下,使用推荐的 eslint:recommended + 默认 rules 即可满足要求,但是如果组内有一些更定制化的场景,那就要自己来开发一个规则了。
问题
举个例子,为了代码的可读性和美观,路径中不允许出现三层以上的../
,若有这种情况,需要换成绝对路径而不是相对路径。(当然,最好加个参数可以控制有几层,默认3层)
js
import A from '../../A'; // ok
import B from '../../../B'; // x
import B from '@/components/card/B'; // ok
import C from '../../../../../../../../../../../../C'; // 灾难,可读性很差
实现
#方案1:利用脚手架
个人觉得先学会利用脚手架实现,再去探究原生实现会更加好理解
网上的脚手架不少,我们用 eslint 赞助的 generator-eslint 项目(github.com/eslint/gene...
- 新建文件夹,这里取名 eslint-demo
- 安装脚手架包
bash
# 脚手架需要 yeoman 启动
npm i yo -g
# 脚手架包
pnpm i generator-eslint
- 生成 plugins (只是个架子,还需要生成其中的 rules)
csharp
yo eslint:plugin
# 编写者
? What is your name? imoo
# 插件名,建议用 eslint-plugin- 开头
? What is the plugin ID? eslint-plugin-imoo-tools
# 插件描述
? Type a short description of this plugin: imoo 工具箱
? Does this plugin contain custom ESLint rules? Yes
? Does this plugin contain one or more processors? No
# 是否重写 package.json,注意重写后 generator-eslint 会没掉,需要重新 install 一个
? Overwrite package.json? overwrite
- 生成 rules
csharp
yo eslint:rule
# 编写者
? What is your name? imoo
? Where will this rule be published? ESLint Plugin
# 规则名
? What is the rule ID? absolutize
# 规则描述
? Type a short description of this rule: 绝对链接化
# 失败代码示例(可以先空着后面写)
? Type a short example of the code that will fail: import A from '../../../../A'
这么一来就可以生成我们的核心文件了
我们来分析一下这个文件
js
module.exports = {
meta: {
type: "", // problem 代码会导致错误,suggestion 建议修正,layout 主要关心分号空格等
docs: {
description: "", // 描述规则功能
recommended: true, // 后面可以使用extends: ['eslint:recommended']继承规则
url: "" // 文档的url
},
fixable: "code", // 标识规则是否可以修复,假如没有这属性,eslint不会帮你修复
schema: [], // 规则参数,比如规则使用时,rules: { myRule: ['error', param1, param2....]} ,error后面的就是参数
messages: "", // 错误信息
},
create: function(context) {
// 核心校验代码,使用 AST
return {
};
}
};
- 书写 rules
来整理一下思路,我们需要一个能这样调用的规则
js
module.exports = {
...
plugins: ['imoo-tools'],
rules: {
...
'imoo-tools/absolutize': ['error', { level: 3 }]
},
}
就可以实现这样的校验
js
import B from '../../B'; // ok
import B from '../../../B'; // x,提示应该修正为绝对路径
import B from '@/components/card/B'; // ok
只要利用一点 AST 知识(具体可看上一篇 AST 介绍),就可以写出如下代码
js
const path = require("path");
module.exports = {
meta: {
type: "problem",
docs: {
description: "绝对链接化",
recommended: true,
url: "",
},
fixable: "code",
schema: [
{
type: "object",
properties: {
level: {
type: "integer",
minimum: 1,
default: 3,
},
},
},
],
messages: {
notAbsolute: "路径应为绝对路径",
},
},
create(context) {
const level = context.options[0].level;
const absolutePathPattern = new RegExp("^(\.{2}/){" + level + ",}");
return {
ImportDeclaration(node) {
const currentPath = node.source.value;
if (absolutePathPattern.test(currentPath)) {
context.report({
node: node,
messageId: "notAbsolute",
// 获取当前的绝对路径并修复
fix(fixer) {
// 获得完整路径
const absolutePath = path.resolve(
path.dirname(context.getFilename()),
currentPath
);
// 我们需要从 src 开始,src 前面的可以去掉
const srcIndex = absolutePath.indexOf("/src");
const projectPath = absolutePath.substring(srcIndex + 1);
return fixer.replaceText(node.source, `'${projectPath}'`);
},
});
}
},
};
},
};
我们可以在 test 中写下测试代码(这里应该贴代码的,但是不知道掘金为啥贴过来不换行了...)
尽可能多的覆盖测试场景(我这里只是 demo),最后 run 一下 test,通过的话就万事大吉了。
#方案2:利用原生 js
如果用原生来实现,需要较多步骤,不过和脚手架思路类似,这里贴一份官网教程
eslint.nodejs.cn/docs/latest...
发布 eslint 规则并应用进组内项目中
发布 npm
- 上 npm 注册一个账号
- 如果在本地使用了淘宝镜像源,发布的时候需要切换回来
arduino
npm config set registry https://registry.npmjs.org/
-
这样即可进行登录并发布了
npm login
npm publish
可以在这里看到你发布的包,跟 github 差不多
💡注意事项:
- 如果使用淘宝源进行 login,会报错并提示"不允许公开注册"
- 切换 npm 源后,注意换回来,不然 pnpm install 可能会报错。
arduinonpm config set registry https://registry.npmmirror.com
- publish 后如果想第二次发布,别忘了修改版本号
使用 npm
- 回到你的项目中,安装这个包(这里以 eslint-plugin-imoo-tools 为例)
- 配置 .eslintrc.cjs
js
module.exports = {
...
plugins: ["imoo-tools"], // 值得一提的是,我们可以省略 eslint-plugin- 前缀
rules: {
"imoo-tools/absolutize": ["error", { level: 3 }], // 别忘了 plugins 前缀 imoo-tools/
},
};
- eslint 变动的时候,建议重启一下编译器
- 写一个 demo 试一下
可以看到已经生效了,我们再试一下修复功能
-- 🎉 主线任务完结撒花,能看到这里的掘友们也太强了,希望大家都有所收获 🎉--
番外:eslint 底层机制
这里就直接套用掘金大佬的文章结论了
ESLint 的核心类是 Linter,它分为这样几步:
- preprocess,把非 js 文本处理成 js
- 确定 parser(默认是 espree)
- 调用 parser,把源码 parse 成 SourceCode(ast)
- 调用 rules,对 SourceCode 进行检查,返回 linting problems
- 扫描出注释中的 directives,对 problems 进行过滤
- postprocess,对 problems 做一次处理
- 基于字符串替换实现自动 fix
基于 AST 做检查,基于字符串做 fix,之前之后还有 pre 与 post 的process,支持注释来配置过滤掉一些 problems。