背景
在以往的项目中,我们定义了很多规范,用得最多的就是eslint,很多人在配置eslint时大部分都是从以前的项目里或者网上复制一份下来,很少人去关心eslint的每行配置是干嘛的,能起到什么作用,为什么是这样的配置。有时候纠结,为啥我的编辑器IDE的代码格式校验不起效果,为啥明明我格式了,到其他人的IDE里就报错。追根溯源,问题就是对ESLint的了解不够彻底。最笨最管用的方式就是把api文档一遍遍的看ESLint - 插件化的 JavaScript 代码检测工具
什么是eslint?
ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。通俗的讲就是让代码风格统一(更在意js代码质量)。
历史
在了解eslint之前,我们可以先认识两个人,Douglas Crockford(道格拉斯 克罗克福德)是Web开发领域最知名的技术权威之一,ECMA JavaScript2.0标准化委员会委员。我们一般叫他道爷或者老道。另一位是Brendan Eich( 布兰登·艾奇),被称为JavaScript之父,是我们每一位前端开发者应该供奉起来的鼻祖,没有他,我们现在可能还被称作为网页美工。
老布发明了JavaScript,和其他语言一样,总得有个约束规范吧,不然一套代码里,单引号,双引号,函数参数乱放,总有一些代码洁癖的人看不下去,道爷就是其中一位,所以他最早提出了JavaScript Lint工具JSlint,.https://www.jslint.com/个在线的体验工具。然后老布和老道就商业互吹了一下,老道说js牛逼,还写了一本书《JavaScript: The Good Parts》翻译成中文版《JavaScript语言精粹》,还强调了“JavaScript:世界上最被误解的语言”,所以我之前说“js是一门最让人学不懂的语言”是有依据的。礼尚往来,老布总得客套一下,夸老道是js界的大宗师(yoda)。
JSlint不得不说确实帮助了广大的JS开发程序员,但是道爷就是道爷,JSlint不允许自定义规范,而且对外界的需求道爷不做丝毫妥协,觉得自己的代码规范就是标准,果然站在巅峰的人物都有那么点霸气和小任性。很能理解道爷的想法,如果可定义规则那就不是规则了。
可在IT行业都是一群谁都不服谁的人,一名Anton Kovalyov的前端程序员在JSlint的基础上做了修改,添加了可配置规则,允许自定义自己的代码规范,JSHint立刻出现在大家的视野。而且JSHint是建立的社区的力量上,所以更加友好和完善,甚至被很多IDE集成。
而我们的主角ESlint 是由Nicholas C. Zakas(尼古拉斯·泽卡斯)在2013年开始开发的。它的初衷就是为了能让开发者能自定义自己的linting rules,并且提供了一套相当完善的插件机制,可以自由的扩展,动态加载配置规则,感觉和JSHint的初衷很像。但绝不是重新造轮子,据闻是实际使用JSHint时没有得到JSHint团队的回应,然后就在jshint优势上添加了更完善的代码风格检查,自定义插件扩展功能,不得不承认大神就是大神,一言不合就自己干。
从历史进程上,似乎lint越来越完善,但三者各有各的优势,只是eslint更容易被多少人采用。
优点和缺点
JSLint
优点
-
所有配置都是固定的,开箱即用,道爷的规则就是规则。
不足
-
规范严格,不能添加,不能修改,不能禁用
-
扩展性差
-
虽然提示报错,但不能提示是什么规则报错,就问你慌不慌
JSHint
优点
-
可以修改规则,和禁用
-
支持配置文件,方便使用
-
支持了一些常用类库
-
支持了基本的ES6
不足
-
不支持自定义规则,只能在原有基础上禁用,修改
-
和jslint一样,不能追踪问题
ESLint
优点
-
包含了JSLint和JSHint的规则,
-
易于迁移,(你要火起来,得向下兼容吧)
-
可以区分警告和错误,禁用等级
-
支持插件扩展,
-
可以自定义规则
-
可以根据错误定位到对应的规则
-
支持ES6
-
唯一一个支持JSX的工具
-
(从人性和我国国情来看,用eslint不解释)
不足
-
因为太灵活,很多配置需要自己自定义
-
速度慢,受规则,插件影响
ESlint校验原理
Linter 是 eslint 最核心的类,它提供了这几个 api:
verify // 检查
verifyAndFix // 检查并修复
getSourceCode // 获取 AST
defineParser // 定义 Parser
defineRule // 定义 Rule
getRules // 获取所有的 Rule
preprocess预处理器,先检查文件格式,找到对应的解析器
通过解析器parser(如:@babel/eslint-parser) 解析成AST抽象树AST explorer
调用rule函数对ast抽象树进行处理,反馈出问题
再通过verifyAndFix格式化文件
更多实现原理建议查看好奇:eslint的工作原理
ESlint配置.eslintrc.js
直接建议阅读ESLint - 插件化的 JavaScript 代码检测工具,我们只针对疑点和问题做更细致的讲解。
env
一个环境定义了一组预定义的全局变量。通常我们会这样配置
env: {
browser: true,
es6: true,
node: true,
},
从env的字面意思看,是控制全局变量的,也就是你的整套代码是打算在哪里允许,会采用什么方式方法。比如我们把browser: false;你会发现在你的代码中就会提示alert不可使用,必须要在前面加上window才能管用。所以要避免类似这个情况,我们通常会用到以上的配置项。(当然可以根据情况减少规则配置,速度会快)
plugins,extends和parser的区别
eslint配置项里最难理解的可能就是这三个,先直接理解三者意思
plugins 表示添加的插件,可以省略 eslint-plugin- 前缀。如eslint-plugin-react,eslint-plugin-;
extends表示规则继承,属性值可以省略包名的前缀 eslint-config-。如eslint-config-ali;
parser表示解析器,作用是将我们写的代码转换为 ESTree,ESLint 会对 ESTree 进行校验;
Eslint本身就具备基础的lint规则校验能力,但是随着市场的进步,不断出现了很多技术栈,必然react,Vue,angular,typescript等等,这个时候,ESlint基础规则就不能满足了,所以市场就出现了校验相应技术栈的插件,就比如eslint-plugin-react。我们可以配置:
plugins: ["react"], //eslint-plugin-react的简写
这样就引入了插件eslint-plugin-react,但是光引用不行啊,就好比你引用了一个js文件,得触发里面的方法,才生效吧,所以这个时候extends就派上用场了。我们不妨先看一下依赖包eslint-plugin-react导出的啥,一下是代码片段:
module.exports = {
deprecatedRules,
rules: allRules,
configs: {
recommended: {
plugins: [
'eslint-plugins-react',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
rules: {
'react/display-name': 2,
'react/jsx-key': 2,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-target-blank': 2,
'react/jsx-no-undef': 2,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-children-prop': 2,
'react/no-danger-with-children': 2,
'react/no-deprecated': 2,
'react/no-direct-mutation-state': 2,
'react/no-find-dom-node': 2,
'react/no-is-mounted': 2,
'react/no-render-return-value': 2,
'react/no-string-refs': 2,
'react/no-unescaped-entities': 2,
'react/no-unknown-property': 2,
'react/no-unsafe': 0,
'react/prop-types': 2,
'react/react-in-jsx-scope': 2,
'react/require-render-return': 2,
},
},
all: {
plugins: [
'react',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
rules: activeRulesConfig,
},
'jsx-runtime': {
plugins: [
'react',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxPragma: null, // for @typescript/eslint-parser
},
rules: {
'react/react-in-jsx-scope': 0,
'react/jsx-uses-react': 0,
},
},
},
};
从eslint-plugin-react插件的出口中,我们看见提供了“recommended”,“all”,“jsx-runtime”三个用处的,所以我们习惯性的用“recommended”(推荐)嘛,
extends: ["plugin:react/recommended"],//eslint-plugin-react/recommended的简写
不过有时候,这个推荐还是不能满足我们的要求,我们能不能采用一些大厂的,比如腾讯的eslint-config-alloy,阿里的eslint-config-ali已经流行的Airbnb(google)等等。我们就采用eslint-config-ali(毕竟UI组件都是用antd了)。eslint-config-ali依赖包的index.js代码片段如下
module.exports = {
extends: [
'./rules/base/best-practices',
'./rules/base/possible-errors',
'./rules/base/style',
'./rules/base/variables',
'./rules/base/es6',
'./rules/base/strict',
'./rules/imports',
].map(require.resolve),
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
globalReturn: false,
impliedStrict: true,
jsx: true,
},
},
root: true,
};
eslint-config-ali依赖包的react.js代码片段如下
module.exports = {
extends: [
'./index',
'./rules/react',
].map(require.resolve),
};
我们才尝试打开看'./rules/react';(是不是我们在.eslintrc.js连很多配置不要再设置了,比如plugins)
module.exports = {
plugins: ['react', 'react-hooks'],
rules: {
// 防止 React 组件定义中缺少 displayName
'react/display-name': ['off', { ignoreTranspilerName: false }],
//……………………
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.json'],
},
},
react: {
pragma: 'React',
version: 'detect',
},
},
};
我们引入了ali的规则配置,这个时候如果还不能满足需求,就只能设置rules了,其实也只是覆盖之前config里的规则而且,只是rules优先级更高。
rules: {
quotes: [1, "double"], //引号类型 `` "" ''
semi: 2, // 语句强制分号结尾
"prefer-const": 0,
"prefer-promise-reject-errors": 0,
eqeqeq: "off",
}
总结plugins,extends的理解,当eslint基础功能不足通过解析器parser一个新的语法糖时,我们就需要制定新的规则,所以各种eslint-plugins-xxxx就出来了。插件只是具备了解析语法糖规则功能,具体的规则还是要一个个rules配置,为了减轻压力和重复工,就出现了eslint-config-xxxx,就是extends。如果eslint-config-xxxx还不能满足自己的需求时,可以用rules在针对性的覆盖一下,当然,自己可以制作出一个eslint-config-my啊,就不用再细化rules了。
Vscode eslint配置不生效问题
-
因为项目中我们使用了.eslintrc.js ,要识别这个文件必须要给Vscode安装插件,不然依旧不起效果
-
首先保证依赖包是否正确安装,简单的测试eslint是否生效,可以删除js代码一行后面的;号,如果有警告就表示eslint生效了。
-
检查.eslintrc配置是否正确,编辑器会在"输出"板块显示
-
配置好eslint依旧不生效,可以重启IDE。等待eslint加载完成,比较校验有些慢(尝试把窗口文件关闭掉)
-
vscode插件编辑器自动保存设置是否开启,这个问题网上自行查找(平时不要过多的修改配置,默认就挺好)
ESlint和Prettier
在日常开发中我们发现明明大家都是eslint的开发的,为啥到另一个小伙伴编辑器上又有爆红提醒。这里可能就是Prettier的配置不一样了。
Eslint严格来说是对js代码质量兼部分风格的检查,更加侧重js代码质量,防止js语法可能存在的隐藏错误。比如不允许重复定义变量,Switch必须有default结尾等等。但是一行什么时候换行,属性值是一行写还是换行写等等规范更加在意代码的美化,所以这个时候Prettier就起到关键性作用。
-
代码质量规则 (code-quality rules)
no-unused-vars
no-extra-bind
no-implicit-globals
prefer-promise-reject-errors
...
-
代码风格规则 (code-formatting rules)
max-len
no-mixed-spaces-and-tabs
keyword-spacing
comma-style
...
之前一直推荐小伙伴在VScode中,装插件Prettier Formatter,这样既可以美化js,还可以美化css,html等等。
我们希望eslint用来提升代码质量+Prettier来美化代码规范。确实需要如此,因为prettier是以插件形式存在,必然存在版本,或者本地编辑器配置了其他美化的规则而导致即便我们感觉我们都一样,依旧会爆红的原因,所以我们更希望把Prettier添加到项目中。这个时候就推荐eslint-config-prettier ,eslint-plugin-prettier
// prettier 一定要是最后一个,才能确保覆盖
extends: ["eslint-config-ali/react","prettier"],
刚刚也讲了很多eslint插件,如何eslint-config-ali里面也包含了很多Prettier中的代码风格规范,所以两者必然会有冲突。如果prettier后面会完全覆盖eslint本身的美化规则,这个时候你按照eslint规则是对的,prettier规则就报错了,让你怎么改都是爆红,两边不讨好,所以这个时候我们可以把prettier规则添加的eslint规则里
extends: ["eslint-config-ali/react", "prettier"],
rules: {
"prettier/prettier": "error"
}
用官网推荐的配置直接包含了这个"prettier/prettier": "error" 规则,所以可以这样简写写
extends: ["ali/react","plugin:prettier/recommended"]
Eslint和.editorconfig
在项目中我们发现.editorconfig也是设置代码规范的,.editorconfig的重心是针对编辑器IDE本身,不同的开发⼈员,不同的编辑器,有不同的默认编码风格,⽽EditorConfig就是⽤来协同团队开发⼈员之间的代码的风格及样式规范化的⼀个⼯具。比如eslint 我们要求首行缩进是2个空格,而编辑器默认是4个空格,这样每次编写后,就会有红色警告,要格式化一下才可以。如果在编写时编辑器默认配置大家和eslint一致,就会更加更好。
之所以要特意把editorconfig提出来讲是因为我们发现window 系统vscode换行 默认是CRLF,MAC电脑默认是LF。
识别.editorconfig 需要安装插件,这样我们在新建文件时,大家都是统一的默认格式了
最佳实践
从上文中,我们就理解了所有的eslint功能,我们就针对目前react项目做一个最佳实战配置:
公司一直用阿里系列组件,规则我们也陆续采用
module.exports = {
root: true,
env: {
browser: true,
es6: true,
node: true,
},
extends: ["ali/react", "plugin:prettier/recommended"],
rules: {
quotes: [1, "double"], // 引号类型 `` "" ''
eqeqeq: "off", // 强制===关闭
radix: "off", // 使用parseInt无需配置第二个参数
"padded-blocks": "off",
"guard-for-in": "off",
"max-len": "off", // 每行最大长度
"no-console": "off", // 允许打印日志
"no-param-reassign": "off", // 函数参数允许重新设值
"no-nested-ternary": "off", // 允许设值三元表达式
"no-unused-vars": "off", // 未定义警告关闭
"no-useless-constructor": "off", // 允许空的构造函数
"no-case-declarations": "off",
"prefer-const": "off", // 不强制不变的变量都用const
"prefer-promise-reject-errors": "off", // 无需强制配置reject
"react/prop-types": "off", // 无需对props是否存在值校验
"react/no-array-index-key": "off", // 允许使用数组的index作为key的索引
"react/no-danger": "off", // 允许使用dangerouslySetInnerHTML赋值
"react/jsx-uses-react": "off", // 允许引用未使用的react
"react/no-unused-state": "off",
"react/no-typos": "off",
"react/no-access-state-in-setstate": "off",
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off",
"import/newline-after-import": "off",
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
settings: {
"import/ignore": ["node_modules"],
},
};
看到上面的配置,可以看到我们只引入了一个extends,没有引用plugins是因为在eslint-config-ali已经引用了eslint-plugins-react,eslint-plugins-react-hooks。从覆盖rules中我们也发现,我们大部分都是关闭行为其实挺惭愧的,很多我们纯粹图方便,没有做严格控制。
举一反三,如果我们要做一个typescript的校验规则,只要引入解析typescript的插件就可以了。至于如何自己开发一个插件,可以参考官网https://eslint.bootcss.com/docs/developer-guide/architecture 开发指南。