T-CREATOR

ESLint の overrides 活用術:複数フレームワーク対応例

ESLint の overrides 活用術:複数フレームワーク対応例

現代の Web プロジェクトでは、フロントエンドとバックエンドで異なるフレームワークを使用することが一般的になっています。そんな中、ESLint の overrides 機能を活用することで、プロジェクト全体のコード品質を統一的に管理できるようになります。

特に複数の技術スタックを組み合わせたプロジェクトでは、「Next.js のコンポーネントファイルで Node.js のルールが適用されてしまう」「API 側で React のルールが警告として表示される」といった問題に直面することがあります。今回は、これらの問題を解決する ESLint の overrides 機能の活用術をご紹介します。

背景

モノレポ構成や複数技術スタック混在プロジェクトの増加

近年、開発効率の向上とコードの再利用性を重視して、モノレポ構成を採用するプロジェクトが急激に増加しています。特に以下のような構成が一般的になっています:

#構成パターン使用技術特徴
1フロントエンド + APINext.js + Node.js最も一般的な JavaScript フルスタック構成
2SPA + APIReact + Express従来の SPA 構成の発展形
3モノレポTypeScript + 複数フレームワーク大規模開発に適した構成

このような構成では、一つのリポジトリ内で複数の実行環境やフレームワークを使用するため、従来の単一設定の ESLint では適切なルール管理が困難になります。

例えば、プロジェクトルートに以下のようなディレクトリ構成がある場合を考えてみましょう:

bashproject-root/
├── frontend/          # Next.js アプリケーション
│   ├── pages/
│   ├── components/
│   └── utils/
├── backend/           # Node.js API
│   ├── routes/
│   ├── models/
│   └── middleware/
├── shared/            # 共通ライブラリ
│   ├── types/
│   └── utils/
└── .eslintrc.js       # ESLint設定ファイル

この構成で単一の ESLint 設定を使用すると、フロントエンド固有のルールがバックエンドに適用されたり、その逆が起こったりします。

課題

異なるフレームワークごとに適切なルールを設定する難しさ

複数フレームワーク環境での ESLint 運用では、以下のような実際のエラーが頻発します:

よくあるエラー例 1: React 固有ルールのバックエンド適用

javascript// backend/routes/users.js
const express = require('express');
const router = express.Router();

// この行でエラーが発生
router.get('/users', (req, res) => {
  res.json({ message: 'Users endpoint' });
});

このコードで以下のエラーが発生します:

sqlerror: 'React' must be in scope when using JSX (react/react-in-scope)
error: JSX not allowed in files with extension '.js' (react/jsx-filename-extension)

よくあるエラー例 2: Node.js 固有ルールのフロントエンド適用

jsx// frontend/components/UserList.tsx
import React from 'react';

const UserList = () => {
  // この行でエラーが発生
  const fetchUsers = async () => {
    const response = await fetch('/api/users');
    return response.json();
  };

  return <div>User List</div>;
};

このコードで以下のエラーが発生します:

perlerror: 'fetch' is not defined (no-undef)
error: Unexpected use of 'fetch' (no-restricted-globals)

設定ファイルの複雑化と保守性の問題

従来の解決策として、各ディレクトリに個別の.eslintrc.jsファイルを配置する方法がありますが、これには以下の問題があります:

  1. 設定の重複: 共通ルールを複数箇所で定義する必要がある
  2. 保守性の低下: 設定変更時に複数ファイルを更新する必要がある
  3. 継承関係の複雑化: どの設定がどこで適用されるかが分からなくなる

チーム開発でのルール統一の困難さ

チーム開発においては、以下のような問題が発生しやすくなります:

#問題点影響
1開発者ごとの設定の違いコードレビューでの議論が増加
2新規参加者の学習コストプロジェクトの理解に時間がかかる
3CI/CD 環境での設定統一デプロイ時のエラーが発生しやすい

解決策

ESLint の overrides 機能を使った段階的ルール適用

ESLint の overrides 機能は、特定のファイルパターンに対して異なるルールセットを適用できる強力な機能です。この機能を使用することで、一つの設定ファイルで複数のフレームワークに対応できます。

