时间轴分享展示

技术学习笔记

Element Plus 组件库相关技术揭秘:4. ESLint 技术原理与实战及代码规范自动化详解

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

在前端工程化的标准中有一项就是自动化,自动化当中就包括了代码规范自动化。实现代码规范自动化可以解放团队生产力,提升团队生产效率。既然代码规范自动化可以带来如此的好处,我们自然要好好了解它并且掌握它。

代码是写给人看的,如果在一个文件里,有的以两个空格做缩进,有的以四个空格做缩进,那么它的阅读体验就会变得很差。

为了规范代码,我们可以使用 ESLint、TSLint 以及 CSSLint 等 Lint 工具,通过配置语法检测规则来对代码风格进行检测。而这些工具不仅能规范我们的代码风格,还能对代码质量起着一定的监督作用。

现在几乎所有的团队使用的代码检查工具都是以 ESLint 为代表的 Linter 和 Prettier 进行配套使用。Linter 和 Prettier 它们的定位不同,解决的问题也不同,但它们却是相辅相成。

通过这些工具进行代码自动化检查,让开发者可以完全聚焦业务开发,不必在代码整理上花费过多的心思。相对其他需要编译的语言可以在编译阶段进行代码检查不同, JavaScript 不具备先天编译流程,同时是一种动态、宽松类型的语言,所以容易在运行时暴露错误。而通过 ESLint 可以让开发者提前将更多的错误或不合理的写法暴露在开发阶段。

ESLint 的主要作用

我们在安装 ESLint 的过程中会存在以下一个步骤让我们进行选择:

? How would you like to use ESLint? ...
  To check syntax only
> To check syntax and find problems
  To check syntax, find problems, and enforce code style
复制代码

中文翻译过来就是:

你希望怎么使用 ESLint?

  1. 只检测语法
  2. 检测语法和发现问题
  3. 检测语法,发现问题,强制代码风格

从上面的选项中我们就可以知道 ESLint 的主要作用在最后一项已经进行了总结:检查语法发现问题强制代码风格

ESLint 的工作原理

ESLint 可谓是现代前端开发过程中必备的工具了,所以我们有必要好好学习并掌握它,而且值得深挖和理解其工作原理 。

那么 ESLint 是如何读懂我们的代码的呢?答案是靠 AST。ESLint 主要通过 AST 进行工作的,把源码通过编译器编译成 AST,然后遍历 AST 节点进行和我们配置的 ESLint 规则进行匹配,匹配成功了就通过规则里的函数进行对 AST 节点的操作,找到问题和要修复的地方,遍历完所有 AST 节点之后遍进行相应提示或修复。

读取配置

在启动了 ESLint 检测命令之后,ESLint 将自动在待校验的文件目录里寻找 ESLint 配置文件,接着是父级目录,一直到系统的根目录,除非在 ESLint 配置文件中指定 root: true 停止向上寻找。这也是 ESLint 配置文件中 root: true 配置项的作用。

在读取配置的过程中有一个点是需要特别注意的,就是配置文件中的 extends 选项的理解,extends 选项就是继承其他配置文件,通过 extends 选项我们可以使用自身的配置(eslint: 开头)或者插件中的配置(plugin: 开头)或者是第三方模块中的配置。最终 ESLint 会通过递归处理 extends,合并成一个配置对象。

编译源码获得 AST

在加载完配置项后,ESLint 就进行编译器确定,即是从配置中确定要使用哪一种编译器对源码进行编译了。当用户没有指定 parser 时,默认使用 espree,若有指定 parser 则使用指定的 parser。通过编译器编译源码之后就获得了 AST,然后就可以通过 AST 去操作我们的源码了。

Rules 的原理

在前面读取配置的时候,就已经把所有的 plugin 和它的 rules 加载进来了。Rules 的原理就是针对源码的 AST 节点添加处理函数,也就是访问者函数。在该函数内进行该 AST 节点的分析操作,看看该 AST 节点有没有存在我们不希望存在的情况,有那么就进行输出警报信息和记录怎么修复的信息。

最后输出警报和修复

在通过编译器获得 AST 之后,就可以遍历 AST,然后查看规则中有没有配置针对该 AST 节点进行检查的 visitor 函数,如果存在就会调用该 visitor 函数进行处理,获得警报信息或者要修复的信息,最后输出警报信息和进行相关源码修复。

更详细的原理可以查看我掘金的上一篇文章:《Element Plus 组件库相关技术揭秘:3.ESLint 核心原理剖析

理解 ESLint 的工作原理之后,我们将从实践中去进一步理解 ESLint。

创建可共享的 ESLint 插件

创建 ESLint 插件开发环境

