强迫症福音:自定义 eslint 规则来规范组内代码

eslint 是什么

eslint 是用来规范 js 代码的工具,它提供了几个个重要功能

  • 代码质量检查。会推荐使用更高可靠性的代码,如用 === 代替 ==
  • 代码错误问题。可以粗略检查代码 bug,如使用未定义的变量
  • 代码格式化 。如给所有代码都加上; 来统一代码风格等。

为什么使用 eslint

  • 能在前期快速察觉到一些小bug,诸如O写成0
  • 统一团队代码风格,比如换行符与空格等,并且能快速修复不符合风格的问题。大大降低了团队内的代码阅读难度和维护成本,简直是强迫症的福音

eslint 常规用法

使用步骤

一般来说分为如下几步,由于不同的构建工具需要不同的方式,具体情况就不多赘述。

  1. 安装本地 vscode 的 eslint 插件并启用
  2. 在项目中安装 eslint 包
  3. eslint 初始化生成 .eslintrc.cjs 配置文件
  4. 将 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 的核心,其中包含了大量规则配置,内容过多,这里提供两个链接

举个例子,如果希望代码中只能使用 === 而不是 ==,那么首先要去规则中找到对应的规则名

接着将其添加到 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,有个简单的自动格式化可以配置,具体步骤如下

  1. vscode 安装 prettier 插件
  2. 在项目中新建 .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...

  1. 新建文件夹,这里取名 eslint-demo
  2. 安装脚手架包
bash 复制代码
# 脚手架需要 yeoman 启动
npm i yo -g

# 脚手架包
pnpm i generator-eslint
  1. 生成 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
  1. 生成 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 {
            
        };
    }
};
  1. 书写 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

  1. npm 注册一个账号
  2. 如果在本地使用了淘宝镜像源,发布的时候需要切换回来
arduino 复制代码
npm config set registry https://registry.npmjs.org/
  1. 这样即可进行登录并发布了

    npm login
    npm publish

可以在这里看到你发布的包,跟 github 差不多

💡注意事项:

  • 如果使用淘宝源进行 login,会报错并提示"不允许公开注册"
  • 切换 npm 源后,注意换回来,不然 pnpm install 可能会报错。
arduino 复制代码
npm config set registry https://registry.npmmirror.com
  • publish 后如果想第二次发布,别忘了修改版本号

使用 npm

  1. 回到你的项目中,安装这个包(这里以 eslint-plugin-imoo-tools 为例)
  2. 配置 .eslintrc.cjs
js 复制代码
module.exports = {
  ...
  plugins: ["imoo-tools"], // 值得一提的是,我们可以省略 eslint-plugin- 前缀
  rules: {
    "imoo-tools/absolutize": ["error", { level: 3 }], // 别忘了 plugins 前缀 imoo-tools/
  },
};
  1. eslint 变动的时候,建议重启一下编译器
  2. 写一个 demo 试一下

可以看到已经生效了,我们再试一下修复功能

-- 🎉 主线任务完结撒花,能看到这里的掘友们也太强了,希望大家都有所收获 🎉--

番外:eslint 底层机制

这里就直接套用掘金大佬的文章结论了

ESLint 的核心类是 Linter,它分为这样几步:

  1. preprocess,把非 js 文本处理成 js
  2. 确定 parser(默认是 espree)
  3. 调用 parser,把源码 parse 成 SourceCode(ast)
  4. 调用 rules,对 SourceCode 进行检查,返回 linting problems
  5. 扫描出注释中的 directives,对 problems 进行过滤
  6. postprocess,对 problems 做一次处理
  7. 基于字符串替换实现自动 fix

基于 AST 做检查,基于字符串做 fix,之前之后还有 pre 与 post 的process,支持注释来配置过滤掉一些 problems。

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰7 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy8 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom9 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom9 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试