前言
在项目中常常会搭配 EditorConfig、Prettier、ESLint 统一代码规范,但对为何需要搭配使用、三者的作用范围和工作原理只有模糊的认识。为了解答心中的疑惑,我开始着手探索最终整理出这篇文章。通过这篇文章,你能:
- 理清三种规范化工具为何而生、如何工作、如何配置和集成、为何需要搭配使用
- 了解 Git Hooks、Husky、lint-staged 的作用、原理和使用方式
EditorConfig:统一代码编辑器编码风格(Editor Coding Style)
EditorConfig 应对的问题
代码编辑器自身维护一份配置,往往可通过 Preferences Settings 修改。配置中包含编码风格设置,比如「设定缩进使用 Tab 还是 Space」,「一个 Tab 占用多少列」,「是否在文件末尾显示空行」等等。
代码编辑器的配置信息独立维护不对外共享。使用不同编辑器打开同一份文件,如果编辑器配置不统一,显示效果和输入内容很有可能不一致。
比如,一个 Tab 占用两列的 Sublime,一个 Tab 占用四列的 VSCode ,打开同一份文件显示效果不同
比如,一个缩进为 Tab 的 Sublime,一个缩进为 2 个 Space 的 VSCode,编辑同一份文件,输入内容不同
单个编辑器独立操作只是存在隐患,在多编辑器或跨编辑器的情况下,隐患就容易升级为问题,最终导致代码处于混杂状态。
EditorConfig 如何工作
我们在项目中共享 .editorconfig 文件,该文件可被 EditorConfig 解析,由 EditorConfig 告知编辑器覆盖默认配置。
一些编辑器内置支持 EditorConfig,比如 WebStorm、Github;而一些编辑器需要安装 EditorConfig 插件,比如 VSCode、Sublime。
对主流代码编辑器的高覆盖支撑 EditorConfig 成为被广泛应用的解决方案。
如何使用 EditorConfig
1. 确保编辑器中已支持 EditorConfig
各编辑器的支持方式请查看 官方文档 「No Plugin Necessary」 和 「Download a Plugin」 两部分
2. 创建并配置 .editorconfig
在项目目录下创建 .editorconfig 文件,EditorConfig 可配置的编码风格条目包括:
indent_style 设置缩进为 tab 或 space
tab_width 设置 tab 所占列数。默认是indent_size
indent_size 设置缩进所占列数,如果 indent_style 为 tab,则以 tab_width 值作为缩进宽度
end_of_line 设置换行符,值为lf、cr和crlf
charset 设置编码,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用utf-8-bom
trim_trailing_whitespace 设为 true 表示会去除行尾的空白字符
insert_final_newline 设为 true 表示使文件以一个空白行结尾
root 表示是最顶层的配置文件,设为 true 时,停止向上查找
复制代码
EditorConfig 的作用对象是编辑器,因此配置可作用于所有类型的可编辑文件。同时 EditorConfig 支持为特定的文件或特定类型的文件进行差异化设置。
可参照下述配置,更细致的设置方法可见 官方文档 File Format Details 部分。
# https://editorconfig.org
# 已经是顶层配置文件,不必继续向上搜索
root = true
[*]
# 编码字符集
charset = utf-8
# 缩进风格是空格
indent_style = space
# 一个缩进占用两个空格,因没有设置tab_with,一个Tab占用2列
indent_size = 2
# 换行符 lf
end_of_line = lf
# 文件以一个空白行结尾
insert_final_newline = true
# 去除行首的任意空白字符
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
复制代码
总结
EditorConfig 解决了编辑器配置层面的编码风格一致性问题。
然而关于代码风格的部分并未涉及,比如是否「需要在语句末尾添加分号」,「字符串使用单引号还是双引号包裹」,「多行对象的书写规范」等等。
Prettier:统一代码风格(Code Formatter)
Prettier 解决的问题
每位开发者的代码书写风格或多或少都有不同,比如一些作者喜欢加分号,一些作者认为基于 JS 引擎识别能力根本没有必要写分号。在 Prettier 之前,对于代码风格的争论似乎永无定论。统一规范便能终止争论,人们不用再纠结于如何设计代码样式,代码修改将集中于逻辑而非代码样式。
Prettier 如何工作
Prettier 通过语法分析将代码解析为 AST 树,在 AST 树上应用代码风格规范重新生成代码。(可在 Playground 查看三者转换过程)
Prettier 根据文件路径自动推断解析器(.js
文件使用 babel 解析器、.css
文件使用 css 解析器),对代码进行解析。对不同类型文件的识别和解析受限于 Prettier 的内部支持,因此 Prettier 并非覆盖所有类型的文件,如下图所示,支持前端主流开发语言,足够应对日常开发。
配置 Prettier
Prettier 是一种严格规范的代码风格工具,主张尽量降低可配置性,严格规定组织代码的方式。
Prettier 只提供少量配置项,这么做的原因很直观:既然为停止争论而生,那么为何还需要设置更多选项让人们继续纠结于如何配置呢?
Prettier 处理的范围包含:
- 字符串引号风格
- 空行处理
- 多行对象格式
- 分号处理
- 打印宽度:控制换行
- 通过换行控制评论影响范围
配置项
Prettier 提供如下配置项,可按需查询选项自行配置,一般只按需少量配置。
{
printWidth: 80,
// 打印宽度,默认是 80 列
tabWidth: 2,
// 缩进所占列数,默认是 2 列
useTabs: false,
// 缩进风格是否是Tab,默认是 false ,使用空格缩进
semi: true,
// 在语句末尾添加分号,默认是 true
singleQuote: false,
// 使用单引号,默认是 false
quoteProps: "as-needed",
// 对象中的属性使用引号,
// "as-needed" 只对需要的属性加引号,
// "consistent" 同一对象中属性引号保持统一,有福同享,有难同当
// "preserve" 强制使用引号。
// 默认为 as-needed
jsxSingleQuotes: false, // JSX中使用单引号,默认是 false
trailingComma: "es5",
// 多行时是否结尾添加逗号
// "es5" ES5中允许逗号的容器中添加逗号,比如 objects/arrays
// "all" 尽可能添加逗号
// "none" 不允许添加逗
// 默认值是 "es5"
bracketSpacing: true,
// 是否保留对象内侧两端的空格,比如 { foo: bar } 和 {foo:bar} 的区别
jsxBracketSameLine: false,
// 多行 JSX 的元素是否能和属性同行,默认是 false
arrowParens: "always",
// 箭头函数参数使用圆括号包裹 比如 (x) => x 和 x => x 的区别
// "always" 总是包裹
// "avoid" 尽可能避免包裹
rangeStart: 0,
// 只格式化文件中的一部分,范围开始于第几行
rangeEnd: Infinity,
// 只格式化文件中的一部分,范围结束于第几行
parser: "none"
// 指定解析器,Prettier会根据文件路径推断解析器
// 比如 .js 文件使用 babel 解析,.scss 文件使用 post-scss 解析
filepath: "none"
// 指定用于推断使用那个解析器的文件名
requirePragma: false
// 限制只格式化在文件顶部做了需格式化标识的文件
// 适用于在大型未格式化项目中,先指定少量文件格式化
insertPragma: false
proseWrap: "preserve"
htmlWhitespaceSensitivity: "css"
// HTML 文件的空格敏感度
// "css" 和 css 的 display 属性保持一致
// "strict" 空格敏感
// "ignore" 空格不敏感
vueIndentScriptAndStyle: false
// 是否对 Vue 文件中 <script> 和 <style> 标签内的代码应用缩进
endOfLine: "lf"
// 换行符
embeddedLanguageFormatting: "auto"
// 是否格式化嵌入引用代码,比如 markdown 文件中嵌入的代码块
// "auto" Prettier 自动识别并格式化
// "off" 关闭自动格式化
}
复制代码
配置文件
Prettier 使用 cosmiconfig
支持配置文件,cosmiconfig
是一种常用的配置文件读取工具,按照下述顺序沿文件树寻找配置文件,找到则停止:
- package.json 中的 prettier 字段
- .prettierrc 文件
- .prettierrc.json 文件
- .prettierrc.js 文件
- .prettierrc.toml 文件
选择上述任一方式进行自定义配置 Prettier,如不存在配置文件,Prettier 将依照默认值处理。
共享配置
module.exports = {
...require("@company/prettier-config"),
semi: false,
};
复制代码
有时需要在多项目之间共享配置文件,这时可将配置文件作为独立模块发布,通过引入模块实现共享;
有时需要在 monorepo 中共享配置文件,这时直接将配置文件放置在根目录,如果子项目需要自定义,引入根目录配置文件再进行扩展即可。
安装 Prettier 依赖
# 安装固定版本,确保不同环境中的表现一致(dev 和 ci)
yarn add prettier --dev --exact
复制代码
安装依赖之后,命令行调用 prettier --write [file/dir/glob]
即可对指定的文件、目录、匹配项进行代码格式化。
但通常我们不会选择手动调用,而是集成进 IDE 和 Git Hooks 使其成为自动化流程中的一部分。
IDE 集成
各个 IDE 的安装方式请查看 Prettier 首页的 Editor Support 部分。
Prettier 格式化工具内置 Prettier 内核并且拥有一份独立配置,但是当项目中已安装 Prettier 依赖时会使用项目依赖,项目依赖 > 全局依赖 > 内置依赖;当查找到项目中的 Prettier 配置文件时优先使用配置文件。
安装工具之后需要设置 IDE 才能成为利器,以 VSCode 为例:
- 设置为默认格式化工具:”Format Document With…” -> “Configure Default Formatter…”,调用 “Format Document” 对当前文件手动格式化检测设置效果;
- 设置保存文件时自动格式化:Preference Settings 中搜索 “Format On Save” 勾选;
上述设置可保证本地被修改的文件正确格式化,但无法保证外部导入的文件,也无法保证团队中其他成员的编辑。因此需要再设置一层更有保障的屏障:提交代码前走一遍自动格式化。
Git Hooks 集成
实现提交代码前自动格式化需要借助 Git Hooks,先来了解一下 Git Hooks、Husky、lint-staged
Git Hooks
Git 在特定的生命周期开放了一些钩子,使用者往钩子上挂自定义脚本,便能在合适的时机自动执行指定任务。比如在提交时执行 prettier --write
自动格式化代码。
Git 的所有钩子在 Git – Git 钩子 (git-scm.com) 中有具体详尽的介绍,有兴趣可以扩展阅读。
在这我们只会用到“提交工作流”钩子,提交工作流包含 4 个钩子:
pre-commit
在提交信息 编辑前 运行,在这个阶段塞入 代码检查 流程,检查未通过返回非零值即可停止提交流程;prepare-commit-msg
在默认信息被创建之后运行,此时正是 启动编辑器前 ,可在这个阶段加载commitizen
之类的辅助填写工具;commit-msg
在 完成编辑后 运行,可在这个阶段借助commitlint
进行提交信息规范性检查;post-commit
在 提交完成后 运行,在这个阶段一般做一些通知操作。
使用 Git 钩子最直观的方式是操作 .git/hooks 下的示例文件,将对应钩子文件的 .sample 后缀名移除即可启用。然而这种操作方式存在弊端:
- 需要操作项目范围外的 .git 目录
- 无法同步 .git/hooks 到远程仓库
Husky
操作 .git/hooks 的两个弊端可以通过为 Git 指定 hooks 目录完美避过,如下命令
# 指定 Git hooksPath 为根目录下的 .githooks 目录
git config core.hooksPath .githooks
复制代码
Husky 便是基于此方案,将 hooks 目录指定为项目下的 .husky 目录,同时提供简单易用的命令行工具管理目录中的 hooks 文件,不妨跟着 Husky 的手动安装流程看看它做了什么
# 安装 husky
npm install husky --save-dev
# 激活 Git hooks,这一步创建 .husky 目录并设置 git hooksPath
npx husky install
# 将自动激活 Git hooks 挂载到 npm prepare 生命周期上
# prepare触发点:无参数执行 npm install 时,执行 npm publish/npm pack 时
npm set-script prepare "husky install"
复制代码
lint-staged
如果对项目中所有文件一次性格式化,大范围的修改很可能出现不可控的情况。
这时可以借助 lint-staged 将处理范围限制在 Git 暂存区内 (staged) 的文件。
lint-staged 同样使用 cosmiconfig 支持配置,由于需配置内容较少,一般会直接在 package.json 中进行设置。
lint-staged 按照文件匹配指定任务,比如:
{
"lint-staged": {
"*.{ts,js,json,yml,md}": ["prettier --write"]
}
}
复制代码
如此一来,需执行的任务直接在 lint-staged 中指定即可。和 Git Hooks 配合使用时,只需在 hooks 上挂载 npx lint-staged
驱动任务执行。
Git Hooks 集成 Prettier
实现提交前进行代码格式化,不过是在 pre-commit
阶段执行 prettier --write
,借助 lint-staged 限制操作范围。一行命令搞定:
# mrm 是配置文件生成工具
# mrm lint-staged 会进行下列操作:
# 1. 自动安装 lint-staged 和 husky 依赖
# 2. 根据项目中已存在的配置文件生成 lint-staged 配置
# 3. 通过 husky 往 pre-commit hooks 上挂载 npx lint-staged
npx mrm lint-staged
复制代码
和 EditorConfig 配合使用
进入总结之前,我们需要思考一个问题,有了 Prettier 还需要 EditorConfig 吗?两者相交且不包含,是否会有冲突?
我们需要重演一下两者的作用过程:EditorConfig 作用于预览和输入阶段,Prettier 在保存和提交阶段重新组织代码,Prettier 会成为代码形态的最终决定者。
实际上如 API · Prettier 所描述,Prettier 对 .editorconfig 文件做了采用。对于一条相交属性,优先级关系是:Prettier 配置 > editorconfig 配置 > Prettier 默认值。
考虑到 EditorConfig 覆盖所有类型的文件,我的方案是 EditorConfig 管理相交属性,其他属性则由 Prettier 控制。
总结
Prettier 用于规范代码风格,但并不涉及语法检查,由此引入下一个话题 —— ESLint
ESLint:JavaScript 代码检查工具(Code Linter)
Prettier 专注于统一代码样式,而 ESLint 专注于找到代码存在的问题避免错误。
JavaScript 本身是一门动态弱类型语言,原始状态下没有语法检查工具支持,开发者只能在运行代码时发现错误,不断“试错”。ESLint 填补了这个缺口,在开发者书写代码时提示代码存在的问题,甚至在能力范围以内加以修正。
简单概括 Lint 提供的功能:
1. 避免低级 bug,找出可能发生的语法错误
使用未声明变量、修改 const 变量……
2. 提示删除多余的代码
声明而未使用的变量、重复的 case ……
3. 确保代码遵循最佳实践
可参考 airbnb style、javascript standard
4. 统一团队的代码风格
加不加分号?使用 tab 还是空格?
ESLint 的工作原理
ESLint 的底层要素是 AST 和 规则(Rules)。ESLint 的内部工作步骤可以概括为:
- ESLint 通过解析器(parser)将源代码解析成 AST
- 遍历 AST,遍历到节点和路径时触发特定的钩子
- Rule 在钩子上挂载检测逻辑;执行检测逻辑时发现当前语法不符合规范,直接向 ESLint 上报错误信息。
Rule
Rule 的本体是一个模块,包含 meta
和 create
两个属性。
meta
定义规则的元数据create
返回对象,该对象在合适的钩子上定义检测逻辑
以 ESLint 内置的 no-void
规则为例:no-void
规则建议禁用 void
如 AST explorer 所示,void
操作符在 AST 中被解析为一个 UnaryExpression
类型的节点;
同时查阅 estree 可知 UnaryExpression
涵盖 "-" | "+" | "!" | "~" | "typeof" | "void" | "delete"
操作符,并通过 operator
字段区分。因此 UnaryExpression[operator="void"]
便可匹配到 void
,AST 遍历到 UnaryExpression[operator="void"]
节点时执行挂载到该钩子上的所有检测。
no-void
模块的代码如下所示
/**
* @fileoverview Rule to disallow use of void operator.
* @author Mike Sidorov
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
// 规则类型:"suggestion" 表示不会导致错误,但建议修改
type: "suggestion",
// 文档描述: 在规则列表页显示的描述
docs: {
description: "disallow `void` operators",
category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-void",
},
// 上报信息中可用的字符串集
messages: {
noVoid: "Expected 'undefined' and instead saw 'void'.",
},
// 规定有效的规则选项
schema: [
{
type: "object",
properties: {
allowAsStatement: {
type: "boolean",
default: false,
},
},
additionalProperties: false,
},
],
},
create(context) {
// meta 中规定的有效选项可在运行上下文中被读取
const allowAsStatement =
context.options[0] && context.options[0].allowAsStatement;
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
'UnaryExpression[operator="void"]'(node) {
// 遍历到 void 节点时执行检测逻辑
if (
allowAsStatement &&
node.parent &&
node.parent.type === "ExpressionStatement"
) {
return;
}
// 上报错误信息
context.report({
node,
// 从 meta 中设定的字符串集查找字符串
messageId: "noVoid",
});
},
};
},
};
复制代码
fix 的实现
想必大家都有所耳闻,ESLint 除了检查问题还能 fix 可修复的问题,是否可修复又是如何确定的呢?
答案还是在 Rule 定义中,可修复的问题在上报错误时会夹带 fixer,告知 ESLint 如何修复。
context.report({
node: node,
message: "Missing semicolon".
fix: function(fixer) {
return fixer.insertTextAfter(node, ";");
}
});
复制代码
配置 ESLint
在 ESLint 的架构中,需要解析器,需要规则;而在这种架构的支撑下,ESLint 支持可配置和可扩展,插件化也是 ESLint 的高光亮点。最终展现给开发者的是丰富的配置,接着来介绍 ESLint 的配置项。
parser
ESLint 默认使用 Espree 作为解析器,Espree 目前(2021.04.17)全面支持 ECMAScript 2020 同时部分支持 ECMAScript 2021(最新的提案支持情况可查阅 proposals)。
得益于 Espree 及时跟进语言更新,开发者在大部分环境中可直接使用 ESLint。但 ESLint 和其他工具同时使用时就需要配置合适的解析器了。
@babel/eslint-parser
面向客户端的项目往往要将高版本语言编译为 ES5,需要语言编译器比如 Babel,这时得确保 Compiler 和 Linter 解析出的 AST 一致。
@babel/eslint-parser 应对 Babel 和 ESLint 合用的场景,用 Babel 解析器生成并转换为 ESLint 可理解的 ESTree;
@typescript-eslint/parser
Espree 无法解析 TS 语法,@typescript-eslint/parser 将 TS 源码解析为 ESTree。
parserOptions
Espree 支持到 ES2020,同时支持 JSX,在解析器选项中可指定你想要支持的语言选项
{
"parserOptions": {
// 开发所用的 ECMAScript 版本 6 - 10 2015 - 2020,默认为 ES5
"ecmaVersion": 6,
// 代码类型:script 脚本, module 模块
"sourceType": "module",
// 额外的语言特性
"ecmaFeatures": {
// 启用 JSX
"jsx": true,
// 允许全局作用域下使用 return 语句
"globalReturn": true,
// 启用全局严格模式
"impliedStrict": true
}
}
}
复制代码
全局变量和环境
ESLint 的 no-undef
规则会检测代码中未定义的变量,有一些变量是手动引入的第三方库声明的,使用前需要在 ESLint 配置文件中声明
{
"globals": {
// 声明 jQuery 对象为全局变量
"$": false // true表示该变量为 writable,false 表示 readonly, "off" 表示禁用
}
}
复制代码
一个环境定义了一组预定义的全局变量,根据代码执行环境设置
{
"env": {
"browser": true, // 项目中有代码在浏览器环境运行
"node": true, // 项目中有代码在 Node.js 环境下运行
"es6": true // 启动 ES6 全局变量和类型,同时会自动设置解析器为ES6
}
}
复制代码
rules
{
"rules": {
"eqeqeq": "off",
"curly": "error",
"quotes": ["error", "double"]
}
}
复制代码
ESLint 在内部维护规则定义,而在配置文件中,你可以使用注释或配置文件开启或关闭规则。
规则有三种状态:
"off"
或0
– 关闭规则"warn"
或1
– 开启规则,使用警告级别的错误:warn
(不会导致程序退出)"error"
或2
– 开启规则,使用错误级别的错误:error
(当被触发的时候,程序会退出)
规则定义中的 meta.schema
限定可接受的有效参数,配置时参数跟在规则状态后传入
// 打开规则 quotes,并且传入参数 "double"
/* eslint quotes: ["error", "double"] */
// 关闭规则 eqeqeq
/* eslint eqeqeq: "off" */
复制代码
plugins
ESLint 内置的核心规则只考虑 ECMAScript,对于 React、Vue、TypeScript 等非 ECMAScript 范围内的,ESLint 内置规则无法应对。这时需要通过插件来定制一些规则。
插件中定制规则,并提供可直接使用的一组或多组配置对象(ESLint 配置是一个对象模块,我将它简称为配置对象,每一个配置对象都可拥有 parser
、rules
、plugins
、extends
等属性)
以 eslint-plugin-prettier
为例:
module.exports = {
// 提供一组名为 recommended 的配置对象,可通过 extends 直接应用到配置中
configs: {
recommended: {
extends: ["prettier"],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
"arrow-body-style": "off",
"prefer-arrow-callback": "off",
},
},
},
// 定制规则,[插件名]/[规则名],该规则为 prettier/prettier
rules: {
prettier: {
meta: {
docs: {
url: "https://github.com/prettier/eslint-plugin-prettier#options",
},
type: "layout",
fixable: "code",
schema: [
// Prettier options:
],
messages: {
// Prettier Messages:
},
},
create(context) {
return {
Program() {},
};
},
},
},
};
复制代码
extends
{
"extends": "eslint:recommended"
}
复制代码
ESLint 配置是一个对象模块,可以通过 extends
应用一组已存在的配置对象。
继承也就是在当前配置对象的基础上合并上另一些配置对象,继承的对象有几种情况:
eslint:
开头的字符串,由官方提供eslint:recommended
启用 ESLint 推荐开启的规则eslint:all
开启 ESLint 中所有核心规则
eslint-config-
开头的字符,由第三方库共享,常见场景:- 直接使用代码最佳实践,如
eslint-config-airbnb
、eslint-config-standard
- 使用自定义的常用配置,以
eslint-config-xxx
形式发包到 npm,在多项目中共享使用
- 直接使用代码最佳实践,如
plugin:
开头的字符串,使用 Plugin 中的配置- 调用方式为
plugin:插件名/配置名
,比如plugin:react/recommended
意思是使用eslint-plugin-react
中的recommended
配置
- 调用方式为
- 相对路径,使用本地配置文件
配置文件
ESLint 并没有使用 cosmiconfig
维护配置文件,而是自成一派,以下述顺序排序优先级。在找到配置文件后会继续向上查找,找到 root: true
的文件为止,然后合并所有文件的配置对象。这种模式的优点在于自动实现按目录层级继承。
.eslintrc.js
.eslintrc.cjs
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
package.json
安装依赖
yarn add eslint -D
复制代码
依赖安装成功之后,便可通过命令行手动使用 ESLint
# eslint cli 引导创建配置文件
yarn eslint --init
# 尝试 lint 指定文件
yarn eslint xxx.js
# lint 所有文件
yarn eslint .
# lint 指定类型,指定目录
yarn eslint --ext .jsx,.js lib/
# 带缓存,只检查有改变的文件
yarn eslint --cache
# 尽量修复问题
yarn eslint --fix
复制代码
接下来将 lint 加入自动化工作流
IDE 集成
各 IDE 的插件安装方式可查看 Integrations – ESLint 中文
以 VSCode 为例,安装 ESLint – Visual Studio Marketplace 插件,该插件要求项目或全局环境安装 eslint 依赖。
插件提供智能提示。这里介绍几个常用的配置项:
{
// 设置 lint 执行时机,onType 输入时,onSave 保存时
"eslint.run": "onSave",
// 设置保存时自动 fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
复制代码
Git Hooks 集成
Git Hooks 在 Prettier 部分已着重介绍过,lint 集成进 commit 流程,同样是在 lint-staged 中进行配置
{
"lint-staged": {
"src/**/*.{js}": ["eslint --fix"]
}
}
复制代码
和 Prettier 配合使用
ESLint 有两类规则:
- 代码风格检查(Formatting rules): max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style…
- 代码质量检查(Code-quality rules): no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors…
ESLint 是否可替代 Prettier?
代码风格检查似乎和 Prettier 的功能相重叠,ESLint 是否可以完全替代 Prettier,答案是否定的。
首先,ESLint 中的 Formatting rules 并非都提供了fixer
,上面列出几条规则示例里仅有 comma-style
提供了修复功能;
其次,ESLint 着重于 JS/TS,无法兼顾 CSS、Markdown 的代码风格;
术业有专攻,代码风格规范化还得交给 Prettier。
如何解决冲突?
如何解决 ESLint 和 Prettier 职责重叠部分的冲突?
方案一,[eslint-config-prettier
](prettier/eslint-config-prettier: Turns off all rules that are unnecessary or might conflict with Prettier. (github.com)) 关闭 ESLint 中和 Prettier 可能冲突的所有 Rules,eslint 负责代码质量检查,prettier 做 formatter;
方案二,eslint-plugin-prettier
该插件增加了 prettier/prettier
规则, 该规则执行 prettier 并将错误信息上报 eslint。简而言之,将 prettier 融合到 eslint 中,担起代码风格检查的功能,同时需要搭配 eslint-config-prettier
关闭掉 ESLint 中代码风格检查相关的规则。
# 安装 config 关闭 ESLint 规则
yarn add eslint-config-prettier -D
复制代码
// .eslintrc.json 使用 prettier 插件提供的配置
// 一步加入全组配置:config/plugin/rules
{
"extends": ["plugin:prettier/recommended"]
}
复制代码
// vscode 设置
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
复制代码
总结
EditorConfig、Prettier、ESLint 三者在代码规范化领域专注的点各不相同,各有专攻,组合使用才能达到全面覆盖。
Prettier 将 EditorConfig 考虑在内,ESLint 通过插件组合 Prettier,个人的推荐配置是自底向上分工
- EditorConfig 专注于统一编辑器编码风格配置
- Prettier 专注于检查并自动更正代码风格
- ESLint 专注于 JavaScript 代码质量检查
最终的配置清单可见 code-canonicalization (github.com)
参考资源
Prettier 看这一篇就行了 – 知乎 (zhihu.com)
ESLint 工作原理探讨 – 知乎 (zhihu.com)
Configuring ESLint – ESLint 中文
okonet/lint-staged: ?? — Run linters on git staged files (github.com)