为了方便测试,我们通过 pnpm 构建一个 Monorepo 项目,在 eslint-guide 目录下进行测试 ESLint 相关配置和测试,此外我们创建一个 eslint-plugin-colint 目录用于创建 ESLint 插件。

├── README.md
├── package.json
├── packages
│   ├── eslint-guide
│   │   ├── .eslintrc.js
│   │   └── package.json
│   └── eslint-plugin-colint
│       └── package.json
└── pnpm-workspace.yaml
复制代码

我们可以选择按照规范自己手动创建一个插件,也可以使用 ESLint 提供的构建工具。我们这里就只讲解通过 ESLint 构建工具如何创建 ESLint 插件。

我们进入 eslint-plugin-colint 目录运行以下命令:

npm install yo generator-eslint -g
复制代码

生成一个 ESLint 的插件模板

运行命令:yo eslint:plugin,出现以下命令界面,然后按提示填写:

? What is your name? Cobyte
? What is the plugin ID? colint
? Type a short description of this plugin: test
? Does this plugin contain custom ESLint rules? Yes
? Does this plugin contain one or more processors? (y/N) n
复制代码

最后生成以下目录结构:

eslint-plugin.png

这里要特别说明一下的是 ESLint 的插件 npm 包的名称必须以 eslint-plugin- 开头,而我们上面通过命令创建的 ESLint 插件则自动命名为了 eslint-plugin-colint,我们查看一下 eslint-plugin-colint 目录下的 package.json 的 name 属性值就知道了。

{
  "name": "eslint-plugin-colint",
  "version": "0.0.0",
  "description": "cobyte lint",
  "author": "Cobyte",
  // 省略 ...
}
复制代码

生成规则,运行命令:yo eslint:rule,出现以下命令界面,然后按提示填写:

? What is your name? Cobyte
? Where will this rule be published? (Use arrow keys)

> ESLint Plugin 选择插件
> ESLint Core
? What is the rule ID? no-var
? Type a short description of this rule: test
? Type a short example of the code that will fail:
复制代码

接着会在 lib 目录下生成一个 rules 目录及一个 no-var.js 的文件。

eslint-rules.png

no-var.js 的文件内容,也是一个 ESLint 插件的最基本结构:

/**
 * @fileoverview 测试
 * @author Cobyte
 */
'use strict'
module.exports = {
  meta: {
    type: null, // `problem`, `suggestion`, or `layout`
    docs: {
      description: '测试',
      recommended: false,
      url: null // URL to the documentation page for this rule
    },
    fixable: null, // Or `code` or `whitespace`
    schema: [] // Add a schema if the rule has options
  },

  create(context) {
    return {
      // visitor functions for different types of nodes
    }
  }
}
复制代码

调试及编写 ESLint 插件

接着我们先把插件运行起来。我们回到根目录把我们的插件安装起来:

pnpm install eslint-plugin-colint@workspace -w
复制代码

再进到 eslint-guide 目录下把我们上面创建的 ESLint 插件在 .eslintrc.js 中配置起来:

{
    'plugins': ['colint'],
    'rules': {
        'colint/no-var': ['error']
    }
}
复制代码

注意:ESLint 插件在配置项 plugins 中只需要写插件名称即可。eslint-plugin- 此部分不用填写。

新建一个 test.js 文件写上以下内容:

var b = '稀土掘金'
复制代码

我们希望当运行 eslint 检测的时候,会警报不能使用 var。我们通过上文知道 ESLint 是通过 AST 工作的,具体就是遍历 AST 树,找到对应的 AST 的节点,然后进行相应的操作。我们可以通过 AST 可视化工具查看 var b = '稀土掘金' 的 AST 树。即如下图:

var-ast.png

我们可以看到 var 对应的 AST 节点类型是 VariableDeclaration,所以我们只需要在规则函数中创建一个 VariableDeclaration 的访问者函数进行监听对应的 AST 节点然后进行相应的操作即可。

module.exports = {
  create(context) {
    return {
      VariableDeclaration(node) {
        console.log(node)
      }
    }
  }
}
复制代码

通过 npx eslint test.js 把插件运行起来,查看 console.log 打印的数据,验证我们前面的操作是否正确。

rules-test1.png

我们可以查看已经正确打印我们的 AST 节点了。

有因为变量声明有 constletvar 所以又进一步判断是不是 var,如果是 var 那么我们就可以通过上下文对象 context.report() 方法进行发布警告或错误,相关代码如下:

module.exports = {
  create(context) {
    return {
      VariableDeclaration(node) {
        if (node.kind === 'var') {
          context.report({
            node,
            message: '不能用var'
          })
        }
      }
    }
  }
}
复制代码

接着再运行 npx eslint test.js

rules-test2.png

成功进行警告,说明我们的插件代码编写是成功的。

