侧边栏

使用ESLint检查JavaScript代码

发布于 | 分类于 前端/前端工程

代码写久了就会形成自己的风格偏好,比如字符串用单引号还是双引号、句末要不要分号之类的,这导致在编码过程中还需要分散一部分精力来维护代码风格。

此外,如果单纯依靠自己的编码习惯,则在不同的时间阶段,写出来的代码风格差异可能会比较大。

那么,有没有什么工具可以将代码格式化成统一的风格呢?

引子

主流的代码编辑器如webstromVS Codesublime等都提供了代码格式化的快捷键。

sublime可以安装很多插件,其中就有快速整理代码格式的插件

  • HTML-CSS-JS Prettify,格式化HTML/CSS/JS代码,需要安装node环境,默认快捷键是ctrl+shift+H
  • Pretty JSON,格式化JSON文件,默认快捷键ctrl+shift+J

WebStrom的功能更加强大,可以先自定义设置对应文件的代码格式,然后通过快捷键格式化

  • 首先打开File > Settings,搜索Code Style,然后选择指定后缀的文件,可以看见右侧展开的操作选项卡
  • 通过切换选项卡设置代码风格,包括缩进、断行、注释、等号对齐等功能,然后确定
  • 通过Code菜单栏执行Reformat Code操作,也可以设置对应的快捷键

不同的编辑器默认的代码风格也存在一些差异,更换环境或者编辑器之后,风格可能会发生变化。

因此这种依赖于编辑器的代码格式化,并不是很通用。

那么,有没有什么工具可以帮助我校验格式,同时可以将代码格式化成我期望的风格呢?

这就需要用到各种编程语言的linter工具了,主流的编程语言都提供了一些检查代码规范的工具

  • pythonPyLintFlake8
  • JavaCheckstyleFindBugs
  • JavaScriptJSLintESLint

由于目前从事前端开发工作,在工作共需要编写大量JavaScript代码,因此本文将介绍使用ESlint检测前端项目代码。

ESLint

ESLint是一个可以静态分析JavaScript代码的工具,这样就可以发现代码的潜在问题。

关于ESLint的具体使用,官方文档非常全面,这里只是简单介绍一下。

可以先阅读一下官网的核心概念板块,然后参考规则文档了解具体可配置的规则。

为项目安装eslint

参考:官方文档的quick start

初始化依赖,该cli工具会询问项目的一些基本信息,自动生成eslint配置文件,同时安装相关的插件。

bash
npm init @eslint/config@latest

安装完了之后,就可以通过命令行进行eslint检测和修复了。

bash
npx eslint xxx.js

注意不同版本的eslint对于NodeJS版本有要求,否则可能会遇见一些报错,具体可以查看相关文档,

下面是通过eslint检测时发现的代码错误

我们安装的只是命令行工具,每次需要运行eslint 命令来进行检测,比较麻烦,一般只会在CI等工作流程中才会通过命令行来进行检测。

有没有什么方法可以让编辑器在我们编写代码的时候就展示响应的提示警告(比如在错误的地方展示一个红色的波浪线~

这种方式是可行的,不过需要对编辑进行配置、或者安装相应的插件。

编辑器配置ESLint

vscode配置eslint

上面的截图使用的是vs code作为代码编辑器,vs code中可以使用ESLint这个插件

搜索并安装插件

然后重启vs code编辑器,现在就可以直接在编辑界面看到对应的提示了

这对在编写时就发现代码中的BUG,很有帮助。

webstrom配置eslint

参考:

Webstrom内置了eslint,不需要安装额外的插件,只需要打开项目开关即可。

首先需要开启webstrom项目的ESLint配置

然后编写一段测试代码,可以发现具体的提示

不过由于webstrom对于解析eslint做了一些内部的处理,因此某些旧版本的webstrom可能无法使用最新版的有break changeeslint版本,这种情况下需要升级webstrom、或者安装旧版本的eslint才能正常工作。

plugins插件

eslint内置了一些解析规则,比如对常量赋值这些。但是还有一些特定场景下需要扩展检查规则,比如react项目需要检测JSX,而Vue项目需要检测SFCtypescript项目则需要支持对ts文件的检测。

这个时候,就需要用到插件,插件可以包含一组 ESLint 规则、配置、处理器(解析特定的文件)和环境,一般会包含自定义规则。

插件名一般以eslint-plugin-xxx格式发布在npm上面。

主流库一般都会提供自己插件,比如Vueeslint-plugin-vue

将插件安装完成,并在plugins字段进行声明之后,就可以在rulesparserOptions等地方使用该插件中创建的规则和处理器等了。

js
{
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    '@typescript-eslint/ban-types': 'off',
    'vue/multi-word-component-names': 'off',
  }	
}

