本文正在参加「金石计划 . 分割6万现金大奖」
在计算机科学中,lint是一种东西程序的名称,它用来符号源代码中,某些可疑的、不具结构性(可能造成bug)的段落。它是一种静态程序剖析东西,最早适用于C语言,在UNIX平台上开发出来。后来它成为通用术语,可用于描绘在任何一种计算机程序语言中,用来符号源代码中有疑义段落的东西。——来自维基百科
现状
ESLint 可能是开发中最常用的东西之一了,不过因为各种脚手架东西的盛行,虽然必定程度上提升了开发效率,可是却也将那些构建细节束之高阁。
假如此刻公司需求你从零开始建立一个完好的项目结构,为了标准团队的代码风格,ESLint 该怎么去装备呢?本篇就来说说怎么构建一个 ESLint + React 的实践,而且测验去了解 ESLint 背面不为人知的细节,而关于其他结构,比方 Vue 这种,构建的流程也是相似的。
重读 ESLint
先来看一张概念图来了解 ESLint 的组成:
-
自顶向下来看,最上面便是 ESLint 的相关生态,比方 Prettier、Husky 等等东西。
-
再下面一层便是 ESLint 的运用,分为运用装备文件,和运用命令行以及通过 NodeJS API 来运用,这个和其他的构建东西,比方 Webpack、Rollup 是共同的。
-
接着便是 ESLint 的中心,规矩以及插件,这一层很多人都知道它的重要性,可是却没有真正了解过。
-
最底层便是 ESLint 处理代码的逻辑。Parser 解析器用来将 JS 代码解析成 AST,默许 ESLint 选用 ESPree 来解析 JS,也能够选用其他的解析器,可是必须契合 ESLint 的接口要求。假如是 TS 的代码,那么就需求能处理 TS 文件的解析器了。
-
Processor 是处理器,用来提取 JS 代码,或许对 JS 代码进行转换。一般处理器作为插件一个特点,暴露给外部运用。
关于开发者来说,装备、插件以及规矩才是需求要点了解的,下面就来看看这些熟悉而又生疏的概念。
ESLint 装备文件
先说装备文件,其格局有下面几种,依照 ESLint 读取装备文件的优先级次序排列:
ESLint 主动去项目下寻找 .eslintrc.* 这种格局的装备文件,假如要运用其他格局的装备文件,则能够运用 --config
选项来指定该文件的途径:
eslint --config myConfig.js ./src
除此之外,比较风趣的一点便是,咱们知道在 json 文件中是不能有注释的,可是在 .eslintrc.json 文件中是能够写注释的,ESLint 会疏忽该注释。需求注意的是,package.json 文件中仍是不能写注释的。
首要的装备文件接口类型如下:
interface ESLintConfig {
root?: boolean; // 表明当前文件是否为根装备文件,用于多装备文件时
parser?: string; // 解析器,默许为Espree,解析 AST 节点
env?: Record<string, boolean>; // 代码运转环境
parserOptions?: ParserOptions; // 解析装备
extends?: string | string[]; // 承继其他的规矩
plugins?: string[]; // 插件装备
rules?: any[]; // 规矩装备
processor: string; // 处理器,详细可看上一节的解说
overrides?: any[]; // 针对不同文件,使用不同规矩
globals: Record<string, boolean | 'off' | 'readonly' | 'readable' | 'writable' | 'writeable'>; // 设置全局变量
ignorePatterns: string | string[] // 疏忽目录或文件,等同于运用.eslintignore
}
其间有几个选项是需求要点关注的。
首要 env 选项表明代码的运转环境,常用的如,浏览器和 NodeJS 以及 Jest 单元测试,这些环境中存在一些全局变量,比方 document、process 这种,假如运用这些变量,则需求装备 env 特点,否则运用它们 ESLint 会提示报错:该变量未界说。
除了装备 env 特点外,parserOptions 也是常用的装备,有几个特点需求关注一下。
ecmaVersion 特点 指定 JS 的版别,通过笔者重复测验,发现这个特点没什么效果,想比较而言,env 特点中的装备愈加重要。
sourceType 特点指定 JS 的模块类型,默许为 script 脚本,一般项目中则运用 module 值,表明模块化。
最终则是 ecmaFeatures 特点,其包括了 jsx 特点,用来启用 jsx。完好的装备类型如下:
interface ParserOptions {
ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | "latest" | undefined;
sourceType?: "script" | "module" | undefined;
ecmaFeatures?: {
globalReturn?: boolean | undefined;
impliedStrict?: boolean | undefined;
jsx?: boolean | undefined;
experimentalObjectRestSpread?: boolean | undefined;
[key: string]: any;
} | undefined;
[key: string]: any;
}
然后便是 extends 特点,经常会看到 extends 特点这么装备的:
{
extends: ['eslint:recommended'],
...
}
这儿的 recommended 便是在 ESLint 的规矩中,界说的一个特点 recommended,值为 boolean 类型,为 true 表明 extends 会选用该规矩。鄙人一节还会说到该特点。除了 recommended 外,还能够装备 all:
{
exntends: ['eslint:all']
}
这样便是选用所有的 ESLint 中的规矩,随着 ESLint 的版别迭代,某些规矩可能会有兼容问题,所以这种做法是不引荐的。
其他装备比较简单,就不再逐个赘述了,要点来看看怎么完成规矩和插件,它们之间又是什么关系呢?
ESLint 规矩
ESLint 中的规矩便是对书写代码做了必定的束缚,假如不依照这个束缚来,则 ESLint 中会陈述这个过错,而且假如存在修正过错的办法,也能够主动修正该过错,比方当咱们多加了空格,运用 --fix
的参数则能够修正该过错。
以 max-lines 规矩为例,其大约的结构如下:
首要分为两个部分,meta 表明规矩的元数据信息,create 则是规矩验证的办法。
先从 meta 说起,type 表明规矩的类型,其界说如下
type Type = 'problem' | 'suggestion' | 'layout'
依照问题的优先级次序来了解,problem 表明不运用该规矩,代码可能会导致过错,suggestion 则表明运用该规矩会更好,layout 则是代码风格上的规矩,比方空格、缩进等。
docs 的类型如下:
interface Docs {
description: string; // 规矩的描绘信息
recommended: boolean; // 是否引荐运用该规矩,调配eslint:recommended运用
url: string; // 指定文档的url链接
}
接着 schema,则是装备规矩的选项模式,ESLint 会依据这个模式来验证你填写的规矩选项是否正确。ESLint 运用 ajv这个库来验证 schema。
最终 meta 中的 message 则表明不契合规矩时的报错信息,调配规矩上下文目标 context.report 办法运用,打开 create 办法能够看到下面这句:
// ...省掉
message: {
exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
}
// ...省掉
create (context) {
// ...省掉
return {
"Program:exit"() {
let max = 300;
// ...省掉
if (lines.length > max) {
const loc = {
start: {
line: lines[max].lineNumber,
column: 0
},
end: {
line: sourceCode.lines.length,
column: sourceCode.lines[sourceCode.lines.length - 1].length
}
};
context.report({
loc,
// 指定对应哪条信息
messageId: "exceed",
// data 中的数据供给给上面 message 运用
data: {
max,
actual: lines.length
}
});
}
}
};
}
}
create 办法返回了一个目标,目标中的每个 key 都是 AST 中的节点,对节点做检查,发现不满足规矩,运用 report 来陈述过错。create 中的参数 context 是 ESLint 供给的上下文目标,其间包括了很多办法,详细能够检查 ESLint 的详细的文档。
上面说到过,能够通过 --fix
来修正过错,这能够通过设置 report 中的参数来完成:
context.report({
node: node,
message: "Missing semicolon",
fix: function(fixer) {
return fixer.insertTextAfter(node, ";");
}
});
ESLint 插件
说完了规矩,来说说 ESLint 的另一个中心,插件。
ESLint 中的插件和其他库中的插件对比,比方 Vue 或许 Webpack 中的插件,后者的插件供给了扩展功用,而 ESLint 的插件更像是集成了上面所有装备的东西包,比方,能够在插件中界说规矩 rules,界说解析器 processors,也能够界说 parserOptions 等等。
ESLint 的插件通常命名都是 eslint-plugin-* 这种格局,运用这种格局,在装备文件中设置 plugins 特点时,能够省掉掉前缀的字符串。相似的还有 eslint-config-* 这样的 npm 包,比方 eslint-config-airbnb 这种,那么二者有什么差异呢?
先来看一个比较闻名的插件 eslint-plugin-react ,它最终导出的模块结构时这样的:
module.exports = {
rules: ...,
configs: {
recommended: ...,
all: ...,
'jsx-runtime': ...
}
}
能够看到插件便是对外暴露了一些规矩、装备目标。在 ESLint 中能够看到完好的插件特点如下:
interface Plugin {
configs?: Record<string, ConfigData> | undefined;
environments?: Record<string, Environment> | undefined;
processors?: Record<string, Linter.Processor> | undefined;
rules?: Record<string, ((...args: any[]) => any) | Rule.RuleModule> | undefined;
}
再来看看别的一个闻名的 ESLint 的装备库 eslint-config-standard,里边的入口文件是这样的:
module.exports = require('./eslintrc.json');
这样,两者的差异就一目了然,假如是关于一些自界说的环境,比方说 Vue、React 这样的环境,运用了 .vue h或许 .jsx .tsx 这样的文件,ESLint 是无法辨认的,所以要对这样的代码进行 Lint,就需求自界说环境,解析文件,规矩等。而假如是运用这些结构,都是详细的事务代码,其实运用 eslint-config-* 更合适,其间包括这些环境下的 ESLint 插件。
那怎么去构建一个 ESLint 的实践呢?
标准代码的实践
以一个 React + TypeScript 的项目为例,首要需求在 VSCode 中下载 ESLint 的插件,能够在写代码的时分,即时发现问题。
接下来,新建一个空项目,eslint-best-practice,选用 pnpm 来下载依赖,先安装 ESLint。然后新建一个 index.js 的文件。得到的目录如下:
安装好今后发现 VSCode 弹出了一个过错提示:
这儿是因为 VSCode 中的 ESLint 的插件更新,曾经的一些装备现在现已不支持了,在 VSCode 中找到 ESLint 的装备项,删掉关于其间相关装备就恢复正常了。
接着来写 .eslintrc.js
的装备文件,也能够用你喜欢的恣意上面说到的格局:
module.exports = {
env: {
browser: true,
node: true,
es6: true,
commonjs: true,
jest: true
},
plugin: ['react'],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended',
'plugin:react-hooks/recommended'
]
}
这样一个根据 React 项目的 ESLint 便根本装备好了,仅有缺少的便是关于 TS 的代码 Lint。关于 TS 的 ESLint 的规矩,目前运用的比较多的便是 @typescript-eslint/eslint-plugin
这个包。增加这个包的话,需求对装备文件做一些改动。
改动的原因,便是上面说到的 parser 解析器只能去解析 JS 文件,关于 TS 则无能为力,当然也能够挑选先把 TS 转换成 JS,再去使用 JS 的规矩,可是通过转换后的 JS 代码并不能实时提示编写代码时出现的过错,所以最好的实践,便是增加这个包来单独解析 TS 文件。
根据上面的装备文件,改动如下:
module.exports = {
env: {
browser: true,
node: true,
es6: true,
commonjs: true,
jest: true
},
plugins: ['react', '@typescript-eslint/eslint-plugin'],
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/eslint-plugin/recommended'
]
}
这么一看,如同更简单了。因为插件 @typescript-eslint/eslint-plugin
中现已包括了 parserOptions 的装备,所以这儿就无需再去装备了,别的需求注意的是,除了安装这个插件,还需求安装 @typescript-eslint/parser
解析器的包。
大部分类型过错 TS 现已帮咱们解决了,所以你可能不想使用这么多的 @typescript-eslint/eslint=plugin
中的规矩,此刻能够挑选运用 overrides 装备,来对 TS、TSX 文件做单独的装备:
module.exports = {
// ...省掉
overrides: [
files: ['*.ts', '.tsx'],
rules: {
'@typescript-eslint/no-semi': 'off',
// ...其他规矩装备
}
]
}
@typescript-eslint
中会有一些规矩,需求配合 TS 的装备文件,否则就会出现如下的过错:
依照这个过错提示,增加 parserOptions.project 的装备即可让规矩收效:
module.exports = {
// ...省掉其他
parserOptions: {
project: './tsconfig.json'
}
}
综上能够知道,ESLint 的装备灵敏多变,没有哪种装备能够一次装备处处运用的,需求依据自己的需求,装备如下几个要害特点,env、plugins、extends、parserOptions、overrides等,即能够到达适合自己或团队的最佳 ESLint 的装备。
其他东西
假如想要保证在前端团队中所有的代码风格共同,除了 ESLint 外,还需求一些其他东西调配运用,才干更好的保证代码风格的共同性。
prettier 代码格局化
prettier 是一个和 ESLint 相似的东西,仅仅 prettier 更趋向于代码风格的规矩:比方缩进多少、运用tab、仍是空格等等。他仅仅让代码保持共同的风格,并不能起到如 ESLint 这种发现过错、纠正过错的效果。
一般首要的装备项便是以下几项,至于详细运用哪种风格,仍是要看自己的团队:
{
"arrowParens": "always", // 箭头函数参数只有一个时,也需求增加括号
"semi": true, // 句子分号
"singleQuote": true, // 启用单引号
"jsxSingleQuote": false, // 封闭jsx中运用单引号
"printWidth": 100, // 每一行代码的长度,超出会换行
"useTabs": false, // 不运用tab缩进
"tabWidth": 2, // tab缩进为2个字符
"trailingComma": "es5" // 目标、数组等结束的逗号是否增加,es5是需求增加结束逗号
}
style lint
别的项目中除了 JS/TS 文件外,还有大量的款式文件,相同也需求保证风格共同。关于款式文件,能够运用 stylelint 东西,它和 ESLint 的装备是相似的,只需求承继一些常用的装备即可:
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"], // 常用的根底装备,这样够用了
"customSyntax": "postcss-less", // 在代码中运用自界说的语法,比方less、scss、CSS in JS这种css语法验证
"rules": {
// ...依照团队的需求
}
}
editorconfig
editorconfig 也是代码风格束缚的东西,调配 VSCode 的扩展插件 EditConfig for VSCode,首要是针对 IDE 东西,能够在编写代码的时分,就能保证共同良好的代码风格,而 prettier 这样的东西,是在执行命令今后才干格局化代码,不能在实时编写时,就保持共同的风格。这也是 editorconfig 和 prettier 的最大的差异。
该东西装备都是迥然不同的,所以项目中用到的话,直接仿制就能够了。在项目的根目录下新建一个 .editorconfig 文件,内容如下:
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
max_line_length = 100
trim_trailing_whitespace = true
装备很好了解,键值对方式的装备项,[*]
表明对所有类型的文件都会使用这个装备,注意,需求下载了上面说到的扩展插件,才干在每次保存文件的时分主动格局化代码。
至此,一个风格共同的项目代码标准就完成了,最终能够增加一个脚原本执行代码 Lint:
prettier . --write && eslint . --fix && stylelint **/*.less
除了这些,也能够挑选运用 Husky 这样的东西,在提交代码前进行格局化。
综上所述,这儿运用了 ESLint + stylelint + prettier 来做代码检查,为了方便其他项目运用,能够利用 ESLint 的共享装备,将这些功用都会集到一个 npm 包中,像是上面说到的 eslint-config-standard 包。
能够创立一个如下结构的项目:
在 index.js 文件中导出 eslint 的装备:
module.exports = require('./eslintrc.js');
这样项目中的 ESLint 装备就能够直接承继该装备了。关于 stylelint、prettier 等则能够在项目中创立一个同名文件,文件中引入对应的装备文件即可,像这样:
module.exports = require('./styleintrc.js');
module.exports = require('./prettierrc.js');
别忘了最终在 package.json 文件中的 main 特点,增加 index.js 文件的途径。
今后无论是哪个项目都能够引证同一个 ESLint 以及其他的格局化装备规矩,到达所有项目,代码风格共同,减少初级的代码过错的目的。
尾声
ESLint 还有很多没有深化的地方,比方整个 ESLint 的架构,分为 cli-engine,init 等等模块,任何一个模块深化研究下去都足够学习很久。
最终总结一下:虽然结构的飞速发展,给开发带来了极大的便当,可是也不要忘掉隐藏在结构、标准背面的技术细节,这些细节才是拉开前端差距的要害。