如何修复代码

var 修复为 let , 修复代码的时候规则中 meta.fixable 属性值必须为:code,我们要修复代码则可以在 context.report() 方法的参数对象中传递一个 fix() 函数,这个 fix() 函数有一个回调参数对象 fixer 就提供了各种修改方法。

修复代码的核心原理就是通过当前的 AST 节点信息找到对应的 tokens 中的具体 token,因为只有 tokens 中的 token 才详细记录了字符标记的具体位置信息内容。

接下来,我们先通过上下对象 context.getSourceCode() 方法获取到的对象就是 ESLint 中是通过一个 SourceCode 类对通过编译器生成的原始 AST 进行二次封装的实例对象。

这个实例对象提供了非常多方法:

  • getText(node) - 返回给定节点的源码。省略 node,返回整个源码。
  • getAllComments() - 返回一个包含源中所有注释的数组。
  • getCommentsBefore(nodeOrToken) - 返回一个在给定的节点或 token 之前的注释的数组。
  • getCommentsAfter(nodeOrToken) - 返回一个在给定的节点或 token 之后的注释的数组。
  • getCommentsInside(node) - 返回一个在给定的节点内的注释的数组。
  • getJSDocComment(node) - 返回给定节点的 JSDoc 注释,如果没有则返回 null。
  • isSpaceBetweenTokens(first, second) - 如果两个记号之间有空白,返回 true
  • getFirstToken(node, skipOptions) - 返回代表给定节点的第一个 token

这里只罗列部分方法,更多可以查看  ESLint  的官网。

其中这里对我们目前的例子有用的就是 getFirstToken() 方法,通过此方法我们可以获得当前 AST 的节点 token 信息,然后再通过前面说到的 context.report() 方法的参数对象中传递的 fix() 函数提供的回调参数对象 fixer.replaceText() 方法就可以进行修复代码了。具体代码如下:

module.exports = {
  meta: {
    type: 'problem', // `problem`, `suggestion`, or `layout`
    docs: {
      description: '自定义插件',
      recommended: false,
      url: null // URL to the documentation page for this rule
    },
    fixable: 'code', // Or `code` or `whitespace`
    schema: [], // Add a schema if the rule has options
    messages: {}
  },

  create(context) {
    const sourceCode = context.getSourceCode()
    return {
      VariableDeclaration(node) {
        if (node.kind === 'var') {
          context.report({
            node,
            message: '不能用var',
            fix(fixer) {
              const varToken = sourceCode.getFirstToken(node)
              return fixer.replaceText(varToken, 'let')
            }
          })
        }
      }
    }
  }
}
复制代码

再运行 npx eslint test.js --fix 要修复代码必须加 --fix

var-to-let.png

这个时候我们就看到 test.js 文件中的 var 变成了 let 了。

关于更详细的原理可以查看我上一篇文章:ESLint 核心原理剖析

插件规则配置集成共享

我们一个插件里面可能会有很多 rules。从下图 eslint-plugin-vue 插件的目录结构可以看到 eslint-plugin-vue 插件有非常多的规则。

eslint-plugin-vue.png

如果都像我们上面自定义的规则那样在 .eslintrc.js 的 plugins 中配置引入,再在 rules 中进行具体每一条的配置,那么这样则让使用者太麻烦了,所以我们可以把相关的规则集成一个组合推荐给用户,用户自己使用推荐的规则组合即可。

plugin-cofig.png

我们只需要在上图中的目录中的 index.js 进行以下配置即可:

module.exports = {
  rules: requireIndex(__dirname + '/rules'),
  configs: {
    recommended: {
      plugins: ['colint'],
      rules: {
        'colint/no-var': ['error']
      }
    }
  }
}
复制代码

这样表明我们在 configs 选项中设置了一组 recommended 的规则,后续用户可以直接使用该集成的规则配置,而不用自己手动在 .eslintrc.js 文件中的 rules 中进行一个个规则配置了。

接下来我们只需要在 .eslintrc.js 文件中进行以下配置即可:

module.exports = {
  extends: ['plugin:colint/recommended']
}
复制代码

再运行 npx eslint test.js --fix 要修复代码必须加 --fix

var-to-let.png

这个时候我们看到 test.js 文件中的 var 还是变成了 let,说明我们的配置是正确的。

至此我们一个 ESLint 插件是如何创建的及插件中的规则是如何配置生效的整个流程我们都进行了一遍梳理和实践。

创建可共享的 ESLint 配置

我们现在的前端项目基本都需要配置 ESLint 的,相同的项目的 ESLint 的配置都是差不多的。如果每一个项目都需要进行从头开始配置的话,那么就非常浪费不必要的时间了,所以我们希望可以针对一些相同的项目只进行一次配置然后在其他项目中直接引用即可,这就是 ESLint 共享配置。并且还可以把 ESLint 共享配置上传到 npm 服务器供其他人下载下来在其项目中安装使用。

