
在我之前的文章中,我談到 eslintrc 設定系統如何透過一系列小的、漸進式的變更而變得比必要更複雜。另一方面,扁平化設定系統從一開始就被設計成在多方面都更簡單。我們汲取了過去六年 ESLint 開發的所有經驗,提出了一種整體性的設定方法,結合了 eslintrc 的優點以及其他與 JavaScript 相關的工具處理設定的方式。其成果是讓現有的 ESLint 使用者感到熟悉,並且比以前的功能更強大。
文件:請在官方文件中閱讀更多關於扁平化設定系統的資訊。
扁平化設定的目標
為了設定扁平化設定中的變更,我們設定了以下幾個目標:
- 邏輯預設值 - 人們編寫 JavaScript 的方式在過去九年中發生了很大變化,我們希望新的設定系統能反映我們目前的現況,而不是 ESLint 首次發佈時的狀況。
- 定義設定的一種方式 - 我們不希望人們有多種方式可以做同一件事。對於任何給定的專案,應該只有一種定義設定的方式。
- 規則設定應保持不變 - 我們認為規則的設定方式已經運作良好,因此為了更容易轉換到扁平化設定,我們不希望對規則設定進行任何變更。相同的
rules
鍵可以在扁平化設定中以相同的方式使用。 - 所有內容都使用原生載入 - 我們對 eslintrc 最大的遺憾之一是以自訂方式重新建立 Node.js 的
require
解析。這是一個複雜的重大來源,而且事後來看是不必要的。展望未來,我們希望直接利用 JavaScript 執行環境的載入能力。 - 更妥善組織的頂層鍵 - 自 ESLint 發佈以來,eslintrc 頂層的鍵數量已大幅增加。我們需要檢視哪些鍵是必要的,以及它們之間是如何相關的。
- 現有的外掛程式應該可以運作 - ESLint 生態系統充滿了數百個外掛程式。這些外掛程式繼續運作非常重要。
- 向後相容性應該是優先考量 - 即使我們要轉向新的設定系統,我們也不希望拋下所有現有的生態系統。特別是,我們希望有方法讓可共享的設定盡可能地繼續運作。雖然我們知道 100% 相容可能不切實際,但我們希望盡最大努力確保現有的可共享設定能夠運作。
有了這些目標,我們提出了新的扁平化設定系統。
設定程式碼檢查的邏輯預設值
當 ESLint 首次建立時,ECMAScript 5 是最新的 JavaScript 版本,大多數檔案都寫為「共享一切」的腳本或 CommonJS 模組(適用於 Node.js)。ECMAScript 6 即將問世,但沒有人知道它會多快實作或模組 (ESM) 會如何被使用。因此,ESLint 的預設值是假設所有檔案都是 ECMAScript 5。我們最終有了 ecmaVersion
解析器設定,讓使用者可以在準備好時選擇加入 ECMAScript 6。
快轉到 2022 年:ECMAScript 不斷發展,而 ESM 是每個人都在使用的標準模組格式。我們無法在不破壞許多現有設定的情況下真正更改 eslintrc 的預設設定,但我們絕對可以透過扁平化設定進行變更。
扁平化設定具有以下預設值:
- 所有 JavaScript 檔案的
ecmaVersion: "latest"
- 沒錯,預設情況下,所有 JavaScript 檔案都將設定為最新版本的 ECMAScript。這模仿了 JavaScript 執行環境的運作方式,即每次升級都表示您選擇加入最新、最棒的 JavaScript 版本。此變更應表示您可能不需要在設定中手動設定ecmaVersion
,除非您想因執行環境的限制而強制使用先前的版本。如有必要,您仍然可以將ecmaVersion
設定為最低3
。 - 所有
.js
和.mjs
檔案的sourceType: "module"
- 預設情況下,扁平化設定會假設您正在編寫 ESM。如果不是,您可以隨時將sourceType
設定回"script"
。 .cjs
檔案的sourceType: "commonjs"
- 我們仍然處於轉換期,其中許多 Node.js 程式碼都是以 CommonJS 編寫的。為了支援這些使用者,我們新增了新的sourceType
:"commonjs"
,它會針對該環境正確設定所有內容。- ESLint 會搜尋
.js
、.mjs
和.cjs
檔案 - 使用 eslintrc 時,當您在命令列上傳遞目錄名稱時,ESLint 只會搜尋.js
檔案,並且您需要使用--ext
旗標來定義更多檔案。使用扁平化設定時,會自動搜尋所有三個最常見的 JavaScript 副檔名。
我們對這些新的預設值感到非常興奮,因為我們認為這將有助於人們更快且更少混淆地開始使用 ESLint。
新的設定檔:eslint.config.js
與 eslintrc 允許多個設定檔位於多個位置、多種設定檔格式,甚至是以 package.json
為基礎的設定相反,扁平化設定只有一個位置用於您的所有專案設定:eslint.config.js
檔案。透過將設定限制在一個位置和一種格式,我們可以直接利用 JavaScript 執行環境的載入機制,並避免自訂剖析設定檔的需求。
當使用 ESLint CLI 時,它會從目前的工作目錄搜尋 eslint.config.js
,如果找不到,將會繼續向上搜尋目錄的祖先,直到找到該檔案或到達根目錄為止。該 eslint.config.js
檔案包含該次 ESLint 執行所需的所有設定資訊,因此與 eslintrc 相比,它大幅減少了所需的磁碟存取次數,eslintrc 必須從程式碼檢查檔案的位置到根目錄檢查每個目錄是否有其他設定檔。
此外,使用 JavaScript 檔案讓我們可以依靠使用者載入他們的設定檔可能需要的其他資訊。您現在可以直接使用 import
和 require
來匯入那些其他資源,而無需透過名稱來載入 extends
和 plugins
。以下是 eslint.config.js
檔案的範例:
export default [
{
files: ["**/*.js"],
rules: {
"semi": "error",
"no-unused-vars": "error"
}
}
];
eslint.config.js
檔案會匯出設定物件的陣列。請繼續閱讀以了解更多關於此範例的資訊。
處處皆有基於 Glob 的設定
雖然 eslintrc 中的 overrides
鍵是許多複雜性的來源,但有一件事非常清楚:人們非常喜歡在設定檔中使用 glob 模式定義設定。因為我們想要消除 eslintrc 的設定階層,所以我們必須使用 glob 模式來啟用相同類型的設定覆寫。我們使用 overrides
設定作為扁平化設定的基礎。
每個設定物件都可以具有選用的 files
和 ignores
鍵,指定基於 minimatch 的 glob 模式來比對檔案。只有當檔名符合 files
中的模式時(或者如果沒有 files
鍵,則會比對所有檔案),設定物件才會套用至該檔案。ignores
鍵會從 files
清單中篩除檔案,因此您可以限制設定物件套用至哪些檔案。例如,您的測試檔案可能與您的原始碼檔案位於同一個目錄中,而您希望設定物件僅套用至原始碼檔案。您可以這樣做:
export default [
{
files: ["**/*.js"],
ignores: ["**/*.test.js"],
rules: {
"semi": "error",
"no-unused-vars": "error"
}
}
];
在此,設定物件會比對所有 JavaScript 檔案,然後篩除任何以 .test.js
結尾的檔案。
如果您想要完全忽略檔案呢?您可以透過指定只有 ignores
鍵的設定物件來執行此操作,如下所示:
export default [
{
ignores: ["**/*.test.js"]
},
{
files: ["**/*.js"],
rules: {
"semi": "error",
"no-unused-vars": "error"
}
}
];
透過此設定,所有以 .test.js
結尾的 JavaScript 檔案都會被忽略。您可以將此視為 eslintrc 中 ignorePatterns
的等效項,但具有 minimatch 模式。
告別 extends
,迎接扁平化階層
雖然我們想擺脫基於目錄的設定階層,但扁平化設定實際上在您的 eslint.config.js
檔案中仍然有一個直接定義的扁平化階層。在陣列內部,ESLint 會找到所有與正在程式碼檢查的檔案相符的設定物件,並將它們合併在一起,方式與 eslintrc 大致相同。唯一的真正差異在於合併是從陣列頂部向下到底部進行,而不是使用目錄結構中的檔案。例如:
export default [
{
files: ["**/*.js", "**/*.cjs"],
rules: {
"semi": "error",
"no-unused-vars": "error"
}
},
{
files: ["**/*.js"],
rules: {
"no-undef": "error",
"semi": "warn"
}
}
];
此設定有兩個具有重疊 files
模式的設定物件。第一個設定物件適用於所有 .js
和 .cjs
檔案,而第二個設定物件僅適用於 .js
檔案。當程式碼檢查以 .js
結尾的檔案時,ESLint 會合併這兩個設定物件,以建立該檔案的最終設定。因為第二個設定將 semi
的嚴重性設定為 "warn"
,因此它會優先於第一個設定中設定的 "error"
。當發生衝突時,最後一個相符的設定始終優先。
這對於可共享設定的意義在於,您可以將它們直接插入到陣列中,而不是使用 extends
,例如:
import customConfig from "eslint-config-custom";
export default [
customConfig,
{
files: ["**/*.js", "**/*.cjs"],
rules: {
"semi": "error",
"no-unused-vars": "error"
}
},
{
files: ["**/*.js"],
rules: {
"no-undef": "error",
"semi": "warn"
}
}
];
這裡,customConfig
會先被插入到陣列中,使其成為此檔案的配置基礎。後續的每個配置物件都會基於這個基礎來建立,以產生指定 JavaScript 檔案的最終配置。
重新構想的語言選項
ESLint 一直以來都有一些奇怪的選項組合,會影響 JavaScript 的解析方式。像是頂層的 globals
鍵可以修改可用的全域變數,還有 ecmaVersion
和 sourceType
作為 parserOptions
,更不用說還有 env
用於加入更多全域變數。最令人困惑的可能是,你必須同時設定 ecmaVersion
並新增一個環境,像是 es6
,才能啟用你想要的語法,並確保可以使用正確的全域變數。
在扁平配置中,我們將所有與 JavaScript 評估相關的鍵移到一個新的頂層鍵,稱為 languageOptions
。
在扁平配置中設定 ecmaVersion
最大的改變是我們將 ecmaVersion
從 parserOptions
中移出,直接放到 languageOptions
中。這樣更能反映此鍵的新行為,也就是根據指定的 ECMAScript 版本來啟用語法和全域變數。例如:
export default [
{
files: ["**/*.js"],
languageOptions: {
ecmaVersion: 6
}
}
];
這個配置將 ecmaVersion
降級到 6
。這樣做可以確保所有 ES6 語法和所有 ES6 全域變數都可用。(任何使用的自訂解析器仍然會收到這個 ecmaVersion
值。)
在扁平配置中設定 sourceType
接下來,我們將 sourceType
移到 languageOptions
中。與 ecmaVersion
類似,這個鍵不僅影響檔案的解析方式,還會影響 ESLint 如何評估其作用域結構。我們保留了傳統的 "module"
用於 ESM,以及 "script"
用於腳本,並新增了 "commonjs"
,讓 ESLint 知道應該將該檔案視為 CommonJS(這也會啟用 CommonJS 特有的全域變數)。如果你使用 ecmaVersion: 3
或 ecmaVersion: 5
,請務必設定 sourceType: script
,如下所示:
export default [
{
files: ["**/*.js"],
languageOptions: {
ecmaVersion: 5,
sourceType: "script"
}
}
];
告別環境,迎接 globals
eslintrc 中的環境提供了一組已知的全域變數,並且一直是使用者困惑的根源。它們需要保持最新(特別是在 browser
的情況下),而更新需要等待 ESLint 的發布。此外,我們在環境中加入了一些額外的功能,以方便使用 Node.js,最終,我們搞得一團糟。
對於扁平配置,我們決定完全移除 env
鍵。為什麼?因為不再需要它了。我們附加在環境上用於 Node.js 的所有自訂功能現在都由 sourceType: "commonjs"
涵蓋,所以剩下的只是讓環境管理全域變數。讓 ESLint 在核心中執行此操作沒有意義,因此我們將這個責任交還給你。
多年前,我們與 Sindre Sorhus 合作創建了 globals
套件,它從 ESLint 中提取了所有環境資訊,以便其他套件可以使用。然後,ESLint 使用 globals
作為其環境的來源。
使用扁平配置,你可以直接使用 globals
套件,隨時更新它,以獲得環境過去提供的所有相同功能。例如,以下是如何在你的配置中加入瀏覽器全域變數:
import globals from "globals";
export default [
{
files: ["**/*.js"],
languageOptions: {
globals: {
...globals.browser,
myCustomGlobal: "readonly"
}
}
}
];
languageOptions.globals
鍵的工作方式與 eslintrc 中的相同,只是現在你可以使用 JavaScript 動態插入你想要的任何全域變數。
自訂解析器和解析器選項大致相同
parser
和 parserOptions
鍵現在已移到 languageOptions
鍵中,但它們的工作方式與 eslintrc 中的大致相同,只有兩個特定的差異:
- 你現在可以直接將解析器物件插入到配置中。
- 解析器現在可以與外掛程式捆綁在一起,並且你可以為
parser
指定一個字串值,以使用外掛程式中的解析器。(在下一節中會更詳細地描述。)
以下是一個使用 Babel ESLint 解析器的範例:
import babelParser from "@babel/eslint-parser";
export default [
{
files: ["**/*.js", "**/*.mjs"],
languageOptions: {
parser: babelParser
}
}
];
這個配置確保將使用 Babel 解析器,而不是預設解析器,來解析所有以 .js
和 .mjs
結尾的檔案。
你也可以使用 parserOptions
鍵,以與 eslintrc 中相同的方式,直接將選項傳遞給自訂解析器:
import babelParser from "@babel/eslint-parser";
export default [
{
files: ["**/*.js", "**/*.mjs"],
languageOptions: {
parser: babelParser,
parserOptions: {
requireConfigFile: false,
babelOptions: {
babelrc: false,
configFile: false,
// your babel options
presets: ["@babel/preset-env"],
}
}
}
}
];
更強大且可配置的外掛程式
ESLint 的強大之處在於個人和公司維護的外掛程式生態系統,以自訂其程式碼檢查策略。因此,我們希望確保現有的外掛程式在不修改的情況下繼續工作,並允許外掛程式執行過去無法執行的操作。
表面上看,在扁平配置中使用外掛程式看起來與在 eslintrc 中使用外掛程式非常相似。最大的差異在於 eslintrc 使用字串,而扁平配置使用物件。你不是指定外掛程式的名稱,而是直接匯入外掛程式,並將其放入 plugins
鍵中,如以下範例所示:
import jsdoc from "eslint-plugin-jsdoc";
export default [
{
files: ["**/*.js"],
plugins: {
jsdoc
},
rules: {
"jsdoc/require-description": "error",
"jsdoc/check-values": "error"
}
}
];
此配置使用 eslint-plugin-jsdoc
外掛程式,方法是將其匯入為本機 jsdoc
變數,然後將其插入到配置中的 plugins
鍵中。之後,外掛程式中的規則會使用 jsdoc
命名空間來引用。
注意:由於外掛程式現在像任何其他 JavaScript 模組一樣被匯入,因此不再嚴格執行外掛程式套件名稱。你不再需要在套件名稱中包含 eslint-plugin-
作為前綴...但如果你這樣做,我們會很高興。
個人化的外掛程式命名空間
由於你配置中外掛程式的名稱現在與外掛程式套件的名稱分離,你可以選擇任何你想要的名稱,如下列範例所示:
import jsdoc from "eslint-plugin-jsdoc";
export default [
{
files: ["**/*.js"],
plugins: {
jsd: jsdoc
},
rules: {
"jsd/require-description": "error",
"jsd/check-values": "error"
}
}
];
在這裡,外掛程式在配置中被命名為 jsd
,因此規則也使用 jsd
來指示它們來自哪個外掛程式。
從 --rulesdir
到執行階段外掛程式
使用 eslintrc,規則需要由 CLI 直接載入,才能在配置檔案中使用。這表示要將自訂規則捆綁到外掛程式中,或者使用 --rulesdir
標誌來指定 ESLint 應該從哪個目錄載入自訂規則。這兩種方法都需要一些額外的工作才能設定,並且經常讓使用者感到沮喪。
使用扁平配置,你可以直接在配置檔案中載入自訂規則。由於外掛程式現在是配置中的物件,你可以輕鬆建立僅存在於你的配置檔案中的執行階段外掛程式,例如:
import myrule from "./custom-rules/myrule.js";
export default [
{
files: ["**/*.js"],
plugins: {
custom: {
rules: {
myrule
}
}
},
rules: {
"custom/myrule": "error"
}
}
];
在這裡,自訂規則被匯入為 myrule
,然後建立一個名為 custom
的執行階段外掛程式,以將該規則以 custom/myrule
的形式提供給配置。
因此,一旦完成轉換為扁平配置,我們將移除 --rulesdir
。
處理器的工作方式與 eslintrc 類似
頂層的 processor
鍵的工作方式與 eslintrc 中的大致相同,主要用例是使用外掛程式中定義的處理器,例如:
import markdown from "eslint-plugin-markdown";
export default [
{
files: ["**/*.md"],
plugins: {
markdown
},
processor: "markdown/markdown"
}
];
此配置物件指定名為 "markdown"
的外掛程式中包含一個名為 "markdown"
的處理器,並且會將該處理器應用於所有以 .md
結尾的檔案。
扁平配置中的一個新增功能是,processor
現在也可以是一個物件,包含 preprocess()
和 postprocess()
方法。
有組織的程式碼檢查器選項
在 eslintrc 中,有一些直接與程式碼檢查器運作方式相關的鍵,即 noInlineConfig
和 reportUnusedDisableDirectives
。這些已移到新的 linterOptions
鍵中,但工作方式與 eslintrc 中完全相同。以下是一個範例:
export default [
{
files: ["**/*.js"],
linterOptions: {
noInlineConfig: true,
reportUnusedDisableDirectives: true
}
}
];
共用設定完全相同
頂層的 settings
鍵的行為與 eslintrc 中完全相同。你可以定義一個包含鍵值對的物件,該物件應適用於所有規則。以下是一個範例:
export default [
{
settings: {
sharedData: "Hello"
}
}
];
使用預定義的配置
ESLint 有兩個針對 JavaScript 的預定義配置:
js.configs.recommended
- 啟用 ESLint 建議每個人使用的規則,以避免潛在的錯誤js.configs.all
- 啟用 ESLint 附帶的所有規則
若要包含這些預定義的配置,請安裝 @eslint/js
套件,然後在後續的配置物件中對其他屬性進行任何修改:
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
semi: ["warn", "always"]
}
}
];
在這裡,eslint:recommended
預定義配置會先被套用,然後另一個配置物件會為 semi
新增所需的配置。
向後相容性公用程式
如先前所述,我們認為為了簡化轉換,需要與 eslintrc 保持良好的向後相容性。 @eslint/eslintrc
套件提供了一個 FlatCompat
類別,讓你可以在扁平配置檔案中輕鬆繼續使用 eslintrc 風格的共用配置和設定。以下是一個範例:
import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";
// mimic CommonJS variables -- not needed if using CommonJS
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname
});
export default [
// mimic ESLintRC-style extends
...compat.extends("standard", "example"),
// mimic environments
...compat.env({
es2020: true,
node: true
}),
// mimic plugins
...compat.plugins("airbnb", "react"),
// translate an entire config
...compat.config({
plugins: ["airbnb", "react"],
extends: "standard",
env: {
es2020: true,
node: true
},
rules: {
semi: "error"
}
})
];
使用 FlatCompat
類別可以讓你繼續使用所有現有的 eslintrc 檔案,同時針對扁平配置進行最佳化。我們認為這是必要的過渡步驟,以便讓生態系統逐步轉換為扁平配置。
結論
團隊花費了很長的時間來設計扁平配置,使其既能讓現有使用者感到熟悉,又能提供對每個人都有益的新功能。我們保留了規則、設定和處理器等內容,同時擴展了外掛程式、語言選項和程式碼檢查器選項等內容,使其更加統一。我們認為扁平配置在這兩個極端之間找到了良好的平衡,並且一旦新的配置系統普遍可用,你將會更喜歡使用 ESLint。同時,相容性公用程式可讓你繼續使用現有的共用配置。
在本部落格系列的 下一部分中,你將學習如何立即開始使用扁平配置。
已更新 (2024-08-12):已更新 JavaScript 的預定義 ESLint 配置。