T-CREATOR

ESLint Flat Config 完全理解:eslint.config.js 時代の設計指針

ESLint Flat Config 完全理解:eslint.config.js 時代の設計指針

ESLint 9.0 の登場とともに、私たちの開発環境は大きな変革期を迎えています。従来の.eslintrcファイルからeslint.config.jsへの移行が進む中で、多くの開発者が新しい設定方式に戸惑いを感じているのではないでしょうか。

この記事では、ESLint Flat Config の全貌を詳しく解説いたします。新しい設定方式がもたらす革新的な変化を理解することで、よりシンプルで保守性の高い Lint 設定を構築できるようになります。

従来の複雑な設定継承から解放され、直感的で予測可能な設定管理を実現する方法をご紹介いたします。

背景

ESLint 設定ファイルの進化

ESLint の設定ファイルは、その誕生から長い進化の歴史を歩んできました。初期の頃は単純な設定ファイルでしたが、JavaScript エコシステムの拡大とともに、より複雑で柔軟な設定システムが求められるようになったのです。

以下の図で、ESLint 設定ファイルの変遷を確認してみましょう。

mermaidflowchart TD
    A[ESLint 初期] -->|設定の拡張| B[.eslintrc]
    B -->|JSON/YAML対応| C[.eslintrc.json/.eslintrc.yml]
    C -->|JavaScript設定| D[.eslintrc.js]
    D -->|複雑化への対応| E[複数設定ファイル]
    E -->|ESLint 9.0| F[eslint.config.js<br/>Flat Config]

    style F fill:#e1f5fe
    style E fill:#fff3e0

図で理解できる要点

  • 設定ファイルは時代と共に複雑化し、最終的に Flat Config で単純化された
  • JavaScript 設定ファイルの導入により動的設定が可能になった
  • 現在の Flat Config は過去の課題を解決する最新の設計思想を採用している

2013 年の ESLint 誕生から 10 年以上の歳月を経て、設定方式は着実に進化を続けてきました。最初は単純なルール設定から始まり、プラグインシステムの導入、継承機能の追加、そして現在の Flat Config へと発展しています。

従来の.eslintrc の課題

従来の.eslintrc形式では、設定ファイルの探索や継承の仕組みが複雑化していました。プロジェクトの規模が大きくなると、どの設定がどこから継承されているのか把握することが困難になりがちです。

特に以下のような問題が頻繁に発生していました。

設定ファイル探索の複雑性

ESLint は設定ファイルを探索する際、複数のファイル名と拡張子の組み合わせを順番にチェックしていました。

javascript// 探索される設定ファイルの優先順位
const configFiles = [
  '.eslintrc.js',
  '.eslintrc.cjs',
  '.eslintrc.yaml',
  '.eslintrc.yml',
  '.eslintrc.json',
  '.eslintrc',
];

この探索メカニズムにより、意図しない設定ファイルが読み込まれる可能性がありました。開発者は複数の設定ファイルが存在する場合、どれが実際に使用されているのか確認する必要があったのです。

継承チェーンの予測困難性

.eslintrcextends機能は便利でしたが、継承チェーンが深くなると設定の最終的な状態を予測することが困難でした。

json{
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended",
    "plugin:react/recommended",
    "./custom-config.js"
  ]
}

このような設定では、最終的にどのルールが有効になっているのか、どの設定が上書きされているのかを理解するために、すべての継承元を確認する必要がありました。

Flat Config が生まれた理由

Flat Config の開発背景には、現代的な JavaScript 開発環境に対応する必要性がありました。モノレポ構造、複数のフレームワーク併用、動的な設定変更など、従来の設定方式では対応が困難な要求が増加していたのです。

以下の現代的な開発環境の特徴を見てみましょう。

mermaidflowchart LR
    subgraph modern [現代的な開発環境]
        A[モノレポ構造]
        B[複数フレームワーク]
        C[動的設定]
        D[複雑なビルド]
    end

    subgraph needs [求められる機能]
        E[柔軟な設定分割]
        F[予測可能な動作]
        G[高いパフォーマンス]
        H[シンプルな管理]
    end

    modern --> needs

    style modern fill:#f3e5f5
    style needs fill:#e8f5e8

Flat Config は、これらの現代的な要求に応えるために設計されました。設定の予測可能性、パフォーマンスの向上、そして開発者体験の改善を目的としています。