接下来我们在 packages 目录中再创建一个 eslint-config-cobytelint 目录用于创建 ESLint 共享配置。 进入 eslint-config-cobytelint 目录初始化项目,运行 pnpm init

{
  "name": "eslint-config-cobytelint",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码

这里值得注意的是项目的名称必须是以 eslint-config-xxx 开头或者 @xxx/eslint-config 此种复合包,其中 xxx 是具体的名字,这是 ESLint 共享配置 npm 包名称的约定。

我们接着在 eslint-config-cobytelint 目录下的 index.js 文件中进行以下配置:

module.exports = {
  extends: ['plugin:colint/recommended']
}
复制代码

我们只配置了我们上面自定义的插件中的规则。

我们回到根目录把我们的 ESLint 配置包安装起来:

pnpm install eslint-config-cobytelint@workspace -w
复制代码

直接我们回到 eslint-guide 目录,在 .eslintrc.js 文件中配置我们上面创建的自定义 ESLint 配置包,主要进行以下配置:

module.exports = {
  extends: ['cobytelint']
}
复制代码

注意:共享配置包在配置项 extends 中只需要填写 eslint-config- 后面部分即可。

再运行 npx eslint test.js --fix

var-to-let.png

这个时候我们看到 test.js 文件中的 var 还是变成了 let,说明我们的配置是正确的。

此外我们还可以使用 eslint-define-config 包来帮助我们做语法提示。

const { defineConfig } = require('eslint-define-config')

module.exports = defineConfig({
  root: true,
  rules: {
    // rules...
  }
})
复制代码

提示效果如下:

define-config.png

上述 ESLint 插件和配置的项目地址:GitHub

ESLint 配置文件中 extends、plugin 及 rules 的区别

ESLint 的核心 — rules

ESLint 的核心就是它的规则,通过规则进行代码校验和代码修复。我们把上面我们实现的 no-var 的 ESLint 规则 的文件夹 rules 拷贝到我们的测试目录 eslint-guide 中。

rulesdir.png

.eslintrc.js 文件进行以下配置:

module.exports = {
    'rules': {
        'no-var': ['error']
    }
}
复制代码

再运行以下命令:

npx eslint test.js --rulesdir rules // --rulesdir 指定运行的规则目录
复制代码

通过下图的运行结果,我们可以看到我们的规则在脱离了 ESLint 的插件框架之后还是能正常运行的。

rulesdir-error.png

那么如果我想分享我写的 ESLint 规则给其他人使用,要怎么做呢?ESLint 则提供了 plugins 的插件机制来实现这一功能。

plugins 插件

ESLint 本身已经提供了很多的规则,但也覆盖不了所有的情况,所以我们可以通过自定义规则进行处理。如果我们想对自定义的规则进行共享,那么就可以通过 ESLint 提供的 plugins 机制进行。

例如我们上文中就实现了一个 ESLint 插件 eslint-plugin-colint。我们就可以把该插件发布成 npm 包,然后提供给他人下载使用。

插件通常是针对某一种特定场景定义开发的一系列规则,例如 eslint-plugin-vue 就是针对 Vue 项目开发的 ESLint 插件,eslint-plugin-prettier 就是针对 Prettier 开发的 ESLint 插件。

插件的使用:

// .eslintrc.js
{
    plugins: ['prettier'],
    rules: {
      	// prettier
    	'prettier/prettier': 'error',
    }
}
复制代码

插件可以通过在 .eslintrc.js 文件中的 plugins 填写插件名称就可以进行引入了,比如 eslint-plugin-prettier 只需要填 prettier,eslint-plugin-vue 只需要填 vue 即可。在 plugins 中进行引入之后,还需要在 rules 选项中进行设置你需要的规则。但如果规则非常多,都需要手动进行设置的话,就太麻烦了,所以还可以简便方式使用插件本身已经配置好的推荐选项。例如进行以下设置:

// .eslintrc.js
{
    extends: ['plugin:vue/vue3-recommended']
}
复制代码

在 extends 选项中进行设置,其中 plugin: 表示这是一个插件,后面跟着的就是插件名称,/ 后面表示的该插件配置集成选项,也就是该插件已经进行相关的 rules 设置,使用者只需要使用它推荐的就可以了。

一般 ESLint 插件都会实现了很多的规则,比如 eslint-plugin-vue 就实现了几十种配置规则,为了方便其他人使用,一般会默认提供几种实践方案。例如在下图中可以看到 eslint-plugin-vue 有很多个配置选项, vue3-recommended 只是 Vue3 的推荐配置。

vue-recommended.png

即便在 extends 引用了推荐的配置选项,但你还是可以在 rules 选项中进行重新配置相关 rules。

// .eslintrc.js
{
    extends: ['plugin:vue/vue3-recommended'],
    rules: {
       'vue/no-v-html': 'off'
    }
}
复制代码

extends 继承

在上一小节中我们已经通过 extends 选项进行配置 ESLint 插件进行使用了,extends 选项除了配置 ESLint 插件之外,还可以配置一个 npm 包,也可以理解为继承他人的 ESLint 配置方案,可以看成实际就是一份别人配置好的 .eslintrc.js

extends 配置一个 npm 包,对此 npm 包的名称是有要求的,我们在前文中也有说到了,在这里再重申说明一下,ESLint 的共享配置 npm 包必须是以 eslint-config-xxx 开头或者 @xxx/eslint-config 此种类型,其中 xxx 是具体的名字,这是 ESLint 共享配置 npm 包名称的约定。

下面是 ELement-Plus 的 ESLint 配置的 extends 选项。

extends: [
    'eslint:recommended',
    'plugin:import/recommended', // 插件中的 extends
    'plugin:eslint-comments/recommended',
    'plugin:jsonc/recommended-with-jsonc',
    'plugin:markdown/recommended',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
],
复制代码

我们来解读一下这里的配置都表示什么意思。

  • eslint: 开头表示的是 ESLint 自身的规则
  • plugin: 开头的示的是 ESLint 插件的规则
  • 此外的则是 ESLint 共享配置

总的来说 extends 选项就是继承其他配置文件,秉着尽可能复用的原则,ESLint 允许我们使用自身的配置(eslint: 开头)或者插件中的配置(plugin: 开头)或者是第三方模块中的配置。

ESLint、Prettier 以及 EditorConfig 之间的协作

ESLint 的强项

我们在上文中已经对 ESLint 的主要作用进行了总结:检查语法发现问题强制代码风格。再进一步概括就是两项:代码质量问题代码风格问题

代码质量问题:

  • debugger 调试代码未删除
  • 参数重复
  • 对象属性重复
  • 变量定义后未使用

代码风格问题:

  • 不允许多个空格
  • 不允许使用混合空格和制表符进行缩进
  • 关键字前后必须有空格
  • 强制使用单引号或双引号

我们一般只使用 ESLint 做代码校验,而强制代码风格我们则使用 Prettier,因为 ESLint 处理代码风格的能力并没有 Prettier 专业。专业的人做专业的事,所以让 Prettier 接管我们的代码风格。

什么是 Prettier?

Prettier 官方首先告诉你,Prettier 是一个 Opinionated 的代码格式化工具。简单来说就是 Prettier 不管你之前的代码是什么规则什么风格,Prettier 会去掉你代码里原先所有样式风格,然后用 Prettier 的格式重新输出。

又因为本身 ESLint 已经有部分代码风格的检测,直接和 Prettier 进行使用会发生冲突,所以我们需要把发生冲突的规则都关闭掉。我们可以使用 eslint-config-prettier 来关掉所有和 Prettier 冲突的 ESLint 的配置项。具体就是在 .eslintrc.js 配置文件里面将 prettier 设为最后一个 extends。

// .eslintrc.js
{
    extends: ["prettier"] // 必须是最后一个,才能确保覆盖
}
复制代码

查看一下 eslint-config-prettier 的源码其实就是把所有冲突的规则设置为 0 或者 'off'

config-prettier.png

其中的英文注释说得很清楚:使用 Prettier 时永远不需要启用的规则。

当我们希望运行 ESLint 命令 --fix 的时候,也可以进行代码风格修复,那么我们就需要安装 eslint-plugin-prettier

.eslintrc.js 文件进行以下配置:

// .eslintrc.js
{
    plugins: ['prettier'],
    extends: ["prettier"], // prettier 必须是最后一个,才能确保覆盖
    rules: {
      	// prettier
    	'prettier/prettier': 'error',
    }
}
复制代码

extends 中的 prettier 代表的是 eslint-config-prettier,是进行关掉所有和 Prettier 冲突的 ESLint 的配置项。

plugins 中的 prettier 代表的是 eslint-plugin-prettier,引入 ESLint 的 Prettier 插件,然后在 rules 选项中进行配置 'prettier/prettier': 'error',这样就开启了 ESLint 的 Prettier 插件。这样我们就可以在运行 ESLint 命令 --fix 的时候,也可以进行代码风格修复。

其实上面的 .eslintrc.js 文件还可以合并成以下方式,这也是官方推荐的配置:

// .eslintrc.js
{
   extends: ["plugin:prettier/recommended","prettier"], // prettier 必须是最后一个,才能确保覆盖
}
复制代码

ESLint 插件 Prettier 如何工作

首先加载 Prettier

load-prettier.png

加载 Prettier 配置项

prettier-options.png

合并配置项目

prettierOptions.png

从上面代码可以看到除了加载 Prettier 配置项之外,还通过 editorconfig: true 选项进行强制解析 .editorconfig 文件来确定要使用的配置选项。.editorconfig 文件就是 EditorConfig 的配置文件。

EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。EditorConfig 项目由定义编码样式的文件格式和一组文本编辑器插件组成,编辑器插件通过读取文件并以已定义的样式格式化指定文件。

Prettier 通过 editorconfig: true 选项进行强制解析 .editorconfig 文件来确定要使用的配置选项,这样可以让 Prettier 和 EditorConfig 共享一些配置项,而不用在两个单独的配置文件中重复这些配置项。

Prettier 源码中有关于 .editorconfig 文件配置的转换如下图:

editorConfigToPrettier.png

我们可以看到 .editorconfig 文件的配置项都将被转换成 Prettier 选项。

而从下图可以知道如果同时配置了 .editorconfig 文件和 .prettierrc 文件,最终将以 .prettierrc 文件的配置为准。

prettierOptions.png

通过 Prettier 格式化代码

formatPrettier.png

我们可以看到如果 Prettier 格式化出错了的话,那么就由 ESLint 进行报错,这样相当于统一了代码问题的来源。

通过 ESLint 修复代码

fixPrettier.png

我们可以看到最终修复还是通过 ESLint 提供的方法。

小结

通过分析 ESLint 插件 Prettier 是如何工作的,我们可以更加深刻理解 ESLint 配置中的 extends 和 plugins 选项的区别和作用,这也是 ESLint 配置中最需要理解的通透的选项。ESLint 自身虽然已经提供了很多检查的规则,但还是覆盖不了所有的场景,所以 ESLint 提供了插件机制让我们可以自定义规则去处理我们特有的场景。plugins 是以 eslint-plugin- 开头的,例如 eslint-plugin-prettier。又因为 eslint-plugin-prettier 中有很多规则是跟 ESLint 原先的规则是有冲突的,所以我们需要把它们都关闭掉,但如果每次都进行手动配置关掉的话,太麻烦了,所以我们可以配置好一份设置之后进行共享,以后只需要使用这一份配置就可以了,这就是 ESLint 中的共享配置,并且可以发布成 npm 包,包名约定为以 eslint-config-xxx 开头或者 @xxx/eslint-config 此种类型。

以上便是 ESLint、Prettier 以及 EditorConfig 之间的协作。

代码规范自动化设置

我们目前所有的测试都是通过运行命令来进行的,那么我们希望在我们书写代码的时候,就立刻能提示错误,同时在我们保存的时候进行相应的格式化,那么我们就要进行以下的设置。

VSCode Worksapce 设置

值得注意的是我们一般都会进行 VSCode 的 Worksapce 选项设置,设置完后会在根目录下生成一个 .vscode 目录并且这个目录是提交到仓库中的,这样所有使用 VSCode 编辑器的人打开这个项目都会拥有相同的配置体验。

设置在保存的时候进行格式化:

02.png

这样所有使用 VSCode 编辑器的人打开这个项目都会拥有相同的配置体验。

VSCode 的 ESLint 插件配置

在 VSCode 中我们要自动执行我们设置的 ESLint 配置,就要安装 VSCode 的 ESLint 插件。

vscode-eslint.png

VSCode 的 ESLint 插件首先会查找当前的目录中有没有 ESLint 配置,没有的话就有去全局中查找 ESLint 的配置,如果都没有的话就不进行校验了,如果存在就匹配待校验的文件属于 VSCode 的 ESLint 插件配置文件中的  "eslint.validate" 或者属于 "eslint.probe" ,匹配成功就进行校验。

ESLint 将自动在待校验的文件目录里寻找 ESLint 配置文件,接着是父级目录,一直到系统的根目录,除非在 ESLint 配置文件中指定 root: true 停止向上寻找。这也是 ESLint 配置文件中 root: true 配置项的作用。

VSCode 的 ESLint 插件配置配置项:

  • eslint.enable 启用或者禁用 VSCode 的 ESLint 插件,默认开启。

  • eslint.validate 指定要校验的文件类型,默认为 ["javascript", "javascriptreact"]。如果待校验的文件类型属于 eslint.validate,VSCode 的 ESLint 插件就会进行 ESLint 校验。这是一个旧的设置。

  • eslint.probe 指定要校验的文件类型,默认值为 ["javascript"、"javascriptreact"、"typecript"、"typecriptreact"、"html"、"vue"、"markdown"]。如果待校验的文件类型属于 eslint.probe,且 ESLint 配置文件中没有引入相应的插件,那么就不进行 ESLint 校验;如果待校验的文件类型属于 eslint.probe,且 ESLint 配置文件中引入了相应的插件,那么就进行 ESLint 校验。

  • editor.codeActionsOnSave 如果设置为 true,则所有 ESLint 插件的可自动修复的错误都将在保存时被修复。

.vscode 目录的配置文件 settings.json 中的 ESLint 配置选项:

{
  "eslint.probe": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "html",
    "vue",
    "markdown",
    "json",
    "jsonc"
  ],
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "html",
    "vue",
    "markdown",
    "json",
    "jsonc"
  ],
}
复制代码