extends多项目共享配置文件

在单个项目里面,相关的eslint配置文件可以通过提交到git进行版本管理,达到团队里面多人共享统一配置的目的。

对于多个项目而言,如何统一eslint的配置文件呢?

最简单的方式肯定是直接复制一份配置文件到新项目里面。根据DRY原则,这样的做法肯定不是最优的。

参考:Shareable Configurations

主流的做法是将团队统一的eslint配置项发布到npm上面,一般以eslint-config-xxx命名。

每个项目通过安装package,再配置eslintextends配置项,实现共享eslint配置的目标。

因此,即可以通过npm安装插件,也可以直接安装集成的eslint config

由于部分配置会依赖特殊插件,比如vue项目需要依赖eslint-plugin-vue,react项目需要依赖eslint-plugin-react

那么是将这些都安装在devDependencies或者peerDependencies项目中,写一个大而全的,针对整个团队的公共配置;还是区分不同的项目,比如vuereacttypescript单独创建不同的公共配置呢?

有些团队的做法是编写cli脚本,在初始化项目的时候,通过命令行参数来为当前项目创建特定的eslint配置项并安装相关依赖。

CI集成eslint

可以通过git hookspre commit的时候进行校验,将ESlint集成到开发CI流程中。

可以通过husky等工具编写相关的钩子脚本,这里不再展开。

需要注意的是,当项目文件比较多时,eslint 需要花费一定的时间,具体实践方案需要根据开发场景来看,比如看看是否存在需要频繁commit的场景。

一些优化eslint速度的技巧

  • .eslintignore配置忽略一些不需要检测的文件,提高检查速度。
  • lint-staged只会检测已通过git提交的的文件,检测文件的数量很少,速度也会快很多。

在大部分时候,开发都会通过IDE来处理lint提示,统一的命令行检测只会在提交或者push的时候进行,因此一般来说,lint耗时不是前端工程优化的重点目标。

typescript

现在ts已经在前端项目中广泛使用,也需要对ts代码进行检测。

由于TS本身有自己的类型检测机制,某些检测与ESLint存在重叠的部分,某些检测又是TS独有的,因为对于TS的代码检测,单独开了一个章节。

noEmit

TS本身就提供了一个编译器命令tsc --noEmit,用于检查 TypeScript 代码的类型错误,该命令不实际编译或输出任何文件。

该命令可以确保代码在类型层面是正确的,也就是说,它可以检查变量是否被正确地使用,以及函数是否接收了正确的参数类型等。

该命令主要用于在构建过程中发现潜在的类型错误,确保代码的类型安全。

TS内置了一套strict,可以在tsconfig.json中配置,表示是否开启一些更严格的类型检查规则,参考

js
"noImplicitAny": true, // 函数参数不指定类型时默认为any,开启后会校验
"strictNullChecks": true,  // 是否允许null 和 undefined 可以被赋值给其它任意类型
"strictFunctionTypes": true, // 严格函数类型检查
"strictBindCallApply": true, // 在使用 bind、call 和 apply 语法的时候,是否进行参数检查
"strictPropertyInitialization": true, // 用于检查类的属性是否被初始化
"noImplicitThis": true, // 是否允许出现隐式 any 类型的 this
"alwaysStrict": true // 开启的话编译后的 js 会带上 use strict

