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

現代の Web プロジェクトでは、フロントエンドとバックエンドで異なるフレームワークを使用することが一般的になっています。そんな中、ESLint の overrides 機能を活用することで、プロジェクト全体のコード品質を統一的に管理できるようになります。
特に複数の技術スタックを組み合わせたプロジェクトでは、「Next.js のコンポーネントファイルで Node.js のルールが適用されてしまう」「API 側で React のルールが警告として表示される」といった問題に直面することがあります。今回は、これらの問題を解決する ESLint の overrides 機能の活用術をご紹介します。
背景
モノレポ構成や複数技術スタック混在プロジェクトの増加
近年、開発効率の向上とコードの再利用性を重視して、モノレポ構成を採用するプロジェクトが急激に増加しています。特に以下のような構成が一般的になっています:
# | 構成パターン | 使用技術 | 特徴 |
---|---|---|---|
1 | フロントエンド + API | Next.js + Node.js | 最も一般的な JavaScript フルスタック構成 |
2 | SPA + API | React + 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 | CI/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 機能の主要なメリットは以下の通りです:
- 単一ファイルでの管理: 全ての設定を一箇所で管理できる
- 継承の明確化: ベース設定からの差分が明確になる
- 保守性の向上: 共通ルールの変更が一度で済む
ファイルパターンの指定方法
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 機能を活用することで、以下のような大きなメリットが得られます:
技術的メリット
- 統一された設定管理: 複数のフレームワークを使用するプロジェクトでも、一つのファイルで設定を管理できる
- 保守性の向上: 共通ルールの変更が一度の修正で済む
- 継承関係の明確化: どのルールがどのファイルに適用されるかが明確になる
チーム開発でのメリット
- 学習コストの削減: 新規参加者が理解すべき設定ファイルが一つに集約される
- レビュー効率の向上: 統一されたルールによりコードレビューが効率化される
- CI/CD 環境での安定性: 環境による設定の差異が発生しにくい
運用面でのメリット
# | 従来の方法 | overrides 使用 |
---|---|---|
1 | 複数の設定ファイルが必要 | 単一ファイルで管理 |
2 | 設定の同期が困難 | 自動的に統一される |
3 | 新しいフレームワーク追加時の工数が大きい | 設定追加のみで対応可能 |
今後の展望
モダンな Web 開発では、さらに多様な技術スタックの組み合わせが予想されます。例えば:
- マイクロフロントエンド: 複数のフレームワークを組み合わせたフロントエンド
- エッジコンピューティング: Cloudflare Workers、Vercel Edge Functions
- WebAssembly: Rust などの他言語との統合
これらの技術に対しても overrides 機能を活用することで、柔軟かつ効率的な設定管理が可能になります。
実装時の推奨アプローチ
overrides 機能を導入する際は、以下の順序で進めることをお勧めします:
- 現在の設定の整理: 既存の設定ファイルを確認し、重複や矛盾を特定
- 段階的な移行: 一度に全てを変更するのではなく、段階的に移行
- チームでの合意: 新しい設定についてチーム全体で合意を形成
- 継続的な改善: 運用開始後も定期的に設定を見直し、最適化
ESLint の overrides 機能は、複数フレームワークを使用するモダンな Web 開発において、もはや必須の技術と言えるでしょう。適切に活用することで、開発効率の向上とコード品質の維持を両立できます。
関連リンク
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実