特に大規模なプロジェクトでは、設定の複雑化が開発速度の低下や保守コストの増大に直結していました。Flat Config の導入により、これらの課題を根本的に解決できるようになったのです。

課題

既存設定ファイルの複雑性

従来の ESLint 設定では、プロジェクトの成長とともに設定ファイルが複雑化する傾向がありました。複数の設定ファイルが階層的に配置され、それぞれが異なるルールセットを定義することで、全体像の把握が困難になっていたのです。

ディレクトリ階層による設定の分散

典型的なプロジェクト構造では、以下のような設定ファイルの配置が見られました。

bashproject/
├── .eslintrc.js                 # ルート設定
├── src/
│   ├── .eslintrc.js             # ソースコード用設定
│   ├── components/
│   │   └── .eslintrc.js         # コンポーネント用設定
│   └── utils/
│       └── .eslintrc.js         # ユーティリティ用設定
├── tests/
│   └── .eslintrc.js             # テスト用設定
└── scripts/
    └── .eslintrc.js             # スクリプト用設定

このような構造では、ファイルの場所によって適用される設定が変わり、開発者がルールの適用状況を把握するのが困難でした。

設定内容の重複と矛盾

複数の設定ファイルに同様の設定が記述されることで、保守性が低下していました。

javascript// src/.eslintrc.js
module.exports = {
  extends: ['eslint:recommended'],
  rules: {
    'no-console': 'warn',
    'prefer-const': 'error',
  },
};

// tests/.eslintrc.js
module.exports = {
  extends: ['eslint:recommended'],
  rules: {
    'no-console': 'off', // 重複した設定
    'prefer-const': 'error', // 重複した設定
  },
};

設定の継承と上書きの問題

従来のextends機能による継承システムでは、設定の上書きが予期しない結果を生む場合がありました。特に複数のプリセット設定を組み合わせる際に問題が顕著でした。

予期しない設定の上書き

継承チェーンが複雑になると、どの設定がどの段階で上書きされているのか追跡が困難でした。

mermaidflowchart TD
    A[eslint:recommended] -->|継承| B[TypeScript設定]
    B -->|継承| C[React設定]
    C -->|継承| D[プロジェクト設定]
    D -->|上書き| E[最終設定]

    F[想定される設定] -.->|不一致| E

    style F fill:#ffebee
    style E fill:#e8f5e8

この図が示すように、継承チェーンの各段階で設定が変更されるため、最終的な設定が開発者の想定と異なる場合が頻繁に発生していました。

プラグイン間の競合

複数のプラグインを使用する場合、同じルールに対して異なる設定が適用される競合が発生することがありました。

json{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended"
  ],
  "rules": {
    "@typescript-eslint/indent": ["error", 2],
    "prettier/prettier": ["error", { "tabWidth": 4 }]
  }
}

このような設定では、インデントに関するルールが競合し、一貫性のない結果を生む可能性がありました。

パフォーマンスとメンテナンス性

大規模なプロジェクトでは、設定ファイルの探索と読み込みがパフォーマンスに大きな影響を与えていました。

設定ファイル探索のオーバーヘッド

ESLint は各ファイルを処理する際に、そのファイルに適用される設定を特定するために、ディレクトリツリーを上方向に探索していました。

javascript// 内部的な探索プロセス(疑似コード)
function findConfig(filePath) {
  let currentDir = path.dirname(filePath);

  while (currentDir !== path.parse(currentDir).root) {
    for (const configFile of CONFIG_FILE_NAMES) {
      const configPath = path.join(currentDir, configFile);
      if (fs.existsSync(configPath)) {
        return loadConfig(configPath);
      }
    }
    currentDir = path.dirname(currentDir);
  }

  return null;
}

この探索処理は、ファイル数が多いプロジェクトではパフォーマンスのボトルネックとなっていました。

メンテナンスコストの増大

設定ファイルが分散していることで、ルールの変更や更新時に複数のファイルを同期して修正する必要がありました。これにより、設定の一貫性を保つためのメンテナンスコストが増大していたのです。

解決策

Flat Config の基本概念

ESLint Flat Config は、従来の複雑な設定継承システムを大幅に簡素化し、より予測可能で直感的な設定方式を実現します。最も重要な概念は「フラット」という名前が示すとおり、階層的な継承ではなく配列ベースの線形な設定構造です。

配列ベースの設定構造

Flat Config では、すべての設定を配列の要素として定義します。この配列の各要素は設定オブジェクトであり、特定のファイルパターンに対するルールセットを表現します。