遗憾的是,即使在strict模式下,tsc --noEmit也不会像ESLint那样检测代码风格和一些通用的代码错误

ts
let b:number = 1 + 1
b = "123" // Type 'string' is not assignable to type 'number'.
if(b = 1) { // 这里的错误检测不到
  console.log('b is 1')
}

比如上面的代码,tsc可以检测出b = "123"这行的错误,但无法检测出if(b = 1) { }这里潜在的BUG。

typescript-eslint

除了TS本身的类型检测,我们也希望ts代码能够像js代码那样被ESLint检测代码风格。

由于ESlint本身只会对js代码进行静态分析,他默认的js代码解析器是无法识别ts代码

因此,社区提供了typescript-eslint,它使 ESLint 能够在 TypeScript 代码上运行

  • 允许 ESLint 解析 TypeScript 语法
  • 为 ESLint 规则创建一组工具,以便能够使用 TypeScript 的类型信息
  • 提供大量特定于 TypeScript 和/或使用该类型信息的 lint 规则列表

因此,借助 typescript-eslint插件,ESlint就可以检查ts文中常规的代码风格。

如果未对 typescript-eslint 进行额外配置以利用 TypeScript 的类型信息,ESLint 将主要基于其自身的规则来检查 TypeScript 代码的风格和潜在的运行时错误,但不会进行深入的类型检查

typescript-eslint内置了一些配置集,参考typed-linting这个文档

  • tseslint.configs.recommended,它包含了一组核心的 ESLint 规则,它不特别利用 TypeScript 的类型信息来提供额外的类型检查。
  • tseslint.configs.recommendedTypeChecked,这个配置在 recommended 的基础上增加了额外的规则,这些规则需要 TypeScript 类型信息来执行更深入的检查
  • tseslint.configs.strictTypeChecked,提供了一个非常严格的规则集,既包括了限制代码风格和结构的规则,也包括了需要 TypeScript 类型信息的类型感知规则(类似于上面提到的ts strict模式)。

举个例子,同样是上面这段ts代码,在eslint进行检测时,可以发现eslint并不会处理类型错误

ts
let b:number = 1 + 1
b = "123" // 这里的类型错误eslint检测不到
if(b = 1) { //  Expected a conditional expression and instead saw an assignment 
  console.log('b is 1')
}

参考这个QAtypescript-eslint插件不会展示ts本身的类型错误

小结

在实际工程中,对于ts代码,一般的处理方式是,通过tsc -noEmit进行类型提供的校验,通过ESLint进行代码风格和错误的校验。

  • TSC:更侧重于类型系统的安全性,确保代码在类型层面的正确性。
  • ESLint:更侧重于代码风格、潜在的错误、以及一些最佳实践的遵循,它不仅限于类型检查,还包括了代码的可读性、维护性等方面的检查。

这里存在的一个问题是,由于tsc命名会全量检查ts文件,且貌似无法通过lint-staged等工具控制检查文件的范围。

这在项目比较庞大、文件数量比较多时,会花费不少的时间,因此tsc是前端CI构建中的性能瓶颈之一(这也是某些项目为什么会抛弃TypeScript,回归js的原因之一)。

关于tsc的性能优化,是另外一个主题,后面单独再整理。

code lint Or code style?

混乱的概念

我是webStrom的重度用户,吸引我的一个点就是:早期各种文本编辑器需要自己安装各种代码格式化的插件,而webstrom作为一个成熟的IDE,直接内置了对应的功能。

后来接触到ESLint,发现webstrom甚至还有一个专门的Apply Eslint Code style Rules快捷键,这是我使用频率非常非常高的快捷键。

