开篇一张图
![](https://i-blog.csdnimg.cn/blog_migrate/24e486bec1d788fb67e13694088082dc.png)
前言
如何作出项目的亮点?
项目中遇到了什么问题?
解决问题的过程并且如何思考?
思考之后通过什么方式解决
最后这一个任务你学到了什么,给团队带来了什么价值,解决了哪些痛点。
就从我的题目说起,本篇文章告诉你针对定制代码规范和检查这个小需求如何做出亮点?看完本文后回顾上面提到的 4 点,感觉下。
本文目标
目标不是一次全部定出来的,在实践和调研过程中会添加一些
去掉项目中原有的
TSLint
,统一使用ESLint
,但是在ESLint
中加入TSLint
检测插件Prettier
支持的格式化规则全部使用Prettier
,不提供的使用ESLint
,以免冲突(个人认为Prettier
提供的格式化规则可以满足开发者)。代码保存时,支持自动
fix
,只对自己控制范围内的fix
,范围外的内容依靠开发者配置或vscode
自动配置。格式化和
ESLint
纳入项目级git
跟踪,所有开发者统一。除了上面的规范与检查实现,了解一些原理,比如
rules
原理?为什么Prettier
和ESLint
冲突?Prettier
原理?
ESLint
ESLint
的原理就是一款插件化的javascript代码静态检查工具,其核心是对代码解析得到的 AST (Abstract Syntax Tree 抽象语法树
)进行模式匹配,定位不符合约定规范的代码。ESLint
是完全插件化的。每一个规则都是一个插件并且可以在运行时添加更多的规则。
社区比较知名的代码规范
eslint-config-airbnb
(https://github.com/airbnb/javascript)
eslint-config-standard
(https://github.com/standard/eslint-config-standard)
eslint-config-alloy
(https://github.com/AlloyTeam/eslint-config-alloy)
如果想降低配置成本,可以直接接入上面的开源配置方案,好多开发者是继承它们的规范,然后在原有基础进行部分修改。我们目前选择的方式不是继承,挑选出了一些适合我们的 ESLint
规则(因为是在原有代码重新建立规范,防止改动过大)。
ESLint 集成
ESLint
使用并不复杂,简单说下 ESLint
的集成。
全局安装
yarn add eslint -D
初始化
eslint --init
这个时候在项目中会出现一个 .eslintrc.js
的文件。
eslint 自定义配置文件
module.exports = {
parser: {}, //定义ESLint的解析器
extends: [], // 定义文件继承的子规范
plugins: [], // 定义了该eslint文件所依赖的插件
env: {},
rules: {} // 规则
};
parser
定义 parser
的解析器,我们常用的解析器应该是 @typescript-eslint/parser
。
env
通过 env
配置需要启动的环境
env: {
es6: true, // 支持新的 ES6 全局变量,同时自动启用 ES6 语法支持
node: true, // 启动 node 环境
mocha: true,
},
extend
extend
提供的是 eslint
现有规则的一系列预设。
这里注意的是,“extends”除了可以引入推荐规则,还可以以文件形式引入其它的自定义规则,然后在这些自定义规则的基础上用rules去定义个别规则,从而覆盖掉”extends”中引入的规则。
{
"extends": [
"./node_modules/coding-standard/eslintDefaults.js",
// Override eslintDefaults.js
"./node_modules/coding-standard/.eslintrc-es6",
// Override .eslintrc-es6
"./node_modules/coding-standard/.eslintrc-jsx",
],
"rules": {
// Override any settings from the "parent" configuration
"eqeqeq": "warn"
}
}
除了在配置文件中指定规则外,还可以在代码中指定规则,代码文件内以注释配置的规则会覆盖配置文件里的规则,即优先级要更高。平时我们常用的就是 eslint-disable-next-line
。
忽略检查可以通过在项目目录下建立 .eslintignore
文件,并在其中配置忽略掉对哪些文件的检查。需要注意的是,不管你有没有在 .eslintignore
中进行配置,eslint
都会默认忽略掉对 /node_modules/**
的检查。也可以在 package.json
文件的 eslintIgnore
字段进行配置。
plugins
plugin
则提供了除预设之外的自定义规则,当你在 ESlint
的规则里找不到合适的的时候就可以借用插件来实现了
module.exports = {
parser: '@typescript-eslint/parser', // 解析器
extends: [
"./.eslintRules.js",
'plugin:prettier/recommended',
"prettier",// 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
], // 继承的规则
plugins: ['@typescript-eslint'], // 插件
ESLint 重要特性
rules
rules
对应的规则,小伙伴可以去官网查看。找到符合自己项目的规则。
ESLint 规则官网地址: http://eslint.cn/docs/rules/
注意:
在整理总结规则的时候有些是自动检测的规则,就可以不用总结进去了。
ESLint
规则的三种级别
"off"或者0,不启用这个规则
"warn"或者1,出现问题会有警告
"error"或者2,出现问题会报错
rules 工作原理
首先来看看 eslin
t源码中关于 rules
的编写。eslint
中的 rules
源码存在于 lib/rules
下。每一个 rules
都是一个 node
模块,用 module.exports
导出一个 meta
对象及一个create
函数。
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow unnecessary semicolons",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-extra-semi"
},
fixable: "code",
schema: [] // no options
},
create: function(context) {
return {
// callback functions
};
}
};
meta
代表了这条规则的 元数据
,如这条规则的类别,文档,可接收的参数 schema
等等。create
返回一个对象,其中定义了一些在 AST
遍历访问到对应节点需要执行的方法等等。函数接受一个 context
对象作为参数,里面包含了例如可以报告错误或者警告的 context.report()
、可以获取源代码的 context.getSourceCode()
等方法,可以简化规则的编写。
function checkLastSegment (node) {
// report problem for function if last code path segment is reachable
}
module.exports = {
meta: { ... },
create: function(context) {
// declare the state of the rule
return {
ReturnStatement: function(node) {
// 在AST从上向下遍历到ReturnStatement node 时执行
},
// 在AST 从下向上遍历到 function expression node 时执行:
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment,
onCodePathStart: function (codePath, node) {
// 在分析代码路径开始时执行
},
onCodePathEnd: function(codePath, node) {
// 在分析代码路径结束时执行
}
};
}
};
遍历 AST
的过程中会以“从上至下”再“从下至上”的顺序经过节点两次,selector
默认会在下行的过程中执行对应的访问函数,如果需要再上行的过程中执行,则需要添加:exit
。
TSLint 迁移到 ESLint 集成
背景
在这里会有读者问有现成的 TSLint
不用,为什么要迁移到 ESLint
中集成?
解答下:由于性能问题,TypeScript
官方决定全面采用 ESLint
,甚至把仓库(Repository)
作为测试平台,而 ESLint
的 TypeScript
解析器也成为独立项目,专注解决双方兼容性问题。
JavaScript
代码检测工具 ESLint
在 TypeScript
团队发布全面采用 ESLint
之后,发布typescript-eslint
项目,以集中解决TypeScript
与 ESLint
兼容性问题。而 ESLint
不再维护 typescript-eslint-parser
,也不会在 npm
上做任何发布。TypeScript
解析器转移至 Github
的 typescript-eslint/parser
。
官方都放弃了我们也没必要太坚持,而且通过 ESLint
加上 ts
插件都可以完成检查
集成过程
首先安装依赖:
yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
这两个依赖分别是:
@typescript-eslint/parser
:ESLint
的解析器,用于解析typescript
,从而检查和规范Typescript
代码。@typescript-eslint/eslint-plugin
:这是一个ESLint
插件,包含了各类定义好的检测Typescript
代码的规范。
安装好2
个依赖之后,修改之前创建的.eslintrc.js
文件,在该文件中加入 TSLint
配置。
module.exports = {
parser: '@typescript-eslint/parser', //定义ESLint的解析器
extends: ['plugin:@typescript-eslint/recommended'],//定义文件继承的子规范
plugins: ['@typescript-eslint'],//定义了该 eslint 文件所依赖的插件
env:{
browser: true,
node: true,
},
parserOptions: {
parser: '@typescript-eslint/parser', // 解析 .ts 文件
ecmaVersion: 2019,
sourceType: 'module',
ecmaFeatures: {
modules: true,
},
},
}
在
typescript
项目中必须执行解析器为@typescript-eslint/parser
,才能正确的检测和规范typescript
代码env
环境变量配置,形如console
属性只有在browser
环境下才会存在,如果没有设置支持browser
,那么可能报console is undefined
的错误。上面的配置中
extends
中定义了了文件继承的子规范,使用的typescript-eslint
默认的推荐规范parserOptions
解析器相关条件配置。
使用自定义的 typescript 规范
上面 extends
中 plugin:@typescript-eslint/recommended
使用的是插件默认推荐的 typescript
规范。但是会不会有同学不想使用推荐的规范,制定自己或者在推荐的规范中进行一些修改(比如一些老项目,加入规范,改动大,可能暂时忽略某些规范)
使用方式:如果想使用推荐,然后在推荐的基础上进行规范修改,可以直接在.eslintrc.js
文件中的rules对象中添加。
举个例子
rules:{
'@typescript-eslint/adjacent-overload-signatures': 2, // 要求成员重载是连续的
}
具体想修改那些自定义规范,可以去官网查看,这里给出官网地址。
TSLint rule 官网:https://palantir.github.io/tslint/rules/
Prettier
无法确定一个让所有人都满意的方案,就很难执行下去!
Prettier
中文的意思是漂亮的、美丽的,是一个流行的代码格式化的工具。
我们都知道 ESLint
本身就带有格式化检查的,我们为什么要是使用它?它有什么优点?使用它要注意那些问题?
优点
Perriter
官网列出几个特点:
An opinionated code formatter (译:固执己见的代码格式化程序)
Supports many languages(译:支持多种语言)
Integrates with most editors(译:与大多数编辑器集成)
Has few options(译:没有什么选择)
其中最核心的点是 opinionated
,google
翻译过来是固执己见的,在 Pertiter
中,就是说:你必须认同我的观点,按照我说的做。否则你就别用我,硬着头皮用就会处处不爽!
要解决的问题
使用
Prettier
如何避免与ESLint
和TSLint
的格式化冲突?Prettier
中不提供的格式化规则,ESLint
中提供的可以兼容一起使用吗?
带着两个问题继续往下看
集成
安装模块包
我们来看如何结合 ESLint
来使用。首先我们需要安装三个依赖:
yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
对每个依赖进行说明:
prettier
:Prettier
插件的核心代码。eslint-config-prettier
:解决ESLint
中的样式规范和Prettier
中样式规范的冲突,以Prettier
的样式规范为准,使ESLint
中的样式规范自动失效。eslint-plugin-prettier
:将prettier
作为ESLint
规范来使用。
创建 .prettierrc 文件
在项目的根目录下创建 .prettierrc.js
文件
module.exports = {
"printWidth": 120,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": true,
"arrowParens": "avoid",
"insertPragma": true,
"tabWidth": 4,
"useTabs": false
};
每个属性的含义可以去 Prettier
中查看。
修改 .eslintrc.js 文件,引入 Prettier
在 extends
中添加
extends:[
'./.eslintRules.js',
'plugin:prettier/recommended',
'prettier', // 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
// 这里可以加一些prettier不支持,eslint支持的格式化规则,但是个人认为prettier的格式化规则够用了
],
关于 Prettier 配置时特殊说明(重点看下)
网上好多在 ESLint
中加入的 Prettier
的文章,但是很少有讲清楚的,好多就是把配置文件写一下,然后很多小伙伴配置时候发现 Prettier
的格式化还是和 ESLint
中的格式化冲突,ctrl+s
保存的时候甚至出现来回切换格式的冲突,不知道小伙伴们遇到过这种情况没。
所以还是知道下原理,extends
中为什么那么写,格式冲突和顺序有什么关系没?
eslint-config-prettier
源码可以看出,它的代码很简单,它实际就是关闭了eslint
的所有格式化规则。
源码地址:https://github.com/prettier/eslint-config-prettier/tree/master/bin
我们
yarn add
插件的时候eslint-config-prettier
模块实际是为eslint-plugin-prettier
插件服务的,在eslint-plugin-prettier
的源码中调用了eslint-config-prettier
中相关的配置,然后执行插件中的代码。看
eslint-config-prettier
中recommended
部分 的源码,源码中也有使用到eslint-config-prettier
(把已有格式化配置关掉),然后自己制定了基础的recommended
版本,讲到这应该明白为什么在eslint-plugin-prettier
中有一段最重要的话,需要把它(eslint-config-prettier)放在所有格式化配置的后面。
前面的内容,通过这个插件对前面
ESLint
的配置进行重置。如果想使用一些 Prettier
中不支持的格式化配置,我们把eslint中的格式化加在他们后面写了,也不会有冲突。
prettier/@typescript-eslint
是用来忽略typescript
中的格式化配置。
这里关于防止 Prettier
和 ESLint
冲突,画了一张
另外
eslint-plugin-prettier
和eslint-config-prettier
的源码都不是很复杂,感兴趣的同学可以去看看,下面是源码地址:
eslint-plugin-prettier 官网(https://github.com/prettier/eslint-plugin-prettier/)
eslint-config-prettier 官网(https://github.com/prettier/eslint-config-prettier/tree/master/bin)
Prettier 原理简单说明
不管你写的代码是个什么鬼样子,Prettier
会去掉你代码里的所有样式风格,然后用统一固定的格式重新输出。输出时基本上只考虑一个参数,就是 line length
。
例如你写的这行代码:
foo(arg1, arg2, arg3, arg4);
一行装得下这么多代码,所以就不需要改。
如果你写了下面代码:
foo(reallyLongArg(), omgSoManyParameters(),IShouldRefactorThis(), isThereSeriouslyAnotherOne());
太长了,Prettier
就会重新改成这样输出:
foo(
reallyLongArg(),
omgSoManyParameters(),
IShouldRefactorThis(),
isThereSeriouslyAnotherOne()
);
咱们再仔细探究一下这个过程。不管你之前写的代码是什么样,首先必须符合语法规范。Prettier
先把你的代码转换成一种中间状态,叫 AST(Abstract Syntax Tree)
。
用 Prettier
提供的 Playground(https://prettier.io/playground/) 更直观一些:
![](https://i-blog.csdnimg.cn/blog_migrate/840d7c73cbe5596bfc5d6aaf825f78a8.png)
上图左侧是手写代码,中间是 AST(去掉了任何代码风格),右侧是重新输出的结果。
Prettier 就是在这个 AST 上重新按照自己的风格输出代码。
这是 Prettier
也搞懂后的最终配置
module.exports = {
parser: '@typescript-eslint/parser', // 解析器
extends: [
"./.eslintRules.js",
'plugin:prettier/recommended',
"prettier",// 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
], // 继承的规则
plugins: ['@typescript-eslint'], // 插件
env: {
es6: true,
node: true,
mocha: true,
},
parserOptions: {
parser: '@typescript-eslint/parser', // 解析 .ts 文件
ecmaVersion: 2019,
sourceType: 'module',
ecmaFeatures: {
modules: true,
},
},
rules: {
}, // 规则
};
注意 eslintRules
是 base
规则,单独提了出来。
VSCode 自动 fix 配置
因为终极目标是我们在使用eslint
格式化并且检查我们自己编写的 javascript
和 typescript
。除了我们要求的代码,如果开发者添加别的代码也应该进行格式化,除非忽略的文件,开发者本地安装什么插件我们管不到,在用户级别配置中 setting.json
中
{
// 此模式不能使用skipFiles特性,暂时关闭,需要调试其他进程时请在本地打开
// "debug.node.autoAttach": "on",
"editor.formatOnSave": true,
"debug.openDebug": "openOnFirstSessionStart",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[javascript]": {
"editor.formatOnSave": false
},
"[typescript]": {
"editor.formatOnSave": false
}
}
lint 校验代码与 与 --fix 参数设置
上面的配置都做完了,如果不是一个新项目是原有的老项目,可能需要做一些改动喽!我们先 Fix一下。
npm 脚本中需要有这样的配置
"scripts": {
"lint": "eslint src",
"lint:create": "eslint --init"
}
执行命令npx run lint
会出现如下的错误:
1:7 error 'lint' is assigned a value but never used no-unused-vars
1:14 error Strings must use doublequote quotes
1:22 error Missing semicolon semi
3 problems (3 errors, 0 warnings)
2 errors, 0 warnings potentially fixable with the `--fix` option.
这里报了三个错误,分别是:
index.js
第1行第7个字符,报错编码规则为no-unused-vars
:变量lint
只定义了,但是未使用;index.js
第1行第14个字符,报错编码规则为quotes
:编码规范字符串只能使用双引号,这里却使用了单引号;index.js
第1行第22个字符,报错编码规则为semi
:编码规范每行代码结尾必须加分号,这里没有加分号。
设置 --fix
参数
说明:这里给 "lint": "eslint src --fix"
, 加上 --fix
参数,是 ESLint
提供的自动修复基础错误的功能。
此时运行 npm run lint
会看到少了两条报错信息,并不是说编码规范变了,而是 Eslint 自动修复了基础错误,打开 index.js 文件,可看到字符串自动变成了双引号,并且代码末尾也加上了分号。可惜的是 --fix
只能修复基础的不影响代码逻辑的错误,像 no-unused-vars
这种错误只能手动修改。
总结
本文主要对开篇那张图片中的本地代码检查部分进行了详细讲解,从实践到原理,另外小伙伴们也可以想下我开篇提到的如何做出亮点,希望有所帮助,最后快去制定一个属于自己项目的规范与检查吧!
如果开篇图中后面 CI/CD
部分感兴趣的可以找我讨论,后面会单独写一篇 CI/CD
文章,不然篇幅太长了,欢迎在看转发。
参考文章
Prettier 看这一篇就行了
使用 ESLint+Prettier 规范 React+Typescript 项目
eslint-plugin-prettier 官网
Using ESLint and Prettier in a TypeScript Project
十分钟了解eslint配置 && 编写自定义eslint规则
关于奇舞周刊
《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。