javascript// eslint.config.js
export default [
  // 設定オブジェクト1: 全JavaScriptファイル向け
  {
    files: ['**/*.js'],
    rules: {
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },
  // 設定オブジェクト2: TypeScriptファイル向け
  {
    files: ['**/*.ts'],
    languageOptions: {
      parser: '@typescript-eslint/parser',
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
    },
  },
];

この構造により、どの設定がどのファイルに適用されるのかが一目で理解できます。

ファイルパターンによる明示的な対象指定

従来のディレクトリベースの設定ではなく、glob パターンによってルール適用対象を明示的に指定します。

javascriptexport default [
  {
    // ソースファイルのみを対象
    files: ['src/**/*.{js,ts,jsx,tsx}'],
    rules: {
      'no-console': 'error',
    },
  },
  {
    // テストファイルは除外
    files: ['src/**/*.{js,ts,jsx,tsx}'],
    ignores: ['**/*.test.{js,ts}', '**/*.spec.{js,ts}'],
    rules: {
      'no-magic-numbers': 'warn',
    },
  },
];

新しい設定形式の特徴

Flat Config では、設定オブジェクトの構造が大幅に見直され、より論理的で使いやすい形式に変更されました。

統一された設定オブジェクト構造

新しい設定オブジェクトは、明確に定義されたプロパティを持ちます。

javascript// 設定オブジェクトの完全な構造例
export default [
  {
    // ファイル対象の指定
    files: ['**/*.js'],
    ignores: ['node_modules/**'],

    // 言語設定
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: 'module',
      parser: '@babel/eslint-parser',
      parserOptions: {
        requireConfigFile: false,
      },
      globals: {
        window: 'readonly',
        document: 'readonly',
      },
    },

    // プラグインの指定
    plugins: {
      import: importPlugin,
      react: reactPlugin,
    },

    // ルールの定義
    rules: {
      'import/order': 'error',
      'react/jsx-uses-react': 'error',
    },

    // プロセッサーの指定
    processor: 'vue/.vue',
  },
];

プラグインの明示的インポート

Flat Config では、プラグインを文字列ではなく、実際のオブジェクトとしてインポートして使用します。これにより、依存関係が明確になり、バンドラーとの相性も改善されます。

javascript// プラグインの明示的インポート
import eslintPluginReact from 'eslint-plugin-react';
import typescriptEslint from '@typescript-eslint/eslint-plugin';

export default [
  {
    files: ['**/*.tsx'],
    plugins: {
      react: eslintPluginReact,
      '@typescript-eslint': typescriptEslint,
    },
    rules: {
      'react/jsx-uses-react': 'error',
      '@typescript-eslint/no-unused-vars': 'error',
    },
  },
];

設計思想とメリット

Flat Config の設計思想は、シンプルさ予測可能性保守性の 3 つの柱に基づいています。

以下の図で、従来の設定方式と Flat Config の違いを比較してみましょう。

mermaidgraph TB
    subgraph legacy [従来の.eslintrc方式]
        A1[.eslintrc] -->|継承| A2[親設定]
        A2 -->|継承| A3[プリセット]
        A3 -->|継承| A4[プラグイン設定]
        A4 --> A5[最終設定?]
        A5 -.->|予測困難| A6[実際の動作]
    end

    subgraph flat [Flat Config方式]
        B1[eslint.config.js] --> B2[配列の要素1]
        B1 --> B3[配列の要素2]
        B1 --> B4[配列の要素3]
        B2 --> B5[予測可能な動作]
        B3 --> B5
        B4 --> B5
    end

    style legacy fill:#ffebee
    style flat fill:#e8f5e8
    style A6 fill:#ffcdd2
    style B5 fill:#c8e6c9

図で理解できる要点

  • 従来方式は複雑な継承チェーンにより予測困難な動作が発生
  • Flat Config は線形な配列構造により予測可能な動作を実現
  • 設定の適用順序が明確で、デバッグが容易

予測可能性の向上

配列ベースの設定により、後から追加された設定が前の設定を上書きする単純なルールで動作します。

javascriptexport default [
  {
    files: ['**/*.js'],
    rules: {
      'no-console': 'error', // 最初の設定
    },
  },
  {
    files: ['**/*.js'],
    rules: {
      'no-console': 'warn', // この設定が上書きする
    },
  },
];

このように、配列の後の要素が前の要素を上書きする明確なルールにより、設定の最終状態を簡単に予測できます。

パフォーマンスの改善

