ESLint Flat Configの深淵:プロジェクト固有ルールを「プラグイン化」せず、ネイティブに埋め込むアーキテクチャ
ESLint 9以降、私たちは「Flat Config」という、よりJS/TSのモジュール解決に忠実な世界線へ移行した。かつて `eslint-plugin-local-rules` を使い、`node_modules` の深淵に独自ルールを押し込んでいた日々は終わったのだ。
本稿では、プロジェクト独自の命名規則や、チーム特有のアーキテクチャ強制を、「プラグインという皮」を被せず、ESLintのオブジェクトとして直接注入する、最もモダンで保守性の高いアーキテクチャを解説する。
—
1. なぜ「プラグイン化」を捨てるのか?
従来の `eslint-plugin-local-rules` は、ESLintの内部パス解決(`require.resolve`)に依存しており、ESMベースのFlat Config環境では、依存関係の解決順序やキャッシュの不整合を引き起こすトリガーとなっていた。
アーキテクトの視点:
静的解析は「いかに高速にAST(抽象構文木)を走査するか」が全てだ。独自ルールを外部プラグインとして隔離するオーバーヘッドを排除し、`eslint.config.js` と同じスコープでルールの実装(`create`関数)を定義することで、CI上での初回起動速度とメモリ使用効率を最適化できる。
—
2. 実装パターン:`eslint.config.js` への直接インライン化
ルールを `rules/` ディレクトリ配下の純粋なJSファイルとして作成し、それをFlat Configのプラグインオブジェクトとしてマッピングする。
構成例
project-root/
├── eslint.config.js # エントリポイント
├── rules/
│ └── no-restricted-imports-custom.js # 独自ルール実装
└── tests/
└── no-restricted-imports-custom.test.js # vitest等で単体テスト
独自ルールの実装 (rules/no-restricted-imports-custom.js)
このファイルはESLintのルールAPIの仕様に準拠する。
// プロジェクト固有のインポート制限ルール
module.exports = {
meta: {
type: ‘suggestion’,
docs: { description: ‘チーム独自のインポート規約’ },
schema: [] // ルールにオプションを渡す場合はここで定義
},
create(context) {
return {
ImportDeclaration(node) {
// 例:特定のディレクトリへの直接参照を禁止するロジック
if (node.source.value.includes(‘/internal/’)) {
context.report({ node, message: ‘Internal APIへの直接アクセスは禁止されています。’ });
}
}
};
}
};
Flat Configでの読み込み (eslint.config.js)
ここで、`plugins` フィールドに直接オブジェクトを渡すのがポイントだ。
import customRule from ‘./rules/no-restricted-imports-custom.js’;
export default [
{
plugins: {
// プレフィックスを定義して注入
‘my-company’: {
rules: {
‘no-restricted-imports-custom’: customRule
}
}
},
rules: {
‘my-company/no-restricted-imports-custom’: ‘error’
}
}
];
—
3. なぜこの手法が最強のDevOps戦略なのか
① テストの容易性
`eslint-plugin-local-rules` を介すと、ルールのテストには `RuleTester` を使いつつも、プラグインの読み込み設定が複雑化していた。この手法であれば、純粋なNode.jsモジュールとしてインポートできるため、`Vitest` や `Jest` でルールのロジックだけを高速にテスト可能だ。
② Dockerビルドとの親和性
CI/CDパイプラインにおいて、`npm install` の時間がボトルネックになるケースは多い。独自ルールをコードベースに含めてしまえば、`node_modules` の状態に依存せず、ビルドコンテナ内で確実に静的解析が走る。
Dockerfileの最適化例
COPY ./rules /app/rules
COPY eslint.config.js /app/
独自プラグインの依存解決を気にする必要がないため、キャッシュ効率が劇的に向上する
—
4. パフォーマンス・ハック:AST走査の最小化
大規模プロジェクトにおいて、独自ルールが全ファイルに対して実行されるとメモリを圧迫する。Flat Configの恩恵を最大限に活かし、パス指定によるスコープ制限を徹底すべきだ。
export default [
{
files: [‘src/domain//.ts’], // ドメイン層のみに適用
plugins: { / … / },
rules: { ‘my-company/no-restricted-imports-custom’: ‘error’ }
}
];
このように `files` キーを活用し、ルールが走る対象を極小化する。これはESLintの内部エンジンがASTを構築する頻度を物理的に減らすため、CI実行時間を数秒単位で短縮できる。
—
結論:エンジニアの美学
「ツールが用意した仕組み」に縛られるな。「ツールが解釈できるデータ構造」を理解し、それを直接操作せよ。
`eslint-plugin-local-rules` を捨てるということは、単なるライブラリの断捨離ではない。ESLintを一つの巨大な「コードの抽象構文木処理エンジン」として再定義し、自社のルールをそのエンジンに直結させるという、アーキテクチャ上の高次元な最適化である。
このアプローチを採用することで、あなたのチームのCI/CDパイプラインは、より堅牢で、より高速で、そして何より「依存関係という名のブラックボックス」から解放された、透明性の高いものになるだろう。