パフォーマンスのトラブルシューティング
型認識リンティングドキュメントで述べたとおり、型認識リンティングを使用している場合、リンティング時間はビルド時間にほぼ等しくなるはずです。
それよりもはるかに時間がかかる場合は、よくある原因がいくつか考えられます。
tsconfigでの広範囲のインクルード
型認識リンティングの使用時には、1つ以上のtsconfigを提供します。その後、完全な型情報を取得できるようにすべてのファイルを事前解析します。
include
で非常に長いグローブ(**/*
など)を提供すると、この事前解析に予想以上に多くのファイルが含まれる可能性があります。さらに、tsconfigでinclude
を提供しない場合は、最も長いグローブが提供された場合と同じです。
長いグローブにより、ビルドアーティファクトのようなものがTypeScriptによって解析され、パフォーマンスに大きな影響を与えることになります。リンティング対象とするフォルダに特化したグローブのみを提供するようにしてください。
ESLint オプションにおける広範囲の含み
ESLint コマンドに tsconfig.json
パスを指定した場合も、予想を超えるディスク I/O が発生する可能性があります。**
を使用してすべてのフォルダーを再帰的にチェックするグローブの代わりに、一度に *
を 1 つだけ使用するパスを使用してください。
- フラット構成
- レガシー構成
// @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 パターン in パーサーのオプション「project」がリンティングを遅くする をご覧ください。
indent
/ @typescript-eslint/indent
ルール
このルールは、コードベースが一定のインデントパターンに従っていることを保証するのに役立ちます。ただし、ファイルのすべてのトークンにたくさんの計算が必要になります。大規模なコードベースでは、これらが積み重なり、パフォーマンスに大きな影響を与える可能性があります。
このルールの使用は推奨しません。代わりに prettier
などのツールを使用して標準化されたフォーマットを適用してください。
詳細については フォーマットに関するドキュメント を参照してください。
eslint-plugin-prettier
このプラグインは、lint 時に Prettier のフォーマットの問題を明らかにし、コードが常にフォーマットされるようにします。ただし、これにはかなりのコストがかかります。差異があるかどうかを確認するには、lint が実行されているすべてのファイルで Prettier フォーマットを実行する必要があります。つまり、各ファイルは 2 回解析されます。1 回は ESLint で、もう 1 回は Prettier でです。大規模なコードベースではこれが積み重なる可能性があります。
このプラグインを使用する代わりに、ファイルが正しくフォーマットされていないかどうかを検出するために Prettier の --check
フラグを使用することをお勧めします。たとえば、CI は以下コマンドを自動的に実行するように設定されており、フォーマットされていない PR をブロックします。
- npm
- Yarn
- pnpm
npm run prettier --check .
yarn prettier --check .
pnpm run prettier --check .
詳細については Prettier の --check
ドキュメント を参照してください。
eslint-plugin-import
これは、このプロジェクトで私たち自身も使用しているもう 1 つの優れたプラグインです。ただし、プラグインが独自の解析とファイル追跡を行うため、lint が非常に遅くなる可能性のあるルールがいくつかあります。この二重の解析は、大規模なコードベースで積み重なります。
単一ファイル静的解析を実行するルールはたくさんありますが、以下の推奨事項を提供します。
TypeScript では標準タイプのチェックと同じチェックが提供されるため、次のルールは使用しないことをお勧めします。
import/named
import/namespace
import/default
import/no-named-as-default-member
import/no-unresolved
(require
よりもimport
を使用している場合)
次のルールは TypeScript に同等のチェックがないため、ローカルのパフォーマンスの負担を軽減するために CI/プッシュ時のみ実行することをお勧めします。
import/no-named-as-default
import/no-cycle
import/no-unused-modules
import/no-deprecated
import/extensions
拡張機能を強制的に使用
ファイル拡張子の使用を常に強制的に行い、moduleResolution
node16
または nodenext
を使用していない場合は、他に優れた選択肢はなく、import/extensions
リントルールを使用し続ける必要があります。
ファイル拡張子の使用を常に強制的に行い、moduleResolution
node16
または nodenext
を使用している場合は、TypeScript が拡張子のインクルードを自動的に強制するため、リントルールを使用する必要はありません。
拡張機能を使用しない強制
基本的に import/extensions
は今回のユースケースでは高速であるべきに思えますが、このルールは単なる純粋な AST チェックではありません。名前の一部として拡張子を含むモジュールをインポートするケースでは誤検出しないように、モジュールをディスク上で解決する必要があります (例: foo.js
は node_modules/foo.js/index.js
に解決されるので、.js
が必要)。このディスクルックアップにはコストがかかり、ルールが遅くなります。
プロジェクトがファイル名に拡張子を持つ npm
パッケージを使用せず、さらに bar.js.ts
のように 2 つの拡張子を使用してファイルに名前を付けていない場合、この追加コストはおそらく無駄であり、no-restricted-syntax
リントルールを使用して、はるかに簡単なチェックを使用できます。
ディスクルックアップを行わないため、以下の構成は 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'),
],
},
};