小插曲:前一篇《浅读Vue3代码10万行,总结出30个代码规范》,有同学提问:"你写的规范,有没有可以直接拿来用的配置?"。再加上最近新建项目,有同学也在提问如何为新项目配置代码规范。引申出来的思考:如何为项目配置代码规范,以及如何定制开发 my eslint插件?
要回答这两个问题,先得抓大放小,对代码规范配置有概览性认识,然后再从底层对eslint实现原理深入剖析。因此,我将分两篇来介绍如何为项目配置代码规范,以及开发一个自己的eslint插件。
本篇目的:当新建项目或者修改已有项目时,对涉及代码规范的eslint、prettier、editorconfig等工具的了解,能够达到"一览众山小"的程度。
代码规范往期介绍:
- 浅读Vue3代码10万行,总结出30个代码规范
- 200+收藏的Vue3规范,如何配置eslint、prettier、editorconfig
- Vue3黑神话:悟空版 eslint: eslint-plugin-wukong
- 这样的Git规范,Leader看了都说好!
editorconfig、prettier、eslint的关系
当查看vue/core、element-plus、antd-design-vue等开源项目,必不可少的几个代码规范文件:
- .eslintrc.js,或者.eslintrc.json,或者eslint.config.js
- .eslintignore
- .prettierrc
- .prettierignore
- .editorconfig
部分包含的规范文件:
- .stylelintrc
- .stylelintrc.json
- commitlint.config.js
项目中经常看到好几个以rc
作为后缀的文件,rc
后缀有什么含义?rc
是run commands
的缩写,通俗点说,就是可执行命令的配置文件。例如,.eslintrc.js作为eslint工具的配置文件,.prettierrc作为prettier工具的配置文件。
eslint配置中,文件类型.js、.json、config.js无关紧要,其作用完全一样。
上述罗列的文件,stylelint用于css、scss、less等样式代码规范,commitlint用于提交代码时执行eslint检查。而eslint、prettier、editorconfig等经常在项目中看到的配置,各自的作用,以及三者之间有何关系?
工具 | 作用范围 | 配置文件 | 特点 | 使用方式 |
---|---|---|---|---|
editorconfig | IDE,如vscode、pycharm、github | .editorconfig | 跨平台,适用于不同IDE,几乎支持所有类型文件 | vscode IDE默认支持editorconfig,无需插件,添加配置即可 |
prettier | JSX、Vue、Angular、TS、CSS、HTML | .prettierrc | 格式化js、css、html代码,保存、提交时自动格式化 | vscode插件:Prettier - Code formatter |
eslint | ECMAScript/JavaScript | eslintrc.js | 检查、修复js代码问题,保存时、提交时修复 | eslint npm plugin、vscode插件:eslint |
上述表格对比了三者之间作用范围、自身特点。总结为:
- editorconfig适用于IDE工具,支持所有类型文件格式化, vscode默认集成了editorconfig,只需添加.editorconfig文件即可生效;
- prettier适用于js、css、html、markdown以及衍生语言代码格式化,除了配置,还需要安装vscode插件Prettier - Code formatter;
- eslint适用于js以及衍生语言代码格式化和代码问题修复,除了配置,还需安装vscode插件eslint;
prettier
prettier有两种使用方式:命令模式、IDE模式。所谓命令模式即通过命令行方式执行代码格式化,而IDE模式即在IDE工具中通过快捷键或者保存时自动格式化。
命令模式
执行npm安装执行,安装prettier:
npm install --save-dev --save-exact prettier
在项目中添加.prettierrc、.prettierignore文件,以下配置来自于ant-design-vue
的.prettierrc文件, 配置内容比较简单,针对每一个格式化规则设置相应参数。
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"proseWrap": "never",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
}
]
}
选项说明:
- trailingComma:设置逗号结尾;
- endOfLine:设置行以
\n
结尾; - printWidth:设置行最大字符长度为100;
- proseWrap:设置markdown、text等文件换行行为;
- arrowParens:设置箭头函数的括号是否保留,
avoid
表示省略括号,例如x => x
; - htmlWhitespaceSensitivity:用于指定HTML文件中空格处理的敏感度,确定Prettier如何解析和格式化HTML代码中的空格和换行。
- overrides:为特定文件设置定制化格式,prettier默认会根据文件名自动推动使用哪种parser,其选项有babel、typescript、css、scss、json、html、markdown等。代码中为
.prettierrc
文件设置parser
为json格式,因此prettier在处理.prettiercc文件时,会将其作为json文件处理。
prettier的命名模式可结合Git Hooks
,在代码提交时自动执行文件格式化,可以在package.json文件中添加如下指令实现:
{
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
}
}
IDE模式
prettier与IDE结合,可通过快捷键或者保存时自动格式化代码,前提是需要为IDE安装prettier插件。VSCode可在插件市场搜索Prettier - Code formatter
。
设置保存时自动格式化需要在项目的.vscode/settings.json中添加配置项:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
通过快捷键格式化代码,执行快捷键CMD/CTRL + Shift + P
,如果有选中文件中的部分代码,则仅格式化选中部分。
prettier、eslint、stylelint格式化冲突
eslint、stylelint不但提供了代码检查功能,还提供了代码风格检查,而代码风格检查部分与prettier的配置项可能冲突,如何解决冲突问题?
专业的事交给专业的人做,最好是prettier负责代码风格格式化,而eslint、stylelint仅负责代码质量规则。
prettier提供了eslint-plugin-prettier
插件,用于关闭eslint中代码风格格式化,因此代码风格将按照prettier设置格式化。同理,prettier提供stylelint-prettier
插件,用于关闭css、html等风格检查。
如何在eslint中配置prettier插件? 可在.eslintrc.*文件中添加如下配置:
{
"extends": [
"some-other-config-you-use",
"prettier"
]
}
prettier与editorconfig冲突
问题:当prettier、editorconfig同时设置行字符串最大长度,那代码格式化按哪一个配置执行?
// prettier配置
"printWidth": 100,
// editorconfig配置
max_line_length = 80
prettier对editorconfig也提供了适配能力,当项目根目录配置有.editorconfig
,prettier会自动将.editorconfig中配置的属性映射为prettier属性,如果prettier提供有相同属性则优先使用prettier配置。
下面是prettier官方给的一段.editorconfig配置
,chartset
和insert_final_newline
配置prettier无映射,则直接保留;而end_of_line
、indent_size
等配置prettier有映射,则将被映射至prettier属性。
# Stop the editor from looking for .editorconfig files in the parent directories # root = true
[*] # Non-configurable Prettier behaviors
charset = utf-8
insert_final_newline = true
# Caveat: Prettier won’t trim trailing whitespace inside template strings, but your editor might.
# trim_trailing_whitespace = true
# Configurable Prettier behaviors
# (change these if your Prettier config differs)
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 80
eslint
一个新项目增加eslint配置,可通过官方提供的指令将其引入到项目中:
npm init @eslint/config@latest
执行后项目根目录会添加eslint.config.js
文件,内容如下:
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
export default [
{files: ["**/*.{js,mjs,cjs,ts,vue}"]},
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/essential"],
{files: ["**/*.vue"], languageOptions: {parserOptions: {parser: tseslint.parser}}},
];
配置为数组结构,每一项为Configuration Objects
类型,object包含的属性有:name、files、rules、ignores、languageOptions、linterOptions、processor、plugins、settings
。
数组结构中,后一项配置继承或覆盖前一项配置,类似于merge并覆盖的效果,例如第一项的files适用于后续配置项。
Configuration Object属性
name
检查项标识,如下图所示,@typescript-eslint/no-unused-vars
表示插件@typescript-eslint
下名称为no-unused-vars的检查项错误。
files
规则支持的文件类型,["**/*.{js,mjs,cjs,ts,vue}"]
表示适用于js、cjs、ts、vue文件;
ignores
和files相反,表示哪些文件排除检查,也可以在.eslintignore文件中添加排除项;
rules
rules添加规则配置项,semi
为配置规则名称,value error
为规则参数,其值包含off、error、warn。如下配置表示需要提供分号,否则报eslint错误。
{
rules: {
semi: "error"
}
}
可以为规则提供额外的参数,此时,value为数组类型。例如,如果semi规则不允许分号,可配置为["error", "never"]
,其中never表示不允许分号结束,否则报错。
{
rules: {
semi: ["error", "never"]
}
}
languageOptions
languageOptions配置指定如何检查错误
,可理解为错误检查的驱动器,其核心为parser选项。
思考问题:eslint是如何检查类似于no-unused-vars
这样的错误? 要识别代码错误,需要先将代码字符串转换为语法树AST,再通过语法树来判断代码中的变量是否有被使用。而语法树的生成正是通过parser
选项实现。
parse方法转换js代码生成ESTree语法树,而parseForESLint用于Vue、TS等语法树生成。parser类型定义如下:
type ParserModule = {
/**
* Parses the given text into an ESTree AST
*/
parse(text: string, options?: ParserOptions): TSESTree.Program;
} | {
/**
* Parses the given text into an AST
*/
parseForESLint(text: string, options?: ParserOptions): ParseResult;
};
目前常用的parser模块有:
- espree
- Esprima
- @babel/eslint-parser
- @typescript-eslint/parser
espree为默认parser,支持将js转换为抽象语法树,执行如下代码:
import * as espree from "espree"
const ast = espree.parse('let foo = "bar"', { ecmaVersion: 6 })
console.log(ast)
运行结果如中,type表示类型, 如变量定义VariableDeclarator
;kind标识变量类型,如let
;id为变量名标识,其名称为foo
。
再回顾eslint.config.js文件中代码片段, 其中有:
import tseslint from "typescript-eslint"
{files: ["**/*.vue"], languageOptions: {parserOptions: {parser: tseslint.parser}}}
这里的parser值为tseslint.parser
,使用的即是typescript-eslint
提供的parser方法,实现了parseForESLint
方法,用于处理typescript类型代码。代码定义如下:
function parse(
code: ts.SourceFile | string,
options?: ParserOptions,
): ParseForESLintResult['ast'] {
return parseForESLint(code, options).ast;
}
function parseForESLint(
code: ts.SourceFile | string,
parserOptions?: ParserOptions | null,
): ParseForESLintResult {
...
}
export { parse, parseForESLint, ParserOptions };
processor
由于需要代码检测的文件类型很多,如.js、.ts、.vue、.tsx、markdown等,processor的作用是提取不同类型文件中代码模块,并将其转换为Lint能识别的Message
格式。例如插件@eslint/markdown
,支持从markdown文件中提取js代码模块。一个processor的类型格式如下:
interface LooseProcessorModule {
/**
* Information about the processor to uniquely identify it when serializing.
*/
meta?: {
[K in keyof ProcessorMeta]?: ProcessorMeta[K] | undefined;
};
/**
* The function to extract code blocks.
*/
preprocess?: (text: string, filename: string) => any;
/**
* The function to merge messages.
*/
postprocess?: (messagesList: any, filename: string) => any;
/**
* If `true` then it means the processor supports autofix.
*/
supportsAutofix?: boolean | undefined;
}
如何使用三方配置
要使用三方配置,有两种方式引入:直接引入三方的rules配置,或者通过插件形式引入部分配置。
引入三方的rules配置
回顾eslint.config.js代码片段,两段recommended
,一段flat/essential
,第一项没有解构,第二、三项有解构。
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/essential"],
pluginJs.configs.recommended
为eslint默认推荐的rules配置, 其类型定义如下:
readonly configs: {
readonly recommended: { readonly rules: Readonly<Linter.RulesRecord> };
};
由于其本身为Record类型,而非数组类型,因此不需要解构,部分源代码如下:
module.exports = Object.freeze({
rules: Object.freeze({
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
...
})
});
tseslint.configs.recommended
、pluginVue.configs["flat/essential"]
的类型为TSESLint.FlatConfig.ConfigArray
数组,因此需要解构。查看tseslint.configs.recommended
源代码, 返回结果为数组类型,而每一项则为 Configuration Object
类型。
/**
* Recommended rules for code correctness that you can drop in without additional configuration.
* @see {@link https://typescript-eslint.io/users/configs#recommended}
*/
export default (
plugin: FlatConfig.Plugin,
parser: FlatConfig.Parser,
): FlatConfig.ConfigArray => [
baseConfig(plugin, parser),
eslintRecommendedConfig(plugin, parser),
{
name: 'typescript-eslint/recommended',
rules: {
'@typescript-eslint/ban-ts-comment': 'error',
'no-array-constructor': 'off',
...
},
},
];
Plugin形式引入
ESLint支持插件形式扩展规则,命名形式为eslint-plugin-XXX
。在eslint.config.js中可通过配置引入三方插件,并支持部分规则引入。 假设现在有example
插件,我们想引入其下rule名称为rule1
的规则,可通过如下配置引入:
// eslint.config.js
import example from "eslint-plugin-example";
export default [
{
plugins: {
example
},
rules: {
"example/rule1": "warn"
}
}
];
其中plugins选项配置需要引入的插件,而rules选项可通过插件名/rule名称
形式引入插件下的具体规则。
总结
相信通过对editorconfig、prettier、eslint的介绍,当面临一个新项目,需要添加代码规范配置时,我们能够很从容的完成这三者的相关配置,并且知道其核心配置的作用。
editorconfig、prettier、eslint没有谁最重要的说法,而是相辅相成的关系。editorconfig通用型更强,能对所有文件设置基础的代码风格。prettier专注于前端代码风格,但也可以通过plugin方式扩展其他语言,例如支持java的prettier-plugin-java
、插件、支持SQL的prettier-plugin-sql
插件。eslint侧重于代码质量检查,也是我们在项目中使用最频繁的工具。
开发人员不需要过于关注各个工具之间的规则冲突,prettier已为开发人员提供了完善的解决方案,自动映射editorconfig配置,通过eslint-plugin-prettier、stylelint-prettier插件解决javascript、html、css风格冲突。
我是
前端下饭菜
,原创不易,各位看官动动手,帮忙关注、点赞、收藏、评轮!