[譯] 以和為貴!讓 ESlint、Prettier 和 EditorConfig 互不沖突
原文鏈接:https://blog.theodo.com/2019/08/empower-your-dev-environment-with-eslint-prettier-and-editorconfig-with-no-conflicts/

來由
如果你已經(jīng)在搭配使用 Prettier 和 ESLint, 可能已經(jīng)遇到過 代碼格式化沖突 的問題了吧。

我曾在一次把 TypeScript 項目從 TSLint 遷移到 ESLint 的工作中遇到過這些問題。我們打算用 ESLint 和 Prettier 接管語法檢查,在添加了一條 ESLint 規(guī)則強(qiáng)制規(guī)定 2 個空格縮進(jìn)以解決上圖中的問題后,其他問題又像按下葫蘆浮起瓢一樣紛紛出現(xiàn)了,很明顯沒法子通過一條條增加規(guī)則解決每一個沖突。
網(wǎng)上關(guān)于這個話題的確有很多說法,但大部分都是針對某個特定項目給出一個配置,而非深入闡釋為什么 ESLint、Prettier 或 EditorConfig 會八字不合。本文的目的就是對這些潛在的問題解惑,以免相關(guān)的 bug 再干擾調(diào)試。
策略
我們先來明確一下 各司其職 的原則:
EditorConfig 將負(fù)責(zé)統(tǒng)一各種編輯器的配置,所有和編輯器相關(guān)的配置 (行尾、縮進(jìn)樣式、縮進(jìn)距離...) 都交給它 Prettier 作為 代碼格式化工具其余的,也就是 代碼質(zhì)量方面的語法檢查,用 ESLint 來做
ESLint 和 Prettier
假設(shè)我們有這么一個 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,應(yīng)該 關(guān)閉所有可能和 Prettier 沖突的 ESLint 規(guī)則 (也就是 代碼格式化 那些)。好消息是,eslint-config-prettier 包已經(jīng)解決了這個問題。
npm install eslint-config-prettier --save-dev
先在 .eslintrc.json 中,將 prettier 加到 extends 數(shù)組的最后,并移除任何 代碼格式化 相關(guān)的規(guī)則:
{
"extends": ["eslint:recommended", "prettier"],
"env": {
"es6": true,
"node": true
}
}
如此一來, Prettier 的配置將覆蓋 extends 數(shù)組中先前任何 代碼格式化 相關(guān)的 ESLint 配置,二者就能并行不悖地工作了。
將 Prettier 整合進(jìn) ESLint
分別運行兩條命令以檢查語法和格式化代碼可不太方便,我們可以通過安裝 eslint-plugin-prettier 包來解決這個問題。
npm install eslint-plugin-prettier --save-dev
在 .eslintrc.json 的 plugins 數(shù)組中加入 prettier 插件,并建立一條指定為 error 的 Prettier 新規(guī)則,這樣任何格式化錯誤就也被認(rèn)為是 ESLint 錯誤了。
{
"extends": ["eslint:recommended", "prettier"],
"env": {
"es6": true,
"node": true
},
"rules": {
"prettier/prettier": "error"
},
"plugins": [
"prettier"
]
}
對 main.js 運行 ESLint 試一下:
npx eslint main.js

可以看到,那些字符過多或縮進(jìn)錯誤的行,都被標(biāo)以了 prettier/prettier 并作為 ESLint 錯誤被打印出來。
修復(fù)之:
npx eslint --fix main.js
文件將按 Prettier 的方式被正確格式化。
Prettier 和 ESLint 配合中的常見問題
添加 ESLint 插件
以上的配置應(yīng)付小項目綽綽有余;但當(dāng)你使用 Vue、React 或其他框架時,還是 很容易讓 ESLint 和 Prettier 的配置打架。我遇到的一個常見問題是當(dāng)開發(fā)者增加一個 ESLint 插件后,如何在不同時改動 Prettier 的情況下,也能讓后者正常工作。
以 TypeScript 為例
出于某些考慮,我們決定在項目中使用 TypeScript。鑒于 TSLint 將被廢棄,自然要用 ESLint 取而代之。這里就使用 TypeScript 作為一個例子,來展示 對于有一個適用的 ESLint 插件的框架,該如何處理。
這是進(jìn)行了 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 可以讀取新的代碼和相關(guān)的一系列規(guī)則。
npm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
然后修改 .eslintrc.json,分別向 parser 選項和 extends 數(shù)組中包含 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:

這時如果我們多次執(zhí)行這條命令,每次都將得到同樣的報錯 -- 盡管控制臺里面說錯誤是可以被修復(fù)的。此時文件被修改為了這樣:
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 規(guī)則有關(guān)。
如果你像我一樣在使用 VSCode 并開啟了保存時自動執(zhí)行 ESLint 修復(fù),可能會看到這種情況:

通過禁用新增插件的所有 ESLint 格式化規(guī)則解決沖突
很多人的一個常見錯誤就是頭疼醫(yī)頭、腳疼醫(yī)腳。比如對于這個 @typescript-eslint 插件里面的縮進(jìn)規(guī)則,他們會往 rules 數(shù)組中添加一條這樣的規(guī)則:
"@typescript-eslint/indent": ["error", 2]
這當(dāng)然解決了具體沖突,但有兩個問題出現(xiàn)了:
無法保證 typescript-eslint插件中的其他規(guī)則今后不和 Prettier 沖突ESLint 和 Prettier 又開始同時負(fù)責(zé)代碼格式化了,這違背了我們的分工策略
按照之前的整合方法,通過在 extends 數(shù)組中增加 prettier/@typescript-eslint 來禁用相關(guān)插件中所有關(guān)乎 代碼格式化 的規(guī)則。
{
"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"
]
}
謹(jǐn)記 .eslintrc.json 中 extends 數(shù)組的順序非常重要。基本上每次向數(shù)組添加新配置時,都將覆蓋之前的配置。因此 prettier 和 prettier/@typescript-eslint 待在數(shù)組末尾至關(guān)重要。
這樣配置后就沒問題了,ESLint 將不會再越俎代庖。
總結(jié)一下這種常見問題:
每當(dāng)你添加一個 ESLint 插件,針對 它引入的 代碼格式化規(guī)則 添加一個prettier配置 (如果有的話) 。在我們的例子中,使用了prettier/@typescript-eslint,但其實我們也可以用prettier/react或prettier/vue。檢查eslint\-config\-prettier 文檔(https://github.com/prettier/eslint-config-prettier) 獲取可用的 ESLint 插件。不要嘗試自己覆蓋 eslintrc 中的格式化規(guī)則 每當(dāng)你見到這種 Prettier 和 ESLint 對同一種格式化的沖突,就以為著你有一條無用的 ESLint 格式化規(guī)則,也意味著你沒有遵守以上兩條
添加一條自定義規(guī)則
項目團(tuán)隊中的 TypeScript 開發(fā)者對 2 個空格縮進(jìn)渾身不舒服,非要改成 4 個。一個常見的錯誤是把我們的 ESLint-Prettier 整合策略拋之腦后,并在 .eslintrc.json 中直接添加規(guī)則,就像這樣:
{
"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"
]
}
熟悉的錯誤毫無意外地又出現(xiàn)了,和我們之前解決 Prettier 協(xié)同 ESLint 時遇到的一摸一樣:

在 rules 數(shù)組中自定義的規(guī)則會覆蓋 prettier/@typescript-eslint 配置。
按照正確的策略,代碼格式化 規(guī)則應(yīng)該在 .prettierrc 中配置。對于例子來說,應(yīng)該是:
.prettierrc
{
"tabWidth": 4
}
這樣 Prettier 將以 4 個空格格式化代碼,而 .eslintrc.json 應(yīng)該不關(guān)心任何縮進(jìn)規(guī)則。
總結(jié)一下這種常見問題:
當(dāng)你想添加一條規(guī)則時,分清其屬于 代碼質(zhì)量還是代碼格式化類別。簡單地做法是,檢查這條規(guī)則在 Prettier 中是不是可行的不要在 .eslintrc.json中添加格式化規(guī)則,這樣做將不可避免的和 Prettier 沖突
Prettier 和 EditorConfig
設(shè)置編輯器配置
EditorConfig 使不同編輯器可以保持同樣的配置。因此,我們得以無需在每次編寫新代碼時,再依靠 Prettier 來按照團(tuán)隊約定格式化一遍(譯注:出現(xiàn)保存時格式化突然改變的情況)。當(dāng)然這需要在你的 IDE 上安裝了必要的 EditorConfig 插件或擴(kuò)展。
本文以 VSCode 為例,但 EditorConfig 支持很多編輯器。
在項目中增加自定義的編輯器配置:
.editorconfig
[*]
end_of_line = lf
charset = utf-8
indent_style = space
如果安裝了 the EditorConfig VSCode extension,編輯器將自動獲知該如何格式化你的文件。你也能在編輯器右下角看到相應(yīng)的信息:

避免 EditorConfig 和 Prettier 的重復(fù)配置
但是,這意味著 Prettier 和 EditorConfig 共享了相同的配置選項,而我們不希望同步維護(hù)兩份重復(fù)的配置 (比如關(guān)于行尾的配置)。Prettier 的最新版本通過處理 .editorconfig 文件來決定使用的配置。
限于以下幾種選項:
end_of_line
indent_style
indent_size/tab_width
max_line_length
這些選項會被映射為 Prettier 的相關(guān)選項 (如果沒有在 .prettierrc 再被定義的話):
"endOfLine"
"useTabs"
"tabWidth"
"printWidth"
就像 ESLint 配合 Prettier 時的策略那樣,在你改變 EditorConfig 或 Prettier 配置時,根據(jù)這些限定選項來決定在哪邊改。上面例子中的選項就應(yīng)該只在 .editorconfig 中存在。
據(jù)此再檢查我們上面做過的所有配置,還能發(fā)現(xiàn)一個配置錯誤。我們在 Prettier 配置中指定了縮進(jìn)距離。因為這也是一個瀏覽器相關(guān)的配置,所以應(yīng)該將其移至 .editorconfig。
.editorConfig
tab_width 4
現(xiàn)在我們刪除了已經(jīng)空白的 .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"
);
代碼縮進(jìn)變成了 4 個空格。現(xiàn)在,無論你在何時用編輯器打開一個新文件,都會應(yīng)用這個配置,Prettier 同樣也會遵循。
--End--
查看更多前端好文
請搜索 云前端 或 fewelife 關(guān)注公眾號
轉(zhuǎn)載請注明出處