以至于在很长一段时间内,我都认为代码检测代码格式化应该是同一件事情。

但在阅读有的项目中,会发现这些项目同时出现了.eslintrc.js.prettierrc两个配置文件。

当进入到.prettierrc文件时,发现webstrom又会提示使用prettier作为项目的代码风格。

而在有的项目中,还会在eslintrc.js配置文件中,使用eslint-config-prettier这个插件,这个插件的作用是关闭eslint中所有与prettier可能冲突的规则。

eslint也可以检测代码风格,为什么还会有一个单独格式化代码的prettier配置呢?

在这些混乱的工具的背后,eslint和prettier到底有什么关系和冲突?

功能不同

要弄明白这个问题,首先需要明确,代码检测 Code Lint代码风格Code Style本身是两个不同的概念

  • 代码风格是纯样式的,不会影响代码执行,也不会出bug,比如缩进2个字符还是4个字符、单引号还是双引号、等号两边有没有空格之类的
  • 代码检测是检测不推荐的编程习惯,可能导致代码维护困难,或者可能出现一些bug,比如if(a = 1){}这种将条件判断误写为赋值的情况,或者对一个const常量进行赋值操作

社区显然把他们当做了两个功能,prettier就是一个专注于统一代码风格的工具,比如缩进、是否使用引号、换行等,不涉及具体的代码质量。

下面是找到的一些关于的FormattersLinters的选取建议:使用单独使用Prettier 等工具进行格式化使用 linter 捕获错误!

  • prettier的文档Prettier vs. Linters
  • typescript-eslint的文档Formatters vs. Linters,理由是单独的Formatters会更快,且linters在不考虑代码格式的规则之后,也可以专注于逻辑本身

当然,这个建议并没有被所有人所接受

  • 有的人已经厌倦在前端项目中管理各种各样的config文件了,同时使用eslintprettier意味着我们需要管理至少两个配置文件,同时prettier也并不支持更灵活的配置
  • 有的人坚持”一个函数只做一件事“的软件开发原则,认为eslint专注于代码质量的检测就可以了;就像prettier只专注于代码格式化

目前看起来eslint团队已经做出了选择:在版本v8.53.0之后,与代码风格相关的eslint规则都被剥离了。

在这篇官方文档Deprecation of formatting rules,介绍了该决定的背景和具体原因。

eslint-stylistic

如果你还是坚持只需要使用eslint来同时处理代码检测和代码格式化(我也是这类用户),可以使用eslint-stylistic

这个包实现了eslint中被废弃的与代码风格相关的规则,由Anthony Fu大佬进行维护。

他之前专门写过一篇文章:为什么我不使用 Prettier,大概原因是是:prettier“固执己见”,可配置度很低,有一些定制化的规则如换行会带来git diff的困扰,具体可以阅读原文。

如果不想手动配置eslint-stylistic,也可以直接使用@antfu/eslint-config这个工具。

pnpm i -D eslint @antfu/eslint-config

这个工具内置了很多lint规则,比如TypeScriptJSXVue等,对于单个项目而言基本上可以开箱即用

如果有某些内置的风格想要自定义,覆盖一下配置即可

js
import antfu from '@antfu/eslint-config'

export default antfu({
  rules: {
    'no-console': 'off',
    'vue/block-order': ['error', {
      order: [['template', 'script'], 'style'],
    }],
  },
})

这样也不需要再从社区眼花缭乱的各种recommended插件中继承样式了。

小结

不论是ESlinttypescript还是prettier,使用他们的目的都是编写规范、统一的代码,并在编写代码的时候就尽可能发现潜在的问题。

各个工具都在跟随时代进行发展,此外社区也在更新一些新的linter,比如这个号称比ESLint快很多倍的Oxlint

至于具体用什么工具,喜欢什么风格,这些都是看自己或团队的偏好习惯了。

一个人的编码习惯和风格是很难修正的,如果在初期养成好习惯,会少走很多弯路。

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。