Flat Config では、設定ファイルの探索処理が大幅に簡素化されました。単一のeslint.config.jsファイルを読み込むだけで済むため、ファイルシステムへのアクセス回数が劇的に減少します。

保守性の向上

すべての設定が単一ファイルに集約されることで、設定の変更や確認が容易になりました。分散していた設定を一箇所で管理できるため、一貫性のある設定を維持しやすくなります。

具体例

基本的な eslint.config.js 設定

最もシンプルな Flat Config 設定から始めてみましょう。従来の.eslintrc.jsと比較しながら、基本的な書き方を確認いたします。

シンプルな JavaScript プロジェクト設定

まず、純粋な JavaScript プロジェクトでの基本設定をご紹介いたします。

javascript// eslint.config.js
export default [
  {
    // 対象ファイルの指定
    files: ['**/*.js'],

    // 言語オプション
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: 'module',
      globals: {
        console: 'readonly',
        process: 'readonly',
      },
    },

    // ルールの定義
    rules: {
      'prefer-const': 'error',
      'no-var': 'error',
      'no-unused-vars': 'warn',
      'no-console': 'warn',
    },
  },
];

この設定では、すべての JavaScript ファイルに対して基本的なモダン JavaScript のルールを適用しています。languageOptionsで言語バージョンとモジュール形式を指定し、グローバル変数も明示的に定義しています。

従来の.eslintrc との比較

同等の設定を従来の形式で書くと以下のようになります。

javascript// .eslintrc.js(従来形式)
module.exports = {
  env: {
    es2024: true,
    node: true,
  },
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
  },
  rules: {
    'prefer-const': 'error',
    'no-var': 'error',
    'no-unused-vars': 'warn',
    'no-console': 'warn',
  },
};

Flat Config ではenvlanguageOptions.globalsに統合され、より明示的な設定が可能になりました。

複数ファイルタイプの設定

実際のプロジェクトでは、JavaScript、TypeScript、テストファイルなど、複数のファイルタイプを扱うことが一般的です。Flat Config では、それぞれに適した設定を配列の要素として定義できます。

JavaScript + TypeScript 混在プロジェクト

javascript// eslint.config.js
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';

export default [
  // JavaScript用設定
  {
    files: ['**/*.js', '**/*.mjs'],
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: 'module',
    },
    rules: {
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },

  // TypeScript用設定
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: typescriptParser,
      parserOptions: {
        project: './tsconfig.json',
      },
    },
    plugins: {
      '@typescript-eslint': typescriptEslint,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type':
        'warn',
      '@typescript-eslint/no-explicit-any': 'error',
    },
  },
];

テストファイル専用設定

テストファイルには、通常とは異なるルールを適用したい場合があります。

javascriptexport default [
  // 通常のソースファイル設定
  {
    files: ['src/**/*.{js,ts}'],
    ignores: ['**/*.test.{js,ts}', '**/*.spec.{js,ts}'],
    rules: {
      'no-console': 'error',
      'no-magic-numbers': 'warn',
    },
  },

  // テストファイル専用設定
  {
    files: ['**/*.test.{js,ts}', '**/*.spec.{js,ts}'],
    languageOptions: {
      globals: {
        describe: 'readonly',
        it: 'readonly',
        expect: 'readonly',
        beforeEach: 'readonly',
        afterEach: 'readonly',
      },
    },
    rules: {
      'no-console': 'off', // テストではconsoleを許可
      'no-magic-numbers': 'off', // テストではマジックナンバーを許可
    },
  },
];

プロジェクト構造に応じた設定分割

大規模なプロジェクトでは、設定を複数のファイルに分割して管理することが効果的です。Flat Config では、設定の分割と再利用が簡潔に行えます。

設定ファイルの分割戦略

プロジェクトの構造に応じて、以下のような分割パターンが推奨されます。

mermaidflowchart TD
    A[eslint.config.js] --> B[base.config.js]
    A --> C[typescript.config.js]
    A --> D[react.config.js]
    A --> E[test.config.js]

    B --> F[基本ルール]
    C --> G[TypeScript専用ルール]
    D --> H[React専用ルール]
    E --> I[テスト専用ルール]

    style A fill:#e3f2fd
    style B fill:#f3e5f5
    style C fill:#e8f5e8
    style D fill:#fff3e0
    style E fill:#fce4ec

基本設定の分離