overrides の基本構造

javascript// .eslintrc.js
module.exports = {
  // 基本設定(プロジェクト全体に適用)
  extends: ['eslint:recommended'],

  // 特定のファイルパターンに対する設定の上書き
  overrides: [
    {
      files: ['**/*.ts', '**/*.tsx'],
      extends: ['@typescript-eslint/recommended'],
      rules: {
        // TypeScript固有のルール
      },
    },
  ],
};

overrides 機能の主要なメリットは以下の通りです:

  1. 単一ファイルでの管理: 全ての設定を一箇所で管理できる
  2. 継承の明確化: ベース設定からの差分が明確になる
  3. 保守性の向上: 共通ルールの変更が一度で済む

ファイルパターンの指定方法

overrides では、glob パターンを使用してファイルを指定できます:

javascriptoverrides: [
  {
    // TypeScriptファイル
    files: ['**/*.ts', '**/*.tsx'],
    // ...
  },
  {
    // 特定ディレクトリのファイル
    files: ['frontend/**/*.js', 'frontend/**/*.jsx'],
    // ...
  },
  {
    // テストファイル
    files: ['**/*.test.js', '**/*.spec.js'],
    // ...
  },
];

具体例

Next.js + TypeScript + Node.js API の構成例

実際のプロジェクト構成を想定して、段階的に ESLint 設定を構築していきましょう。

プロジェクト構成

bashmodern-webapp/
├── frontend/              # Next.js + TypeScript
│   ├── pages/
│   ├── components/
│   └── utils/
├── backend/               # Node.js + Express API
│   ├── src/
│   │   ├── routes/
│   │   ├── models/
│   │   └── middleware/
│   └── tests/
├── shared/                # 共通ライブラリ
│   ├── types/
│   └── utils/
└── .eslintrc.js          # 統一設定ファイル

ステップ 1: ベース設定の構築

まず、プロジェクト全体に適用する基本設定を定義します:

javascript// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    es2022: true,
  },
  extends: ['eslint:recommended'],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  rules: {
    // 全プロジェクト共通のルール
    'no-console': 'warn',
    'no-unused-vars': 'error',
    'prefer-const': 'error',
  },
};

ステップ 2: TypeScript 対応の追加

TypeScript ファイルに対する設定を追加します:

javascriptoverrides: [
  {
    files: ['**/*.ts', '**/*.tsx'],
    parser: '@typescript-eslint/parser',
    plugins: ['@typescript-eslint'],
    extends: [
      'eslint:recommended',
      '@typescript-eslint/recommended',
    ],
    rules: {
      // TypeScript固有のルール
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type':
        'warn',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
];

ステップ 3: Next.js 固有の設定

フロントエンド側の Next.js 固有の設定を追加します:

javascript{
  files: ['frontend/**/*.js', 'frontend/**/*.jsx', 'frontend/**/*.ts', 'frontend/**/*.tsx'],
  env: {
    browser: true,
    es2022: true,
  },
  extends: [
    'eslint:recommended',
    'next/core-web-vitals',
    '@typescript-eslint/recommended',
  ],
  plugins: ['react', 'react-hooks'],
  rules: {
    // React固有のルール
    'react/react-in-scope': 'error',
    'react/jsx-uses-react': 'error',
    'react/jsx-uses-vars': 'error',
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',

    // Next.js固有のルール
    '@next/next/no-img-element': 'error',
    '@next/next/no-html-link-for-pages': 'error',
  },
}

ステップ 4: Node.js API 固有の設定

バックエンド側の Node.js 固有の設定を追加します:

javascript{
  files: ['backend/**/*.js', 'backend/**/*.ts'],
  env: {
    node: true,
    es2022: true,
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
  ],
  rules: {
    // Node.js固有のルール
    'no-process-exit': 'error',
    'no-process-env': 'warn',
    'callback-return': 'error',
    'handle-callback-err': 'error',

    // Express固有のルール
    'no-unused-vars': 'off', // TypeScriptで管理
    '@typescript-eslint/no-unused-vars': 'error',
  },
}

完全な ESLint 設定ファイル

javascript// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    es2022: true,
  },
  extends: ['eslint:recommended'],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  rules: {
    // 全プロジェクト共通のルール
    'no-console': 'warn',
    'no-unused-vars': 'error',
    'prefer-const': 'error',
  },
  overrides: [
    // TypeScript対応
    {
      files: ['**/*.ts', '**/*.tsx'],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
      ],
      rules: {
        '@typescript-eslint/no-unused-vars': 'error',
        '@typescript-eslint/explicit-function-return-type':
          'warn',
        '@typescript-eslint/no-explicit-any': 'warn',
      },
    },
    // Next.js フロントエンド
    {
      files: [
        'frontend/**/*.js',
        'frontend/**/*.jsx',
        'frontend/**/*.ts',
        'frontend/**/*.tsx',
      ],
      env: {
        browser: true,
        es2022: true,
      },
      extends: [
        'eslint:recommended',
        'next/core-web-vitals',
        '@typescript-eslint/recommended',
      ],
      plugins: ['react', 'react-hooks'],
      rules: {
        'react/react-in-scope': 'error',
        'react/jsx-uses-react': 'error',
        'react/jsx-uses-vars': 'error',
        'react-hooks/rules-of-hooks': 'error',
        'react-hooks/exhaustive-deps': 'warn',
        '@next/next/no-img-element': 'error',
        '@next/next/no-html-link-for-pages': 'error',
      },
    },
    // Node.js バックエンド
    {
      files: ['backend/**/*.js', 'backend/**/*.ts'],
      env: {
        node: true,
        es2022: true,
      },
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
      ],
      rules: {
        'no-process-exit': 'error',
        'no-process-env': 'warn',
        'callback-return': 'error',
        'handle-callback-err': 'error',
        'no-unused-vars': 'off',
        '@typescript-eslint/no-unused-vars': 'error',
      },
    },
  ],
};

React + Express + TypeScript の組み合わせ

SPA 構成での設定例もご紹介します:

javascriptmodule.exports = {
  root: true,
  env: {
    es2022: true,
  },
  extends: ['eslint:recommended'],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  overrides: [
    // React SPA
    {
      files: [
        'src/**/*.js',
        'src/**/*.jsx',
        'src/**/*.ts',
        'src/**/*.tsx',
      ],
      env: {
        browser: true,
      },
      extends: [
        'eslint:recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: [
        'react',
        'react-hooks',
        '@typescript-eslint',
      ],
      settings: {
        react: {
          version: 'detect',
        },
      },
      rules: {
        'react/prop-types': 'off', // TypeScriptで管理
        'react/react-in-scope': 'off', // React 17+では不要
      },
    },
    // Express API
    {
      files: ['server/**/*.js', 'server/**/*.ts'],
      env: {
        node: true,
      },
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
      rules: {
        'no-console': 'off', // サーバーサイドでは許可
        '@typescript-eslint/no-unused-vars': 'error',
      },
    },
  ],
};

モノレポでの設定パターン

Yarn ワークスペースを使用したモノレポ構成での設定例:

javascript// ルートの.eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    es2022: true,
  },
  extends: ['eslint:recommended'],
  overrides: [
    // packages/ui - React コンポーネントライブラリ
    {
      files: [
        'packages/ui/**/*.ts',
        'packages/ui/**/*.tsx',
      ],
      env: {
        browser: true,
      },
      extends: [
        'eslint:recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: [
        'react',
        'react-hooks',
        '@typescript-eslint',
      ],
      settings: {
        react: {
          version: 'detect',
        },
      },
    },
    // packages/utils - 共通ユーティリティ
    {
      files: ['packages/utils/**/*.ts'],
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
      rules: {
        '@typescript-eslint/explicit-function-return-type':
          'error',
      },
    },
    // apps/web - Next.js アプリケーション
    {
      files: ['apps/web/**/*.ts', 'apps/web/**/*.tsx'],
      env: {
        browser: true,
      },
      extends: [
        'eslint:recommended',
        'next/core-web-vitals',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
    },
    // apps/api - Node.js API
    {
      files: ['apps/api/**/*.ts'],
      env: {
        node: true,
      },
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
      ],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
    },
  ],
};

