前言#
這不是一篇比較 ESLint 和其他相關工具優劣進行拉踩的文章,而是介紹我選擇使用 ESLint 來進行代碼檢查和格式化的原因。雖然不可避免地會提到其他工具,但我覺得每個流行工具的存在都是因為它們有自己的特點和優勢。作為用戶,我們只需要根據自己的需求和喜好來選擇適合自己的工具。如果工具存在問題,我們可以通過反饋和貢獻來幫助改進。
我看中的 ESLint 的優勢#
更不固執己見的格式化#
關於為什麼不使用如 Prettier 或者 dprint 之類的格式化工具,我覺得 Anthony Fu 的 Why I don't use Prettier 一文已經說的足夠清楚。在此,我補充一點自己的想法。
配置 printWidth
並不能告訴 Prettier 我們希望在何時希望換行。你通過增加 printWidth
的值來迴避一個不該換行的長字符串變量,就可能會導致一個你期望會換行的多參數函數的參數沒有被正確地換行。// prettier-ignore
的問題和 // @ts-ignore
一樣,我們雖然成功告訴工具這裡不要換行,但同時也失去了這裡本可以應用的其它格式化規則。
儘管 Prettier 的哲學是用戶不需要考慮格式化的配置選項,讓你把代碼放心的交給它來變得漂亮。但實際上現有的可配置選項可能是 Prettier 能成為現在 js 社區最流行的格式化工具的原因之一。想像一下,你會使用一個不能控制是用 tab
還是 space
來縮進的格式化工具嗎?考慮到 tab
和 space
之爭 基本上是五五開的情況,如果沒有這個選項的話,Prettier 至少會少一半的用戶?
此外,對於相對沒那麼多爭論的選項,Prettier 的固執己見可能會給用戶帶來一些困擾。例如 Prettier 會強制在文件末尾保留一行空行,這是 不可配置的。儘管這個行為是大多數人所支持且有利於 git 等工具的,但對於無法忍受的人來說,他們不得不尋找一些 hack 的手段或者尋找別的工具來替代 Prettier。
是的,我們有其它格式化工具可以選擇,如 dprint,它的性能表現更好且提供更多的配置選項。但同樣的,它仍然沒有解決何時代碼應該換行的問題。這不是個 bug,而是它們本身的工作模式所決定的。此外,即便 dprint 配置選項更多,但你永遠不可能滿足所有人的需求,在 ESLint 的世界裡,你可以輕鬆地通過配置現有規則的選項或是寫插件來實現你自己的需求。
當我注意到 ESLint Stylistic 現有的規則貌似沒有處理 jsx 中一行多個參數間的空格時,我只需要把當前的代碼放到 typescript-eslint 的 playground 中,然後根據右邊的的 ESTree 展示的數據結構來寫插件,就可以 實現自己的需求。
可拓展性#
通過使用性能更好的語言來重寫原本用 js 寫的工具,可以獲得更好的性能。但是,這通常是有代價的。使用 Rust 來編寫的 lint 工具一般無法輕鬆的自定義規則,所以只有在 ESLint 社區中極其流行的規則能夠得到移植。這意味著我們無法在第一時間擁有官方推出的 ESLint 插件,如隨著 React Compiler 發布的 eslint-plugin-react-compiler;也無法擁有 ESLint 社區中小眾但十分有用的插件,如 ESLint Plugin Command。
除了擴展規則之外,ESLint 還支持擴展 lint 的語言,如今你可以輕鬆的在 Vue,JSON,YAML,Toml,Markdown,Astro,Svelte 等等語言中使用 ESLint 來進行代碼檢查。但對於使用原生語言所編寫的 lint 工具來說,他們通常只能優先支持最主流的語言。比如如果你使用 Biome,那麼在你寫 Vue 項目時你就暫時無法使用它,還是需要回到 ESLint。我不太喜歡不同項目中使用不同工具導致的不一致性。
優秀的生態#
這一節中,我並不想再提及 ESLint 的插件生態有多麼豐富,我們來聊聊 ESLint VSCode 插件。除了我們每天使用的保存自動修復功能,它還提供了一些其它有用的功能。
在使用 ESLint 時,有些規則我們希望能自動修復,但卻不是在保存時馬上修復。比如移除未使用的 import,又或是將 let 馬上換成 const(我們可能很快就會對變量重新賦值)。此時,我們可以使用 eslint.codeActionsOnSave.rules
設置。
{
"eslint.codeActionsOnSave.rules": [
"!prefer-const",
"!unused-imports/no-unused-imports",
"*"
]
}
配合 lint-staged
與 simple-git-hooks
,我們可以實現在編輯器中忽略部分規則,然後在 commit 之前將其自動修復。
另一個十分有用的設置是 eslint.rules.customizations
。前面我們關閉了部分規則的自動修復,但是編輯器還是會將其顯示為錯誤。通過這個設置,我們可以將這些規則的嚴重程度降低或是徹底關閉。
{
"eslint.rules.customizations": [
{ "rule": "@stylistic/*", "severity": "off" },
{ "rule": "@stylistic/no-tabs", "severity": "default" },
{ "rule": "@stylistic/max-statements-per-line", "severity": "default" },
{ "rule": "antfu/consistent-list-newline", "severity": "off" },
{ "rule": "prefer-const", "severity": "off" },
{ "rule": "unused-imports/no-unused-imports", "severity": "off" },
{ "rule": "simple-import-sort/*", "severity": "off" }
]
}
這個設置對於能將 ESLint 作為一個代碼格式化工具十分有用,我們可以直接關閉 ESLint Stylistic 中的規則在編輯器中的錯誤顯示,同時保留它們的自動修復功能。在 下個版本 中,它還允許你調整全部可自動修復的規則的嚴重程度。
類型感知的 lint 規則#
基於 Rust 的 lint 工具很快,但是卻沒有使用類型信息進行 lint 的能力,Josh Goldberg 在 Rust-Based JavaScript Linters: Fast, But No Typed Linting Right Now 一文中進行了十分詳細的介紹。
oxlint 最近進行了嘗試,但是這似乎也導致了它回到了 JavaScript 的速度。
Biome 開始準備實現 Type-aware linter。
我不那麼在乎的 ESLint 的 “缺點”#
性能#
你可以看到非常多的 benchmark 展現出 oxlint,biome 等工具的性能遠遠超過 ESLint。但是,從我的使用場景來看,性能問題好像沒有那麼重要。
在編輯器中的實時 lint 和 precommit 時的 lint 一般都只需要對少量文件進行檢查,我們可以將完整的 lint 過程交給 CI。CI 並不會阻塞我們本地的開發流程,我們只需要在 CI 報錯時再在本地對特定文件進行 lint 即可。
我遇到的在編輯器中依然會出現性能問題的情況是,當項目逐漸變大,我們開啟基於類型檢查的規則會導致編輯器的保存操作出現明顯的延遲。但這時我們也不用完全妥協,關閉基於類型檢查的規則。ESLint Flat Config 的靈活性允許我們在編輯器中關閉特定的規則。在終端或是 CI 環境中,我們依然可以進行完整的 lint。
對於我自己的 ESLint Config,可以使用如下的配置。
import defineConfig from "eslint-config-hyoban";
const isInEditor = !!(
(process.env.VSCODE_PID ||
process.env.VSCODE_CWD ||
process.env.JETBRAINS_IDE ||
process.env.VIM) &&
!process.env.CI
);
export default defineConfig({
typeChecked: isInEditor ? false : "essential",
});
你也可以嘗試一下 tsslint,它是一個與 TypeScript 語言服務器無縫集成的輕量級檢查工具。
非官方推薦#
ESLint 和 typescript-eslint 官方都決定 廢棄格式化代碼相關的規則,同時他們並 不推薦使用 ESLint 來進行格式化,而是推薦配合 Prettier 等格式化工具來使用。但實際上我並不覺得這是一個問題,廢棄這些規則並將其轉到社區來維護實際上是個好事,我們現在有 ESLint Stylistic 這樣的開箱即用的工具,並且它表現的非常好。
配置複雜,升級麻煩#
最近的 ESLint 9.0 讓很多人覺得 ESLint 的大版本升級十分複雜,主要遇到的問題是新的的配置文件格式導致我們需要重寫寫配置,以及 API 的 breaking change 導致很多使用的插件在 9.0 中無法使用。
但是,我覺得這是一個短暫的問題,新的配置文件帶來了很多有用的新工具和用法,這是利大於弊的。如 ESLint Config Inspector 可以幫助我們更好編寫和測試配置文件;可以根據項目安裝的依賴來動態的生成配置(只在安裝了 react 的項目中開啟 react hooks 相關的規則)。
API 的 breaking change 帶來的問題也可以通過多種方式來解決:
- 給上游插件寫 PR 來適配 ESLint v9,很多情況下我們只需要修改幾行代碼,在 ESLint v9 中使用新的 API,保留舊的 API 作為兼容
- 暫時使用 ESLint v8,等待插件插件適配(我們依然可以使用 Flat Config)
- 使用官方推出的 ESLint Compatibility Utilities 來幫助我們升級
结语#
需要再次強調的是,這只是我的個人感受和觀點,它可能存在考慮得不對的地方,歡迎你和我交流,也歡迎你分享你的觀點。
如果你現在想要嘗試 ESLint All In One 的話,我十分推薦你從 Anthony Fu 的 ESLint config 起手,它支持非常多的語言和框架,你也可以在其基礎之上靈活的進行配置。
如果你主要寫 TypeScript 和 React 的話,也推薦你使用我的 ESLint config 試試。我配置規則的哲學是儘可能使用插件預設的規則,在此基礎上按照我的習慣進行調整,同時提供 strict
和 typeChecked
選項進行不同級別的調整。