
在我之前的文章中,我談到了 eslintrc 配置系統如何透過一系列小的、漸進式的變更變得比必要的更複雜。另一方面,flat config 系統從一開始就被設計得在許多方面都更簡單。我們汲取了 ESLint 開發前六年的所有經驗,提出了一種整體的配置方法,該方法結合了 eslintrc 的優點,並結合了其他 JavaScript 相關工具處理配置的方式。結果是,對於現有的 ESLint 用戶來說,它應該會感到熟悉,並且比以前的功能強大得多。
文件: 在官方文件中閱讀更多關於 flat config 系統的資訊。
flat config 的目標
為了替 flat config 的變更奠定基礎,我們設定了幾個目標
- 合理的預設值 - 人們編寫 JavaScript 的方式在過去九年中發生了很大變化,我們希望新的配置系統能夠反映我們目前的現實,而不是 ESLint 首次發布時的現實。
- 定義配置的單一方法 - 我們不希望人們再有多種方法來做同樣的事情。對於任何給定專案,應該只有一種定義配置的方法。
- 規則配置應保持不變 - 我們認為規則的配置方式已經運作良好,因此為了更容易過渡到 flat config,我們不想對規則配置進行任何變更。相同的
rules
鍵可以在 flat config 中以相同的方式使用。 - 對所有事物使用原生載入 - 我們對 eslintrc 最遺憾的事情之一是以自訂方式重新創建了 Node.js
require
解析。這是複雜性的重要來源,事後看來是不必要的。展望未來,我們希望直接利用 JavaScript 執行時期的載入能力。 - 更好地組織頂層鍵 - 自 ESLint 發布以來,eslintrc 頂層的鍵數量已大幅增加。我們需要查看哪些鍵是必要的,以及它們彼此之間有何關聯。
- 現有的外掛程式應能運作 - ESLint 生態系統中充滿了數百個外掛程式。重要的是這些外掛程式要能繼續運作。
- 向後相容性應是優先事項 - 即使我們正在轉向新的配置系統,我們也不希望拋下所有現有的生態系統。特別是,我們希望讓可共享的配置能夠盡可能地繼續運作。雖然我們知道 100% 相容性可能不切實際,但我們希望盡力確保現有的可共享配置能夠運作。
基於這些目標,我們提出了新的 flat config 系統。
為 linting 設定合理的預設值
當 ESLint 首次創建時,ECMAScript 5 是最新版本的 JavaScript,大多數檔案都是以「共享一切」腳本或 CommonJS 模組(用於 Node.js)編寫的。ECMAScript 6 即將到來,但沒有人知道它會多快被實作,或者模組 (ESM) 最終將如何被使用。因此,ESLint 的預設值是假設所有檔案都是 ECMAScript 5。我們最終使用了 ecmaVersion
解析器配置,以便人們可以在準備好時選擇加入 ECMAScript 6。
快轉到 2022 年:ECMAScript 不斷發展,ESM 是每個人都在使用的標準模組格式。我們無法真正更改 eslintrc 的預設設定,而不會可能破壞許多現有的配置,但我們絕對可以透過 flat config 進行更改。
Flat config 具有以下預設值
- 所有 JavaScript 檔案的
ecmaVersion: "latest"
- 沒錯,預設情況下,所有 JavaScript 檔案都將設定為最新版本的 ECMAScript。這模仿了 JavaScript 執行時期的運作方式,即每次升級都意味著您選擇加入最新和最棒版本的 JavaScript。此變更應意味著您可能不必在配置中手動設定ecmaVersion
,除非您想要由於執行時期限制而強制執行先前的版本。如果需要,您仍然可以將ecmaVersion
設定為最低3
。 - 所有
.js
和.mjs
檔案的sourceType: "module"
- 預設情況下,flat config 假設您正在編寫 ESM。如果不是,您可以隨時將sourceType
設定回"script"
。 .cjs
檔案的sourceType: "commonjs"
- 我們仍然處於過渡時期,許多 Node.js 程式碼都是以 CommonJS 編寫的。為了支援這些使用者,我們新增了一個新的sourceType
"commonjs"
,它可以為該環境正確配置所有內容。- ESLint 搜尋
.js
、.mjs
和.cjs
檔案 - 使用 eslintrc,當您在命令列上傳遞目錄名稱時,ESLint 僅搜尋.js
檔案,並且您需要使用--ext
標誌來定義更多檔案。使用 flat config,最常見的三種 JavaScript 檔案副檔名都會自動搜尋。
我們對這些新的預設值感到非常興奮,因為我們認為這將有助於人們更快、更少困惑地開始使用 ESLint。
新的配置檔案:eslint.config.js
與 eslintrc 允許在多個位置、多種配置檔案格式,甚至基於 package.json
的配置不同,flat config 對您專案的所有配置只有一個位置:eslint.config.js
檔案。透過將配置限制在一個位置和一種格式,我們可以利用 JavaScript 執行時期的載入機制,並避免自訂解析配置檔案的需求。
當使用 ESLint CLI 時,它會從目前工作目錄中搜尋 eslint.config.js
,如果找不到,將繼續在目錄的祖先目錄中搜尋,直到找到該檔案或到達根目錄。該 eslint.config.js
檔案包含該次 ESLint 執行的所有配置資訊,因此與 eslintrc 相比,它大大減少了所需的磁碟存取,eslintrc 必須檢查從 linted 檔案位置到根目錄的每個目錄,以尋找任何其他配置檔案。
此外,使用 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
配置作為 flat config 的基礎。
每個配置物件都可以有選用的 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
,你好 flat cascade
雖然我們想要擺脫基於目錄的配置級聯,但 flat config 實際上仍然在您的 eslint.config.js
檔案中直接定義了一個 flat cascade。在陣列內部,ESLint 找到所有與正在 linting 的檔案匹配的配置物件,並以與 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
檔案。當 linting 以 .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
這樣的環境,才能同時啟用您想要的語法並確保正確的全域變數可用。
在 flat config 中,我們將所有與 JavaScript 評估相關的鍵都移到了一個新的頂層鍵,稱為 languageOptions
。
在 flat config 中設定 ecmaVersion
最大的變更是我們將 ecmaVersion
從 parserOptions
中移出,直接移到 languageOptions
中。這更好地反映了此鍵的新行為,即根據指定的 ECMAScript 版本啟用語法和全域變數。例如
export default [
{
files: ["**/*.js"],
languageOptions: {
ecmaVersion: 6
}
}
];
此配置已將 ecmaVersion
降級為 6
。這樣做可確保所有 ES6 語法和所有 ES6 全域變數都可用。(任何使用的自訂解析器仍將收到此 ecmaVersion
值。)
在 flat config 中設定 sourceType
接下來,我們將 sourceType
移到 languageOptions
中。與 ecmaVersion
類似,此鍵不僅影響檔案的解析方式,還影響 ESLint 評估其範圍結構的方式。我們保留了用於 ESM 的傳統 "module"
和用於腳本的 "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,最終,我們搞砸了。
對於 flat config,我們決定完全移除 env
鍵。為什麼?因為不再需要它了。我們為在 Node.js 中使用而掛鉤到環境的所有自訂功能現在都由 sourceType: "commonjs"
涵蓋,因此剩下的只是讓環境管理全域變數。在核心中執行此操作對 ESLint 沒有意義,因此我們將此責任交還給您。
多年前,我們與 Sindre Sorhus 合作創建了 globals
套件,該套件從 ESLint 中提取了所有環境資訊,以便其他套件可以使用它。然後,ESLint 使用 globals
作為其環境的來源。
使用 flat config,您可以直接使用 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 的優勢在於個人和公司維護的外掛程式生態系統,以自訂其 linting 策略。因此,我們希望確保現有的外掛程式繼續運作而無需修改,並允許外掛程式執行過去從未能夠執行的操作。
從表面上看,在 flat config 中使用外掛程式看起來與在 eslintrc 中使用外掛程式非常相似。最大的區別在於 eslintrc 使用字串,而 flat config 使用物件。您無需指定外掛程式的名稱,而是直接匯入外掛程式並將其放入 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 應從中載入自訂規則的目錄。這兩種方法都需要額外的工作來設定,並且經常引起使用者的挫敗感。
使用 flat config,您可以直接在配置檔案中載入自訂規則。因為外掛程式現在是配置中的直接物件,所以您可以輕鬆建立僅存在於配置檔案中的執行時期外掛程式,例如
import myrule from "./custom-rules/myrule.js";
export default [
{
files: ["**/*.js"],
plugins: {
custom: {
rules: {
myrule
}
}
},
rules: {
"custom/myrule": "error"
}
}
];
在這裡,自訂規則作為 myrule
匯入,然後建立一個名為 custom
的執行時期外掛程式,以將該規則作為 custom/myrule
提供給配置。
因此,一旦過渡到 flat config 完成,我們將移除 --rulesdir
。
處理器的工作方式與 eslintrc 類似
頂層的 processor
鍵的運作方式與 eslintrc 大致相同,主要用例是使用在外掛程式中定義的處理器,例如
import markdown from "eslint-plugin-markdown";
export default [
{
files: ["**/*.md"],
plugins: {
markdown
},
processor: "markdown/markdown"
}
];
此配置物件指定名為 "markdown"
的外掛程式中包含一個名為 "markdown"
的處理器,並將該處理器應用於所有以 .md
結尾的檔案。
flat config 中的一個新增功能是,processor
現在也可以是一個物件,其中包含 preprocess()
和 postprocess()
方法。
有組織的 linter 選項
在 eslintrc 中,有幾個直接與 linter 運作方式相關的鍵,即 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
類別,可讓您輕鬆地在 flat config 檔案中繼續使用 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 檔案,同時針對 flat config 的使用進行最佳化。我們預想這是一個必要的過渡步驟,以允許生態系統緩慢地轉換為 flat config。
結論
團隊花費了很長時間設計 flat config,使其既能讓現有使用者感到熟悉,又能提供惠及所有人的新功能。我們保留了規則、設定和處理器等內容,同時擴展了外掛程式、語言選項和 linter 選項等內容,使其更加統一。我們認為 flat config 在這兩個極端之間找到了良好的平衡,並且一旦新的配置系統普遍可用,您將更享受使用 ESLint。同時,相容性實用程式將允許您繼續使用現有的共享配置。
在本部落格系列的下一部分中,您將了解如何立即開始使用 flat config。
已更新 (2024-08-12): 更新了 JavaScript 的預定義 ESLint 配置。