Prettier 配置

Prettier 可以强制格式化我们的代码风格,其他在上文中已经说了太多,这里不再进行赘述。

在 VSCode 中安装 Prettier 插件:

prettier.png

.prettierrc 配置文件

{
  "semi": false,
  "singleQuote": true,
  "overrides": [
    {
      "files": ".prettierrc",
      "options": {
        "parser": "json"
      }
    }
  ]
}
复制代码

值得注意的是,我们一般还会进行 formmater 设置,并且把默认选项设置为 Prettier,但我们这里不用这样设置了,因为我们使用了 ESLint 的 eslint-plugin-prettier 插件之后,我们又在 VSCode 的 ESLint 插件配置中设置了 ESLint 的配置,这样我们的 Prettier 代码风格也已经通过 ESLint 来实现了。

01.png

所以我们一般使用了 ESLint 的 eslint-plugin-prettier 插件之后又配置了 ESLint 的 VSCode 配置项,就不再需要进行此项设置了。

EditorConfig 编辑器配置

为了解决编辑器配置层面的编码风格不一致问题,我们就需要使用到 Editorconfig 文件,可以向项目或解决方案添加 EditorConfig 文件,强制对使用该基本代码的所有人实施一致的编码样式。 EditorConfig 文件中声明的设置优先于全局文本编辑器设置。 在项目或基本代码中使用 EditorConfig 文件可为项目设置编码样式、首选项和警告。EditorConfig 项目由一个用于定义编码样式的配置文件和文本编辑器插件组成,这些插件使编辑器能够读取文件格式配置文件并遵循定义的样式。

