原文链接: blog.theodo.com/2019/08/emp…
来由
如果你已经在搭配使用 Prettier 和 ESLint, 可能已经遇到过 代码格式化冲突 的问题了吧。
我曾在一次把 TypeScript 项目从 TSLint 迁移到 ESLint 的工作中遇到过这些问题。我们打算用 ESLint 和 Prettier 接管语法检查,在添加了一条 ESLint 规则强制规定 2 个空格缩进以解决上图中的问题后,其他问题又像按下葫芦浮起瓢一样纷纷出现了,很明显没法子通过一条条增加规则解决每一个冲突。
网上关于这个话题的确有很多说法,但大部分都是针对某个特定项目给出一个配置,而非深入阐释为什么 ESLint、Prettier 或 EditorConfig 会八字不合。本文的目的就是对这些潜在的问题解惑,以免相关的 bug 再干扰调试。
策略
我们先来明确一下 各司其职 的原则:
- EditorConfig 将负责统一各种编辑器的配置,所有和编辑器相关的配置 (行尾、缩进样式、缩进距离...) 都交给它
- Prettier 作为
代码格式化
工具 - 其余的,也就是
代码质量
方面的语法检查,用 ESLint 来做
ESLint 和 Prettier
假设我们有这么一个 main.js
,并同时配置了 ESLint 和 Prettier。
main.js
function printUser(firstName, lastName, number, street, code, city, country) { console.log(`${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`); } printUser('John', 'Doe', 48, '998 Primrose Lane', 53718, 'Madison', 'United States of America');
eslintrc.json
{ "extends": ["eslint:recommended"], "env": { "es6": true, "node": true } }
让 Prettier 做它自己的工作
为了能 配合使用 ESLint 和 Prettier,应该 关闭所有可能和 Prettier 冲突的 ESLint 规则 (也就是 代码格式化
那些)。好消息是,eslint-config-prettier
包已经解决了这个问题。
bash
代码解读
复制代码
npm install eslint-config-prettier --save-dev
先在 .eslintrc.json
中,将 prettier
加到 extends
数组的最后,并移除任何 代码格式化
相关的规则:
{ "extends": ["eslint:recommended", "prettier"], "env": { "es6": true, "node": true } }
如此一来, Prettier 的配置将覆盖 extends
数组中先前任何 代码格式化
相关的 ESLint 配置,二者就能并行不悖地工作了。
将 Prettier 整合进 ESLint
分别运行两条命令以检查语法和格式化代码可不太方便,我们可以通过安装 eslint-plugin-prettier
包来解决这个问题。
npm install eslint-plugin-prettier --save-dev
在 .eslintrc.json
的 plugins
数组中加入 prettier
插件,并建立一条指定为 error
的 Prettier 新规则,这样任何格式化错误就也被认为是 ESLint 错误了。
{ "extends": ["eslint:recommended", "prettier"], "env": { "es6": true, "node": true }, "rules": { "prettier/prettier": "error" }, "plugins": [ "prettier" ] }
对 main.js
运行 ESLint 试一下:
npx eslint main.js
可以看到,那些字符过多或缩进错误的行,都被标以了 prettier/prettier
并作为 ESLint 错误被打印出来。
修复之:
npx eslint --fix main.js
文件将按 Prettier 的方式被正确格式化。
Prettier 和 ESLint 配合中的常见问题
添加 ESLint 插件
以上的配置应付小项目绰绰有余;但当你使用 Vue、React 或其他框架时,还是 很容易让 ESLint 和 Prettier 的配置打架。我遇到的一个常见问题是当开发者增加一个 ESLint 插件后,如何在不同时改动 Prettier 的情况下,也能让后者正常工作。
以 TypeScript 为例
出于某些考虑,我们决定在项目中使用 TypeScript。鉴于 TSLint 将被废弃,自然要用 ESLint 取而代之。这里就使用 TypeScript 作为一个例子,来展示 对于有一个适用的 ESLint 插件的框架,该如何处理。
这是进行了 TypeScript 改造后的 main.ts
文件:
function printUser(firstName: string, lastName: string, number: number, street: string, code: number, city: string, country: string): void { console.log(`${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`); } printUser('John', 'Doe', 48, '998 Primrose Lane', 53718, 'Madison', 'United States of America');
要想让 ESLint 兼容 TypeScript 或是其他什么特殊语法的框架,需要增加一个 parser 以使 ESLint 可以读取新的代码和相关的一系列规则。
npm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
然后修改 .eslintrc.json
,分别向 parser
选项和 extends
数组中包含 TypeScript 插件:
{ "parser": "@typescript-eslint/parser", "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier"], "env": { "es6": true, "node": true }, "rules": { "prettier/prettier": "error" }, "plugins": [ "prettier" ] }
带上 --fix
选项,再来试试 ESLint:
这时如果我们多次执行这条命令,每次都将得到同样的报错 -- 尽管控制台里面说错误是可以被修复的。此时文件被修改为了这样:
function printUser( firstName: string, lastName: string, number: number, street: string, code: number, city: string, country: string ): void { console.log( `${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}` ); } printUser( 'John', 'Doe', 48, '998 Primrose Lane', 53718, 'Madison', 'United States of America' );
所以 Prettier 的确格式化了我们的代码,但 ESLint 期望的 4 个空格没有被满足。错误看起来和 @typescript-eslint
规则有关。
如果你像我一样在使用 VSCode 并开启了保存时自动执行 ESLint 修复,可能会看到这种情况:
通过禁用新增插件的所有 ESLint 格式化规则解决冲突
很多人的一个常见错误就是头疼医头、脚疼医脚。比如对于这个 @typescript-eslint
插件里面的缩进规则,他们会往 rules
数组中添加一条这样的规则:
"@typescript-eslint/indent": ["error", 2]
这当然解决了具体冲突,但有两个问题出现了:
- 无法保证
typescript-eslint
插件中的其他规则今后不和 Prettier 冲突 - ESLint 和 Prettier 又开始同时负责代码格式化了,这违背了我们的分工策略
按照之前的整合方法,通过在 extends
数组中增加 prettier/@typescript-eslint
来禁用相关插件中所有关乎 代码格式化
的规则。
{ "parser": "@typescript-eslint/parser", "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier", "prettier/@typescript-eslint"], "env": { "es6": true, "node": true }, "rules": { "prettier/prettier": "error" }, "plugins": [ "prettier" ] }
谨记 .eslintrc.json
中 extends
数组的顺序非常重要。基本上每次向数组添加新配置时,都将覆盖之前的配置。因此 prettier
和 prettier/@typescript-eslint
待在数组末尾至关重要。
这样配置后就没问题了,ESLint 将不会再越俎代庖。
总结一下这种常见问题:
- 每当你添加一个 ESLint 插件,针对 它引入的
代码格式化
规则 添加一个prettier
配置 (如果有的话) 。在我们的例子中,使用了prettier/@typescript-eslint
,但其实我们也可以用prettier/react
或prettier/vue
。 检查eslint\-config\-prettier 文档
(github.com/prettier/es…) 获取可用的 ESLint 插件。 - 不要尝试自己覆盖 eslintrc 中的格式化规则
- 每当你见到这种 Prettier 和 ESLint 对同一种格式化的冲突,就以为着你有一条无用的 ESLint 格式化规则,也意味着你没有遵守以上两条
添加一条自定义规则
项目团队中的 TypeScript 开发者对 2 个空格缩进浑身不舒服,非要改成 4 个。一个常见的错误是把我们的 ESLint-Prettier 整合策略抛之脑后,并在 .eslintrc.json
中直接添加规则,就像这样:
{ "parser": "@typescript-eslint/parser", "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier", "prettier/@typescript-eslint"], "env": { "es6": true, "node": true }, "rules": { "prettier/prettier": "error", "@typescript-eslint/indent": ["error", 4] }, "plugins": [ "prettier" ] }
熟悉的错误毫无意外地又出现了,和我们之前解决 Prettier 协同 ESLint 时遇到的一摸一样:
在 rules
数组中自定义的规则会覆盖 prettier/@typescript-eslint
配置。
按照正确的策略,代码格式化
规则应该在 .prettierrc
中配置。对于例子来说,应该是:
.prettierrc
{ "tabWidth": 4 }
这样 Prettier 将以 4 个空格格式化代码,而 .eslintrc.json
应该不关心任何缩进规则。
总结一下这种常见问题:
- 当你想添加一条规则时,分清其属于
代码质量
还是代码格式化
类别。简单地做法是,检查这条规则在 Prettier 中是不是可行的 - 不要在
.eslintrc.json
中添加格式化规则,这样做将不可避免的和 Prettier 冲突
译注:最新版本 Prettier 已经不需要再配置 prettier/@typescript-eslint
Prettier 和 EditorConfig
设置编辑器配置
EditorConfig 使不同编辑器可以保持同样的配置。因此,我们得以无需在每次编写新代码时,再依靠 Prettier 来按照团队约定格式化一遍(译注:出现保存时格式化突然改变的情况)。当然这需要在你的 IDE 上安装了必要的 EditorConfig 插件或扩展。
本文以 VSCode 为例,但 EditorConfig 支持很多编辑器。
在项目中增加自定义的编辑器配置:
.editorconfig
* end_of_line = lf charset = utf-8 indent_style = space
如果安装了 the EditorConfig VSCode extension
,编辑器将自动获知该如何格式化你的文件。你也能在编辑器右下角看到相应的信息:
避免 EditorConfig 和 Prettier 的重复配置
但是,这意味着 Prettier 和 EditorConfig 共享了相同的配置选项,而我们不希望同步维护两份重复的配置 (比如关于行尾的配置)。 Prettier 的最新版本通过处理 .editorconfig
文件来决定使用的配置。
限于以下几种选项:
end_of_line indent_style indent_size/tab_width max_line_length
这些选项会被映射为 Prettier 的相关选项 (如果没有在 .prettierrc
再被定义的话):
"endOfLine" "useTabs" "tabWidth" "printWidth"
就像 ESLint 配合 Prettier 时的策略那样,在你改变 EditorConfig 或 Prettier 配置时,根据这些限定选项来决定在哪边改。上面例子中的选项就应该只在 .editorconfig
中存在。
据此再检查我们上面做过的所有配置,还能发现一个配置错误。我们在 Prettier 配置中指定了缩进距离。因为这也是一个浏览器相关的配置,所以应该将其移至 .editorconfig
。
.editorConfig
tab_width 4
现在我们删除了已经空白的 .prettierrc.json
,并对未格式化的代码运行 ESLint:
function printUser( firstName: string, lastName: string, number: number, street: string, code: number, city: string, country: string ): void { console.log( `${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}` ); } printUser( "John", "Doe", 48, "998 Primrose Lane", 53718, "Madison", "United States of America" );
代码缩进变成了 4 个空格。现在,无论你在何时用编辑器打开一个新文件,都会应用这个配置,Prettier 同样也会遵循。