效能除錯
正如在 支援型別的 linting 文件 中提到的,如果你使用支援型別的 linting,你的 linting 時間理論上會和你的建置時間相去無幾。
如果你遇到的時間大幅慢於理論值,表示有幾個常見的罪魁禍首。
tsconfig
中的使用範例過廣
當使用支援型別的 linting 時,你需要提供一或多個 tsconfig。我們會先將所有檔案都進行預先解析,以便取得完整而充分的類型資訊。
如果在 include
中提供了範圍非常廣的 glob (像是 **/*
),會導致比你預期還要多得多的檔案包含在這個預先解析中。此外,如果你沒有在 tsconfig 中提供 include
,則等同於提供了範圍最廣的 glob。
廣泛 glob 會讓 TypeScript 分析類似建構程式的項目,這可能會對效能造成很大的影響。請務必提供目標為特別希望執行 linting 檢查的資料夾的 globs。
ESLint 選項中的廣泛 includes
在 ESLint 指令中指定 `tsconfig.json` 路徑也可能會產生比預期還多的磁碟 IO。請優先選用使用單一 `*` 且不使用 `**` 來遞迴檢查所有資料夾的路徑,而非具有 `**` 的 glob。
- 扁平設定檔
- 舊版設定檔
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedRequiringTypeChecking,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: ['./**/tsconfig.json'],
project: ['./packages/*/tsconfig.json'],
},
},
},
);
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./**/tsconfig.json'],
project: ['./packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint'],
root: true,
};
請參閱 分析器選項「專案」中的 Glob 模式會讓 linting 檢查變慢 以取得更多詳細資料。
indent
/ @typescript-eslint/indent
規則
此規則協助確保您的程式碼庫遵循一致的縮排模式。不過,這會涉及檔案中每個單一標記的大量運算。在大型程式碼庫中,這些運算會累加起來並嚴重影響效能。
我們建議您不要使用此規則,而改用類似 prettier
的工具,強制套用標準化格式化。
請參閱我們的 格式化文件 以取得更多資訊。
eslint-plugin-prettier
此外掛元件會在 linting 檢查期間浮出 Prettier 格式化問題,協助確保您的程式碼隨時都有格式化。不過,這會產生相當大的成本 - 為了找出是否存在差異,它必須對每個在執行 linting 檢查的檔案進行一次 Prettier 格式化。這表示每個檔案會被分析兩次 - 一次由 ESLint 分析,另一次由 Prettier 分析。這會為大型程式碼庫累積起來。
我們建議您使用 Prettier 的 --check
旗標,而非使用此外掛元件,以偵測檔案是否未格式化正確。例如,我們的持續整合已設定為自動執行下列指令,這會阻擋未格式化的公關事項
- npm
- Yarn
- pnpm
npm run prettier --check .
yarn prettier --check .
pnpm run prettier --check .
請參閱 Prettier 的 --check
文件 以取得更多詳細資料。
eslint-plugin-import
這是我們在這個專案中自己使用的另一個偉大外掛程式。但是,有幾個規則可能會導致你的 Lints 非常慢,因為它們會導致外掛程式執行自己的剖析和檔案追蹤。對於大型程式庫而言,這個重複剖析會累積起來。
有許多規則會單獨分析檔案,但我們提供以下建議。
我們建議你不需要使用以下規則,因為 TypeScript 提供的檢查屬於標準類型檢查的一部份。
import/named
import/namespace
import/default
import/no-named-as-default-member
import/no-unresolved
(只要你使用import
而非require
)
以下規則在 TypeScript 中沒有等效的檢查,因此我們建議你只在 CI/推送時執行這些規則,以減輕本地的效能負擔。
import/no-named-as-default
import/no-cycle
import/no-unused-modules
import/no-deprecated
import/extensions
強制使用副檔名
如果你想強制執行檔案副檔名的使用,且你沒有使用 moduleResolution
node16
或 nodenext
,那對你而言其實沒有什麼好的替代方案,你應該繼續使用 import/extensions
Lint 規則。
如果你想強制執行檔案副檔名的使用,且你有使用 moduleResolution
node16
或 nodenext
,那你就完全不需要使用 Lint 規則,因為 TypeScript 會自動強制你包含副檔名!
強制不使用副檔名
從表面上來看,import/extensions
似乎應該很快就能用於此用例,但這個規則不僅僅是純 AST 檢查,它必須解析磁碟中的模組,否則它不會對你輸入副檔名作為名稱的模組產生誤報(例如,foo.js
解析為 node_modules/foo.js/index.js
,因此需要 .js
)。這種磁碟查詢成本很高,因此會導致規則執行得較慢。
如果你的 project 沒有使用任何名稱中有副檔名的 npm
套件,你也不會使用兩個副檔名(例如 bar.js.ts
)來命名檔案,那麼這樣的額外成本可能不值得花費,而你可以使用一個更簡單的檢查方式,透過使用 no-restricted-syntax
Lint 規則。
以下設定的運算速度比 import/extensions
快了幾個數量級,因為它不會進行磁碟搜尋,但它會對前述 foo.js
模組等情況產生誤報。
function banImportExtension(extension) {
const message = `Unexpected use of file extension (.${extension}) in import`;
const literalAttributeMatcher = `Literal[value=/\\.${extension}$/]`;
return [
{
// import foo from 'bar.js';
selector: `ImportDeclaration > ${literalAttributeMatcher}.source`,
message,
},
{
// const foo = import('bar.js');
selector: `ImportExpression > ${literalAttributeMatcher}.source`,
message,
},
{
// type Foo = typeof import('bar.js');
selector: `TSImportType > TSLiteralType > ${literalAttributeMatcher}`,
message,
},
{
// const foo = require('foo.js');
selector: `CallExpression[callee.name = "require"] > ${literalAttributeMatcher}.arguments`,
message,
},
];
}
module.exports = {
// ... other config ...
rules: {
'no-restricted-syntax': [
'error',
...banImportExtension('js'),
...banImportExtension('jsx'),
...banImportExtension('ts'),
...banImportExtension('tsx'),
],
},
};