安装 VSCode 的 Editorconfig 插件:

EditorConfig.png

.editorconfig 配置文件

root = true // 表示是最顶层的配置文件,设为 true 时,停止向上查找

[*]
charset = utf-8 // 设置编码
indent_style = space // 设置缩进为 tab 或 space
indent_size = 2 // 设置缩进所占列数,如果 indent_style 为 tab,则以 tab_width 值作为缩进宽度
end_of_line = lf // 设置换行符,值为 lf(换行)、cr(回车) 和 crlf(回车换行)
insert_final_newline = true // 设为 true 表示使文件以一个空白行结尾
trim_trailing_whitespace = true // 设为 true 表示会去除行尾的空白字符
quote_type = single // 使用单引号 single 或者双引号 double
复制代码

值得注意的是同时使用 Prettier 和 Editorconfig,Prettier 默认会和 Editorconfig 的配置文件进行合并配置。

VSCode 提示安装插件配置

我们在上面介绍了很多可以自动化校验我们代码的插件,这些插件可以使我们的工作效率更高,所以我们也希望其他使用此项目的小伙伴也同样使用这些插件。我们可以进行以下设置来向我们的团队推荐相关的插件。

我们找到插件列表中相关的插件然后点解设置选择推荐到工作区的推荐中。操作如下图的 1、2步骤:

recommend.png

这样就会在根目录的 .vscode 目录下创建一个 extensions.json 文件:

{
  "recommendations": [
    "vue.volar",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "antfu.vite",
    "lokalise.i18n-ally"
  ]
}
复制代码

当我们的团队成员拉取我们的项目之后,使用 VSCode 打开,再打开插件窗口在搜索栏输入 @ 符号,在出现的下拉列表中选择 @recommended 选项:

aite.png

在出现的下拉列表中选择 @recommended 选项之后就会出现推荐的插件列表了:

recommendations.png

###利用 Git Hooks 进行代码检查

我们在前文中已经介绍了各种命令和利用编辑器进行代码格式自动化检查,但难免有人嫌麻烦不会进行浏览器配置又或者他使用的编辑器根本不支持自动化格式检查,我们还可以进行设置最后一道关卡,由于提交代码前是最后一个管控代码质量的环节,所以我们可以利用 Git Hooks 在提交代码之前进行代码检查和格式化。这样可以确保代码是按照我们设置规定的代码格式被提交到仓库上。

Husky

Husky 是社区常用的 Git Hooks 工具,可以在你进行一些 Git 操作(如 commit/push)的时候自动执行一些  Node Script。Husky 支持所有 Git 钩子

安装 Husky:

pnpm install husky -D -w
复制代码

