【译】无冲突设置 ESLint,Prettier 和 EditorConfig

7,933 阅读3分钟

翻译自:Set up ESlint, Prettier & EditorConfig without conflicts

本文是有关如何使用 ESLintPrettierEditorConfig 增强你的开发环境系列文章的第二篇。

如果对这三种工具的使用仍有疑问,你可以阅读我的第一篇文章 为什么你应该使用 ESLint,Prettier 以及 EditorConfig?。本文的目的是提供无冲突配置 ESLint、Prettier 和 EditorConfig 的指南,同时避免多余的配置。

背景介绍

如果你已经将 Prettier 和 ESLint 一起使用过,你可能会遇到 代码格式的问题

工作中,当我们决定把一个 TypeScript 项目的 TSLint 迁移到 ESLint 时,我遇到了这些问题。当初我们的想法是通过共同使用 ESLint 和 Prettier 来升级代码检测能力。我们通过强制添加两个空格缩进的 ESLint 规则修补了上述问题。但是,其他冲突又出现了。显然,我们不能通过为每个冲突添加 ESLint 规则来修补这些冲突。

解决模式

正如上篇文章所探讨的那样,我们想使用 专事专办 的方法。在这种情况下,ESLint 成为了我们的代码质量检测器,Prettier 充当代码格式化工具,而 EditorConfig 将为每个人提供正确的编辑器配置。

  • 与编辑器相关的所有配置(结尾行、缩进风格、缩进大小等等)应该由 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(译者注:这里仅是为了说明,json 文件不支持注释)
{
  "extends": ["eslint:recommended"],
  "env": {
    "es6": true,
    "node": true
  }
}

Prettier 开始干活吧

为了可以和 Prettier 一起使用 ESLint,办法就是 停用可能与 Prettier 冲突的所有 ESLint 规则(仅指代码格式规则)。幸运地是,eslint-config-prettier 包已经帮我们做了这件事。

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 集成 Prettier

必须运行两个命令的来检测与格式化文件非常的不便。为了解决这个问题,我们将通过添加 eslint-plugin-prettier 包来让 ESLint 集成 Prettier。

npm install eslint-plugin-prettier --save-dev

接着重写 .eslintrc.json 文件,在 plugins 数组中添加 prettier 插件,并设置新建的 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

在 ESLint 的错误输出中,我们可以看到写了太多字符或缩进有问题的行被 prettier/prettier 标记为错误。

现在让我们使用 ESLint 的修复选项:

npx eslint main.js --fix

你将看到我们的文件被格式化的方式与 Prettier 做的一样。

通过在 .eslintrc.json 文件的 extends 数组中添加 plugin:prettier/recommended 配置,你可以替换掉我们已设置的所有与 prettier 相关的配置。

它看起来如下所示:

// .eslintrc.json(译者注:这里仅是为了说明,json 文件不支持注释)
{
  "extends": ["eslint:recommended", "plugin:prettier/recommended"],
  "env": {
    "es6": true,
    "node": true
  }
}

这个配置与先前的效果一样,但是更简短且不太明确,为了清晰可见,在余文中我们仍将使用原先的配置。

Prettier 与 ESLint 之间的“鸿沟”

添加一个 ESLint 插件

上述的这种设置对于这种小型项目已经够了,但是一旦开始使用 Vue、React 或其他框架时,就很容易把 ESLint 和 Prettier 的配置搞混。我遇到的一个常见问题就是开发者添加了一个 ESLint 插件,期望它能够正常工作,且不添加任何其他的东西以让 Prettier 也可以工作。

以 TypeScript 为例

某个时候,我们决定在上面项目中使用 TypeScript。由于 TSLint 很快就会被弃用,我们决定将其替换为 ESLint。我们将以 TypeScript 为例。下面我们所做的同样适用于 React,Vue 或具有 ESLint 插件可用的任何其他框架

这里是源于 main.jsmain.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 或其他带有特定语法的框架兼容,我们需要添加一个解析器,以便 ESLint 可以读取新加插件中新代码以及与 TypeScript(或任何其他需要解析器的框架)相关的一套新规则。

npm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

然后,我们通过在 parser 选项中添加 TypeScript 解析器并将插件添加到 extends 数组中来修改 .eslintrc.json 文件以包含 TypeScript:

{
  "parser": "@typescript-eslint/parser",
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier"],
  "env": {
    "es6": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": "error"
  },
  "plugins": [
    "prettier"
  ]
}

再然后,我们使用修复选项在文件上运行 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 个空格,却从 Prettier 获得 2 个空格。错误看起来像是来自 @typescript-eslint 规则。

如果你像我一样,使用 VSCode 并启用了 ESLint 的保存时自动修复选项,那问题就看起来像下面这样:

通过禁用新插件的所有 ESLint 格式化规则来解决冲突

此时,很多人常犯的一个错误就是通过覆盖导致冲突的规则来解决这个或任何其他在引入新插件后与 Prettier 之间的冲突问题。 那上述错误就是 @typescript-eslint 的缩进规则引起的。为了解决它,他们把这个规则添加到了 rules 数组中:

"@typescript-eslint/indent": ["error", 2]

通过强制指定 typescript-eslint 的缩进规则来和 prettier 相匹配确实有效解决了当前的问题,但是两个新问题又冒出来了

  • 目前,我们只有一个缩进问题,但是不晓得 typescript-eslint 插件中是否还有其他规则与 Prettier 起冲突。
  • 现在 ESLint 和 Prettier 都将负责格式化我们的代码,这与我们想要遵循的解决问题的模式不一致。

如果我们遵循先前的解决模式,代码格式化的工作不应该见到 ESLint 的身影,新添加的插件也不例外。因此,我们需要通过向 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.jsonextends 数组的顺序非常重要。基本上,每次一个新配置添加到这个数组中,它都会覆盖前面的配置。因此,prettierprettier/@typescript-eslint 位于数组尾部至关重要。

有了这些配置,不会再有问题出现了。尽管放心,ESLint 不会再尝试做 Prettier 的工作了。

总结下这个常见问题:

  • 每当你在 ESLint 中添加插件时,必须考虑它带来的代码格式规则,并添加一个更 prettier 配置(如果有的话)以将其全部禁用。在我们举的例子中,我们用的是 prettier/@typescript-eslint,但你完全可以使用 prettier/reactprettier/vue。请查阅 eslint-config-prettier 文档,以获取受支持的 ESLint 插件列表。
  • 不要尝试自己在 .eslintrc.json 文件中覆盖格式设置规则(这不是 ESLint 该干的事)。
  • 如果你发现你的代码由 Prettier 和 ESLint 冲突导致了两种不同风格的格式,这就说明你有一个无用的 ESLint 格式规则导致了这个冲突,那你就没有遵从上述的解决方案。

添加自定义规则

假定对于我们上面的项目,团队不满意 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"
  ]
}

如果你跟上了前文的思路,那你对生成的错误就不会觉得奇怪了,我们陷入了同样的遭遇:

这里存在两个问题:

  • 由于我们的自定义规则是在我们自己的 .eslintrc.jsonrules 数组中定义的,因此它会覆盖负责禁用所有格式设置规则的 prettier/@typescript-eslint 的配置。
  • 再一次地,通过添加此规则,我们只是忘记了 Prettier 的作用,而违背了我们的解决模式。

要遵循此模式,应在 Prettier 的配置文件 .prettierrc 中设置所有代码格式规则。这样的话,我们现在就应该添加一个:

// .prettierrc
{
  "tabWidth": 4
}

现在 Prettier 将使用 4 个空格而不是默认的 2 个空格来格式化代码,而 .eslintrc.json 文件应不包含任何有关缩进的规则。

小结一下:

  • 每当你想添加一个规则,请尝试在代码质量和代码格式之间对其进行归类。简单的方法就是,你只需检查 prettier 是否强制支持该规则。
  • 不要将自定义格式设置规则添加到 .eslintrc.json 文件中。这些几乎肯定会与 Prettier 产生冲突。

Prettier & EditorConfig

设置编辑器配置

无论使用什么编辑器,EditorConfig 都可以使我们拥有相同的编辑器配置。因此,每次编写新代码时,我们不必依靠 Prettier 来按照团队的约定来格式化我们的代码。但它确实需要你在 IDE 上安装必要的 EditorConfig 插件或扩展。

我们将在本文中使用 VSCode,但请记住 EditorConfig 支持多种编辑器。

我们往项目中添加了一个自定义的编辑器配置:

# .editorconfig
[*]
end_of_line = lf
charset = utf-8
indent_style = space

如果你使用了 EditorConfig VSCode 扩展 ,那编辑器就会知道如何格式化文件。你会在编辑器的右下方注意到这一点:

避免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 的相关配置,然后在适当的文件中进行更改。这 4 个选项之外的先前配置选项仅只写到 .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。现在,每当你使用编辑器打开一个新文件时,缩进就已经配置为 4 了。Prettier 无需为此设置格式化代码。

我希望本文能帮助你在自己的项目中配置 ESLint,Prettier 和 EditorConfig。 我花了一些时间才弄清楚它们。对于这种类型的配置,我认为最好不要采用设置一次过后就忘的方法来获得你的代码检测和格式化体验。如果你想修改你的配置,你必须记住遵照上面描述的模式做。