テストファイルの特別な設定

テストファイルには、通常の開発ルールとは異なる設定が必要です:

javascript// テストファイル専用のoverrides設定
{
  files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
  env: {
    jest: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:jest/recommended',
  ],
  plugins: ['jest', '@typescript-eslint'],
  rules: {
    // テストファイルでは緩和するルール
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    'jest/expect-expect': 'error',
    'jest/no-focused-tests': 'error',
  },
}

パフォーマンスとトラブルシューティング

よくある問題と解決方法

問題 1: overrides の優先順位

javascript// 誤った設定例
overrides: [
  {
    files: ['**/*.ts'],
    rules: { 'no-console': 'error' },
  },
  {
    files: ['frontend/**/*.ts'],
    rules: { 'no-console': 'warn' },
  },
];

この場合、後に定義された設定が優先されます。より具体的なパターンを後に配置することが重要です。

問題 2: 循環参照エラー

vbnetError: ESLint configuration is invalid:
- Configuration for rule "import/no-cycle" is invalid

この問題は、設定の継承関係で発生することがあります。以下のように明示的に設定を分離することで解決できます:

javascriptoverrides: [
  {
    files: ['**/*.ts'],
    extends: [
      'eslint:recommended',
      '@typescript-eslint/recommended',
    ],
    rules: {
      'import/no-cycle': 'off', // 一時的に無効化
    },
  },
];

問題 3: パフォーマンスの最適化

大きなプロジェクトでは、ESLint の実行時間が問題になることがあります:

javascript// .eslintrc.js
module.exports = {
  // ...
  ignorePatterns: [
    'node_modules/',
    'dist/',
    'build/',
    '.next/',
    'coverage/',
  ],
  // ...
};

まとめ

overrides 活用によるメリットと今後の展望

ESLint の overrides 機能を活用することで、以下のような大きなメリットが得られます:

技術的メリット

  1. 統一された設定管理: 複数のフレームワークを使用するプロジェクトでも、一つのファイルで設定を管理できる
  2. 保守性の向上: 共通ルールの変更が一度の修正で済む
  3. 継承関係の明確化: どのルールがどのファイルに適用されるかが明確になる

チーム開発でのメリット

  1. 学習コストの削減: 新規参加者が理解すべき設定ファイルが一つに集約される
  2. レビュー効率の向上: 統一されたルールによりコードレビューが効率化される
  3. CI/CD 環境での安定性: 環境による設定の差異が発生しにくい

運用面でのメリット

#従来の方法overrides 使用
1複数の設定ファイルが必要単一ファイルで管理
2設定の同期が困難自動的に統一される
3新しいフレームワーク追加時の工数が大きい設定追加のみで対応可能

今後の展望

モダンな Web 開発では、さらに多様な技術スタックの組み合わせが予想されます。例えば:

  • マイクロフロントエンド: 複数のフレームワークを組み合わせたフロントエンド
  • エッジコンピューティング: Cloudflare Workers、Vercel Edge Functions
  • WebAssembly: Rust などの他言語との統合

これらの技術に対しても overrides 機能を活用することで、柔軟かつ効率的な設定管理が可能になります。

実装時の推奨アプローチ

overrides 機能を導入する際は、以下の順序で進めることをお勧めします:

  1. 現在の設定の整理: 既存の設定ファイルを確認し、重複や矛盾を特定
  2. 段階的な移行: 一度に全てを変更するのではなく、段階的に移行
  3. チームでの合意: 新しい設定についてチーム全体で合意を形成
  4. 継続的な改善: 運用開始後も定期的に設定を見直し、最適化

ESLint の overrides 機能は、複数フレームワークを使用するモダンな Web 開発において、もはや必須の技術と言えるでしょう。適切に活用することで、開発効率の向上とコード品質の維持を両立できます。

関連リンク