javascript// config/base.config.js
export const baseConfig = {
  languageOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
  },
  rules: {
    'prefer-const': 'error',
    'no-var': 'error',
    'no-unused-vars': 'warn',
  },
};

TypeScript 設定の分離

javascript// config/typescript.config.js
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';

export const typescriptConfig = {
  files: ['**/*.ts', '**/*.tsx'],
  languageOptions: {
    parser: typescriptParser,
    parserOptions: {
      project: './tsconfig.json',
    },
  },
  plugins: {
    '@typescript-eslint': typescriptEslint,
  },
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type':
      'warn',
  },
};

メイン設定ファイルでの統合

javascript// eslint.config.js
import { baseConfig } from './config/base.config.js';
import { typescriptConfig } from './config/typescript.config.js';

export default [
  {
    files: ['**/*.js'],
    ...baseConfig,
  },
  typescriptConfig,
  // 必要に応じて他の設定も追加
];

TypeScript + React 環境での実装

実際の開発現場で最も一般的な TypeScript + React 環境での完全な設定例をご紹介いたします。

完全な React プロジェクト設定

javascript// eslint.config.js
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';

export default [
  // 基本JavaScript設定
  {
    files: ['**/*.js', '**/*.jsx'],
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: 'module',
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
    rules: {
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },

  // TypeScript設定
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: typescriptParser,
      parserOptions: {
        project: './tsconfig.json',
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
    plugins: {
      '@typescript-eslint': typescriptEslint,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type':
        'off',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
];

React 専用ルールの追加

javascript// 上記の設定に追加
export default [
  // ...既存の設定,

  // React + TypeScript設定
  {
    files: ['**/*.tsx', '**/*.jsx'],
    plugins: {
      react: reactPlugin,
      'react-hooks': reactHooksPlugin,
      'jsx-a11y': jsxA11yPlugin,
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
    rules: {
      // React基本ルール
      'react/jsx-uses-react': 'error',
      'react/jsx-uses-vars': 'error',
      'react/prop-types': 'off', // TypeScriptを使用するため無効

      // React Hooksルール
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',

      // アクセシビリティルール
      'jsx-a11y/alt-text': 'error',
      'jsx-a11y/anchor-has-content': 'error',
    },
  },
];

パフォーマンス最適化設定

大規模な React プロジェクトでは、Lint のパフォーマンスも重要な要素です。

javascriptexport default [
  // 除外パターンの設定
  {
    ignores: [
      'node_modules/**',
      'dist/**',
      'build/**',
      '*.min.js',
      'coverage/**',
    ],
  },

  // 開発用ファイルの軽量設定
  {
    files: ['**/*.dev.{js,ts,jsx,tsx}'],
    rules: {
      'no-console': 'off',
      'no-debugger': 'off',
    },
  },

  // プロダクション用の厳格設定
  {
    files: ['src/**/*.{js,ts,jsx,tsx}'],
    ignores: ['**/*.dev.*', '**/*.test.*'],
    rules: {
      'no-console': 'error',
      'no-debugger': 'error',
      'no-alert': 'error',
    },
  },
];

この設定により、開発効率を保ちながら品質の高いコードを維持できます。ファイルの種類や用途に応じて適切なルールが適用され、チーム全体での一貫した開発スタイルを実現できるでしょう。

まとめ

ESLint Flat Config は、従来の複雑な設定継承システムから脱却し、より直感的で予測可能な設定管理を実現する革新的なアプローチです。配列ベースの線形構造により、設定の適用順序が明確になり、開発者の認知負荷を大幅に軽減します。

Flat Config の主要なメリット

  • 予測可能性: 配列の順序による明確な上書きルール
  • シンプルさ: 単一ファイルでの設定管理
  • パフォーマンス: 設定ファイル探索の効率化
  • 保守性: 分散した設定の統合による管理コストの削減

従来の.eslintrc形式では困難だった大規模プロジェクトでの設定管理も、Flat Config により格段に改善されます。特にモノレポ構造や複数のフレームワークを組み合わせた現代的な開発環境において、その真価を発揮するでしょう。

TypeScript + React 環境での実装例で示したように、複雑な要件にも柔軟に対応できる設計となっています。段階的な移行も可能なため、既存プロジェクトでも安心して導入を検討できます。

ESLint 9.0 以降の新しい時代において、Flat Config をマスターすることは、より効率的で品質の高い開発環境を構築するための重要なスキルとなります。ぜひ、この機会に新しい設定方式を活用して、開発体験の向上を実感してください。

関連リンク