自动配置 Husky

pnpm dlx husky-init
复制代码

此命令将在 package.json 文件的 scripts 中创建一条脚本命令:"prepare": "husky install"

"scripts": {
    "prepare": "husky install"
},
复制代码

意思就是说我们克隆代码安装完依赖后会自动执行 husky install 命令。

在运行 pnpm dlx husky-init 命令之后同时也在根目录下创建 .husky 目录和相关文件。

husky-dir.png

这个时候我们就可以在 pre-commit 文件中配置一些脚本命令了,让这些脚本命令在 git commit 之前执行。

Lint-staged

如果在整个项目上进行代码检查那么效率就会非常低下,所以最好的做法就是检查那些被改动了的文件,而 Lint-staged 则可以帮我们实现这个目的。

安装 lint-staged

pnpm install lint-staged -D -w
复制代码

然后在 package.json 文件进行配置:

"lint-staged": {
    "*.{vue,js,ts,jsx,tsx,md,json}": "eslint --fix"
}
复制代码

接着在刚从创建的 .husky 目录中的 pre-commit 文件中配置如下脚本:

pnpm exec lint-staged
复制代码

pnpm exec 是在项目范围内执行 shell 命令的意思。

Pretty-quick

同样的如果在整个项目上进行代码格式化也是效率非常低下的,我们可以通过 pretty-quick 在更改的文件上运行 Prettier 进行代码格式化。

安装 pretty-quick

pnpm install pretty-quick -D -w
复制代码

接着在 .husky 目录中的 pre-commit 文件中配置如下脚本:

pnpm exec pretty-quick --staged
复制代码

总的来说就如下:

  • lint-staged 只检查那些被改动了的文件
  • pretty-quick 在更改的文件上运行 Prettier 格式化
  • husky 通过提供 Git Hooks 能力执行以上操作

总结

首先本文不是一篇专门讲解 ESLint 配置的文章,而更像一篇代码规范的配置指引,所以即便你看完本文你还是需要结合其他 ESLint 教程进行学习。ESLint 可谓是现代前端开发过程中必备的工具了,所以我们有必要好好学习并掌握它,而且值得深挖和理解其工作原理。

ESLint 的主要作用就是检查语法发现问题强制代码风格。它的工作原理就是通过编译器编译源码获得 AST,然后遍历 AST 节点,通过规则对 AST 节点进行检查,获得警报信息和修复信息,最后输出警报信息和进行源码修复。

我们一般只使用 ESLint 做代码校验,强制代码风格我们则使用 Prettier。并且通过分析 eslint-plugin-prettier 是如何工作的,我们可以更加深刻理解 ESLint 配置中的 extends 和 plugins 选项的区别和作用,这也是 ESLint 配置中最需要理解的通透的选项。

ESLint 自身虽然已经提供了很多检查的规则,但还是覆盖不了所有的场景,所以 ESLint 提供了插件机制让我们可以自定义规则去处理我们特有的场景。plugins 是以 eslint-plugin- 开头的,例如 eslint-plugin-prettier。又因为 eslint-plugin-prettier 中有很多规则是跟 ESLint 原先的规则是有冲突的,所以我们需要把它们都关闭掉,但如果每次都进行手动配置关掉的话,太麻烦了,所以我们可以配置好一份设置之后进行共享,以后只需要使用这一份配置就可以了,这就是 ESLint 中的共享配置,并且可以发布成 npm 包,包名约定为以 eslint-config-xxx 开头或者 @xxx/eslint-config 此种类型。

我们希望在我们书写代码的时候,就立刻能提示错误,同时在我们保存的时候进行相应的格式化,那么我们就要进行各种编辑器插件的配置,这就是代码规范自动化最重要的一部分。

虽然我们通过各种命令和利用编辑器进行代码格式自动化检查,但难免有人嫌麻烦不会进行浏览器配置又或者他使用的编辑器根本不支持自动化格式检查,我们还可以进行设置最后一道关卡,我们可以利用 Git Hooks 在提交代码之前进行代码检查和格式化。这样可以确保代码是按照我们设置规定的代码格式被提交到仓库上。

欢迎关注本专栏,了解更多 Vue3 组件库技术知识。

本专栏往期文章:

Element Plus 组件库相关技术揭秘:1. Vue3 组件库的设计和实现原理

Element Plus 组件库相关技术揭秘:2. 组件库工程化实战之 Monorepo 架构搭建

Element Plus 组件库相关技术揭秘:3. ESLint 核心原理剖析