T-CREATOR

ESLint × Monorepo での管理方法と注意点

ESLint × Monorepo での管理方法と注意点

モノレポ(Monorepo)環境での開発が普及する中、ESLint の設定管理は従来の単一プロジェクトとは異なる複雑な課題を抱えています。複数のパッケージが共存し、異なる技術スタックが混在する環境では、設定の継承、競合、パフォーマンスなど、様々な問題が発生します。

この記事では、モノレポ環境での ESLint 管理の効率的な解決策を段階的に解説していきます。基本的な設定戦略から実際の運用課題まで、実践的な内容をお届けし、大規模なプロジェクトでも安定して運用できる方法をご紹介します。

モノレポと ESLint の基本関係

モノレポ環境特有の ESLint 課題

モノレポ環境では、単一プロジェクトでは発生しない特有の課題があります。

主要な課題一覧

#課題発生理由影響度
1設定継承の複雑化複数階層の設定ファイルが相互作用
2パフォーマンス劣化大量のファイルとパッケージの処理
3技術スタック混在React/Vue/Node.js などが共存
4設定競合異なるパッケージ間でのルール衝突
5開発体験の不統一パッケージごとに異なるエラー表示

従来の単一プロジェクト管理との違い

単一プロジェクトの場合

dirproject/
├── .eslintrc.js (root: true)
├── src/
│   ├── components/
│   └── utils/
└── package.json

この構成では、一つの .eslintrc.js ですべてのファイルを管理できます。

モノレポの場合

dirmonorepo/
├── .eslintrc.js (root: true, 共通設定)
├── packages/
│   ├── web-app/
│   │   ├── .eslintrc.js (React設定)
│   │   ├── src/
│   │   └── package.json
│   ├── api-server/
│   │   ├── .eslintrc.js (Node.js設定)
│   │   ├── src/
│   │   └── package.json
│   └── shared-lib/
│       ├── .eslintrc.js (ライブラリ設定)
│       ├── src/
│       └── package.json
└── package.json

この構成では、複数の設定ファイルが階層的に存在し、それぞれが異なる目的を持ちます。

設定継承と分離のバランス

モノレポでは、共通設定と個別設定のバランスが重要です。

良いバランスの例

javascript// monorepo/.eslintrc.js (ルートレベル)
module.exports = {
  root: true,
  extends: ['eslint:recommended'],
  env: {
    es2021: true,
  },
  parserOptions: {
    ecmaVersion: 2021,
  },
  rules: {
    // 全パッケージ共通のルール
    'no-console': 'warn',
    'prefer-const': 'error',
  },
  overrides: [
    {
      files: ['**/*.test.js', '**/*.spec.js'],
      env: {
        jest: true,
      },
      rules: {
        'no-console': 'off',
      },
    },
  ],
};
javascript// packages/web-app/.eslintrc.js (個別設定)
module.exports = {
  // root: true を設定しない(親設定を継承)
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  env: {
    browser: true,
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
  rules: {
    // React固有のルール
    'react/prop-types': 'error',
    'react-hooks/exhaustive-deps': 'warn',
  },
};

基本的な設定戦略

ルートレベルでの共通設定

ルートレベルの設定は、モノレポ全体で適用される基盤となる重要な設定です。

基本構成の設計原則

javascript// monorepo/.eslintrc.js
module.exports = {
  root: true, // 必須:上位ディレクトリへの探索を停止
  extends: [
    'eslint:recommended',
    '@company/eslint-config', // 社内共通設定があれば
  ],
  env: {
    es2021: true,
    node: true, // 全般的に Node.js 環境を想定
  },
  parserOptions: {
    ecmaVersion: 2021,
    sourceType: 'module',
  },
  plugins: [
    'import', // 必須プラグイン
  ],
  rules: {
    // 全パッケージで統一すべき基本ルール
    'no-unused-vars': 'error',
    'no-console': 'warn',
    'prefer-const': 'error',
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal'],
        'newlines-between': 'always',
      },
    ],
  },
  ignorePatterns: [
    // 全体で無視するパターン
    'node_modules/',
    'dist/',
    'build/',
    'coverage/',
    '*.d.ts',
  ],
};

パッケージレベルでの個別設定

各パッケージでは、そのパッケージ特有の要件に応じた設定を追加します。

React パッケージの設定例

javascript// packages/web-app/.eslintrc.js
module.exports = {
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:jsx-a11y/recommended',
  ],
  env: {
    browser: true,
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
  rules: {
    'react/react-in-jsx-scope': 'off', // React 17+
    'react/prop-types': 'error',
    'jsx-a11y/anchor-is-valid': 'error',
  },
  overrides: [
    {
      files: ['**/*.tsx'],
      rules: {
        'react/prop-types': 'off', // TypeScript使用時
      },
    },
  ],
};

Node.js パッケージの設定例

javascript// packages/api-server/.eslintrc.js
module.exports = {
  extends: [
    'plugin:node/recommended',
    'plugin:security/recommended',
  ],
  env: {
    node: true,
    browser: false, // 明示的にブラウザ環境を無効化
  },
  rules: {
    'no-console': 'off', // サーバーサイドではconsoleを許可
    'node/no-process-env': 'off', // 環境変数の使用を許可
    'security/detect-object-injection': 'warn',
  },
};

設定ファイルの階層構造設計

効率的な階層構造を設計するための原則です。

推奨される階層パターン

dirmonorepo/
├── .eslintrc.js              (レベル1: 全体基盤設定)
├── packages/
│   ├── frontend/
│   │   ├── .eslintrc.js      (レベル2: 技術領域別設定)
│   │   ├── web-app/
│   │   │   ├── .eslintrc.js  (レベル3: 個別プロジェクト設定)
│   │   │   └── src/
│   │   └── mobile-app/
│   │       ├── .eslintrc.js
│   │       └── src/
│   └── backend/
│       ├── .eslintrc.js      (レベル2: 技術領域別設定)
│       ├── api-server/
│       │   └── src/
│       └── worker/
│           └── src/
└── tools/
    ├── .eslintrc.js          (レベル2: ツール固有設定)
    └── scripts/

レベル 2 設定の例(技術領域別)

javascript// packages/frontend/.eslintrc.js
module.exports = {
  extends: [
    'plugin:import/recommended',
    'plugin:prettier/recommended',
  ],
  env: {
    browser: true,
  },
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
  rules: {
    // フロントエンド共通のルール
    'no-alert': 'error',
    'no-confirm': 'error',
  },
};

技術スタック別の管理手法

React/Vue/Node.js 混在環境での設定

異なるフレームワークが混在する環境では、適切な分離と共通化が重要です。

共通ベース設定の作成

javascript// eslint-config/base.js (共通設定)
module.exports = {
  extends: ['eslint:recommended'],
  env: {
    es2021: true,
  },
  parserOptions: {
    ecmaVersion: 2021,
    sourceType: 'module',
  },
  plugins: ['import', 'unicorn'],
  rules: {
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal'],
        'newlines-between': 'always',
        alphabetize: { order: 'asc' },
      },
    ],
    'unicorn/prefer-module': 'error',
    'prefer-const': 'error',
  },
};

フレームワーク固有設定の作成

javascript// eslint-config/react.js
const base = require('./base');

module.exports = {
  ...base,
  extends: [
    ...base.extends,
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  env: {
    ...base.env,
    browser: true,
  },
  settings: {
    react: { version: 'detect' },
  },
  rules: {
    ...base.rules,
    'react/prop-types': 'error',
    'react-hooks/exhaustive-deps': 'warn',
  },
};
javascript// eslint-config/vue.js
const base = require('./base');

module.exports = {
  ...base,
  extends: [...base.extends, 'plugin:vue/vue3-recommended'],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ...base.parserOptions,
    parser: '@typescript-eslint/parser',
  },
  env: {
    ...base.env,
    browser: true,
  },
  rules: {
    ...base.rules,
    'vue/component-name-in-template-casing': [
      'error',
      'PascalCase',
    ],
  },
};

TypeScript/JavaScript 共存パターン

TypeScript と JavaScript が混在する環境での設定例です。

ファイル拡張子による分岐設定

javascript// packages/mixed-project/.eslintrc.js
module.exports = {
  extends: ['eslint:recommended'],
  env: {
    es2021: true,
    node: true,
  },
  overrides: [
    {
      // TypeScript ファイル用設定
      files: ['**/*.ts', '**/*.tsx'],
      parser: '@typescript-eslint/parser',
      plugins: ['@typescript-eslint'],
      extends: [
        'eslint:recommended',
        '@typescript-eslint/recommended',
        '@typescript-eslint/recommended-requiring-type-checking',
      ],
      parserOptions: {
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
      },
      rules: {
        '@typescript-eslint/no-unused-vars': 'error',
        '@typescript-eslint/explicit-function-return-type':
          'warn',
        '@typescript-eslint/no-explicit-any': 'warn',
      },
    },
    {
      // JavaScript ファイル用設定
      files: ['**/*.js', '**/*.jsx'],
      env: {
        es2021: true,
      },
      rules: {
        'no-unused-vars': 'error',
        'no-undef': 'error',
      },
    },
  ],
};

フロントエンド/バックエンド分離戦略

技術領域による明確な分離戦略です。

よくあるエラーと解決方法

エラー例 1: ブラウザ API の Node.js 環境での誤用

bash✗ 15:3  error  'document' is not defined  no-undef
✗ 16:3  error  'window' is not defined    no-undef

原因: フロントエンド用のコードがバックエンド環境で実行されている

javascript// 問題のあるコード(バックエンドパッケージ内)
function handleClick() {
  document
    .getElementById('button')
    .addEventListener('click', () => {
      window.location.href = '/dashboard';
    });
}

解決方法:

javascript// packages/backend/.eslintrc.js
module.exports = {
  env: {
    node: true,
    browser: false, // 明示的に無効化
  },
  rules: {
    'no-undef': 'error',
  },
};

エラー例 2: Node.js API のブラウザ環境での誤用

bash✗ 8:17  error  'process' is not defined  no-undef
✗ 9:17  error  'Buffer' is not defined   no-undef

解決方法:

javascript// packages/frontend/.eslintrc.js
module.exports = {
  env: {
    browser: true,
    node: false, // Node.js環境を無効化
  },
  globals: {
    // 必要に応じて特定のグローバル変数のみ許可
    process: 'readonly', // 環境変数アクセス用
  },
};

パフォーマンス最適化

大規模モノレポでの実行時間短縮

大規模なモノレポでは、ESLint の実行時間が大幅に増加する問題があります。

パフォーマンス問題の例

bash# 問題のある実行例
$ yarn lint
✓ Linting 15,000 files...
⚠ Completed in 180.5s (3 minutes)

# 最適化後の実行例
$ yarn lint
✓ Linting 2,500 files (changed files only)...
✓ Completed in 12.3s

実行時間短縮の戦略

json// package.json - 効率的なlintスクリプト
{
  "scripts": {
    "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx .",
    "lint:changed": "eslint --cache $(git diff --name-only --diff-filter=AM HEAD~1 | grep -E '\\.(js|jsx|ts|tsx)$' | tr '\\n' ' ')",
    "lint:package": "eslint --cache packages/$PACKAGE/src",
    "lint:parallel": "concurrently \"yarn lint:frontend\" \"yarn lint:backend\"",
    "lint:frontend": "eslint --cache packages/frontend",
    "lint:backend": "eslint --cache packages/backend"
  }
}

キャッシュ戦略と並列実行

効率的なキャッシュ戦略の実装例です。

ESLint キャッシュの最適化

javascript// .eslintrc.js
module.exports = {
  // ... 他の設定
  cache: true,
  cacheLocation: '.eslintcache',
  // ファイル変更時のみ再実行
};
bash# .gitignore
.eslintcache
node_modules/

並列実行の設定

json// package.json
{
  "scripts": {
    "lint:all": "concurrently --names \"web,api,shared\" \"yarn workspace web-app lint\" \"yarn workspace api-server lint\" \"yarn workspace shared-lib lint\""
  },
  "devDependencies": {
    "concurrently": "^7.6.0"
  }
}

変更検知による差分実行

Git 連携による効率的な差分実行の実装です。

変更ファイル検知スクリプト

javascript// scripts/lint-changed.js
const { execSync } = require('child_process');
const path = require('path');

function getChangedFiles() {
  try {
    const changedFiles = execSync(
      'git diff --name-only HEAD~1',
      {
        encoding: 'utf8',
      }
    )
      .split('\n')
      .filter((file) => file.match(/\.(js|jsx|ts|tsx)$/))
      .filter((file) => file.length > 0);

    return changedFiles;
  } catch (error) {
    console.warn('Git diff failed, linting all files');
    return [];
  }
}

function lintChangedFiles() {
  const changedFiles = getChangedFiles();

  if (changedFiles.length === 0) {
    console.log('No JavaScript/TypeScript files changed');
    return;
  }

  console.log(
    `Linting ${changedFiles.length} changed files...`
  );
  console.log(changedFiles.map((f) => `  ${f}`).join('\n'));

  try {
    execSync(
      `npx eslint --cache ${changedFiles.join(' ')}`,
      {
        stdio: 'inherit',
      }
    );
  } catch (error) {
    process.exit(1);
  }
}

lintChangedFiles();
json// package.json
{
  "scripts": {
    "lint:changed": "node scripts/lint-changed.js"
  }
}

運用とメンテナンス

設定変更の影響範囲管理

モノレポでは設定変更の影響範囲を正確に把握することが重要です。

影響範囲分析ツールの作成

javascript// scripts/analyze-eslint-impact.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');

function findEslintConfigs() {
  const configs = glob.sync('**/.eslintrc.{js,json,yaml}', {
    ignore: ['node_modules/**'],
  });

  return configs.map((configPath) => {
    const fullPath = path.resolve(configPath);
    const directory = path.dirname(fullPath);

    return {
      path: configPath,
      directory,
      affectedFiles: glob.sync('**/*.{js,jsx,ts,tsx}', {
        cwd: directory,
        ignore: ['node_modules/**'],
      }),
    };
  });
}

function analyzeConfigHierarchy() {
  const configs = findEslintConfigs();

  console.log('ESLint Configuration Hierarchy:');
  configs.forEach((config, index) => {
    console.log(`${index + 1}. ${config.path}`);
    console.log(
      `   Affected files: ${config.affectedFiles.length}`
    );
    console.log(`   Directory: ${config.directory}`);
    console.log('');
  });
}

analyzeConfigHierarchy();

設定変更時のチェックリスト

#チェック項目実行方法重要度
1影響範囲の確認node scripts​/​analyze-eslint-impact.js
2全パッケージでの lint 実行yarn lint:all
3CI/CD パイプラインでのテストyarn test:ci
4チーム内での設定レビュープルリクエストでの確認
5段階的ロールアウトパッケージ単位での適用

新パッケージ追加時の自動設定

新しいパッケージを追加する際の自動化戦略です。

パッケージテンプレートの作成

bash# scripts/create-package.sh
#!/bin/bash

PACKAGE_NAME=$1
PACKAGE_TYPE=$2  # frontend, backend, shared

if [ -z "$PACKAGE_NAME" ]; then
  echo "Usage: ./create-package.sh <package-name> <package-type>"
  exit 1
fi

PACKAGE_DIR="packages/$PACKAGE_NAME"

# ディレクトリ作成
mkdir -p "$PACKAGE_DIR/src"

# package.json 作成
cat > "$PACKAGE_DIR/package.json" << EOF
{
  "name": "@monorepo/$PACKAGE_NAME",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "lint": "eslint src --ext .js,.jsx,.ts,.tsx",
    "lint:fix": "yarn lint --fix",
    "test": "jest"
  }
}
EOF

# ESLint設定ファイル作成
case $PACKAGE_TYPE in
  "frontend")
    cp templates/.eslintrc.frontend.js "$PACKAGE_DIR/.eslintrc.js"
    ;;
  "backend")
    cp templates/.eslintrc.backend.js "$PACKAGE_DIR/.eslintrc.js"
    ;;
  "shared")
    cp templates/.eslintrc.shared.js "$PACKAGE_DIR/.eslintrc.js"
    ;;
  *)
    echo "Unknown package type: $PACKAGE_TYPE"
    exit 1
    ;;
esac

echo "Package $PACKAGE_NAME created successfully!"

テンプレート設定ファイル

javascript// templates/.eslintrc.frontend.js
module.exports = {
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  env: {
    browser: true,
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
  rules: {
    'react/prop-types': 'error',
  },
};

チーム間での設定合意形成

大規模開発チームでの設定合意形成のプロセスです。

設定変更プロセス

mermaidgraph TD
    A["設定変更提案"] --> B["技術検討"]
    B --> C["影響範囲分析"]
    C --> D["チーム内議論"]
    D --> E{合意形成}
    E -->|合意| F["段階的適用"]
    E -->|非合意| G["再検討"]
    F --> H["効果測定"]
    H --> I["本格運用"]
    G --> D

設定提案テンプレート

markdown# ESLint 設定変更提案

## 概要

[変更内容の概要]

## 背景・目的

[なぜこの変更が必要か]

## 変更内容

```javascript
// 変更前
// 既存の設定

// 変更後
// 新しい設定
```

影響範囲

  • 影響を受けるパッケージ: [パッケージ名]
  • 影響を受けるファイル数: [ファイル数]
  • 修正が必要なファイル: [リスト]

移行計画

  1. [ステップ 1]
  2. [ステップ 2]
  3. [ステップ 3]

リスク評価

  • 高リスク: [内容]
  • 中リスク: [内容]
  • 低リスク: [内容]
bash
## 実際の問題と解決事例

### よくある設定競合と対処法

モノレポ環境で頻繁に発生する設定競合の具体例と解決方法です。

**問題事例1: パーサー競合エラー**

```bash
✗ Error: Cannot read config file: /path/to/monorepo/packages/web-app/.eslintrc.js
✗ Error: You have used a rule which requires parserServices to be generated.

原因:

javascript// packages/web-app/.eslintrc.js (問題のある設定)
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    '@typescript-eslint/recommended-requiring-type-checking',
  ],
  // parserOptions.project が設定されていない
  rules: {
    '@typescript-eslint/no-floating-promises': 'error',
  },
};

解決方法:

javascript// packages/web-app/.eslintrc.js (修正後)
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: './tsconfig.json', // 必須設定
    tsconfigRootDir: __dirname,
  },
  extends: [
    '@typescript-eslint/recommended-requiring-type-checking',
  ],
  rules: {
    '@typescript-eslint/no-floating-promises': 'error',
  },
};

問題事例 2: 設定継承の無限ループ

bash✗ Error: Configuration for rule "import/order" is invalid
✗ Error: Circular dependency detected in extends chain

原因:

javascript// packages/frontend/.eslintrc.js
module.exports = {
  extends: ['../shared/.eslintrc.js'], // 相対パス参照
};

// packages/shared/.eslintrc.js
module.exports = {
  extends: ['../frontend/.eslintrc.js'], // 循環参照
};

解決方法:

javascript// eslint-config/base.js (共通設定を分離)
module.exports = {
  extends: ['eslint:recommended'],
  rules: {
    'no-unused-vars': 'error',
  },
};

// packages/frontend/.eslintrc.js
module.exports = {
  extends: ['../../eslint-config/base.js'],
  rules: {
    // フロントエンド固有のルール
  },
};

// packages/shared/.eslintrc.js
module.exports = {
  extends: ['../../eslint-config/base.js'],
  rules: {
    // 共有ライブラリ固有のルール
  },
};

パフォーマンス問題の診断と改善

大規模モノレポで発生するパフォーマンス問題の診断方法です。

パフォーマンス分析スクリプト

javascript// scripts/eslint-performance.js
const { performance } = require('perf_hooks');
const { ESLint } = require('eslint');
const glob = require('glob');

async function analyzePerformance() {
  const eslint = new ESLint({
    cache: true,
    cacheLocation: '.eslintcache',
  });

  const patterns = [
    'packages/web-app/src/**/*.{js,jsx,ts,tsx}',
    'packages/api-server/src/**/*.{js,ts}',
    'packages/shared-lib/src/**/*.{js,ts}',
  ];

  for (const pattern of patterns) {
    const files = glob.sync(pattern);
    console.log(
      `\nAnalyzing ${files.length} files in ${pattern}:`
    );

    const startTime = performance.now();
    const results = await eslint.lintFiles(files);
    const endTime = performance.now();

    const duration = endTime - startTime;
    const errorsCount = results.reduce(
      (sum, result) => sum + result.errorCount,
      0
    );
    const warningsCount = results.reduce(
      (sum, result) => sum + result.warningCount,
      0
    );

    console.log(`  Duration: ${duration.toFixed(2)}ms`);
    console.log(
      `  Files/sec: ${(
        files.length /
        (duration / 1000)
      ).toFixed(2)}`
    );
    console.log(
      `  Errors: ${errorsCount}, Warnings: ${warningsCount}`
    );
  }
}

analyzePerformance().catch(console.error);

パフォーマンス改善例

改善前:

bash$ node scripts/eslint-performance.js

Analyzing 1,247 files in packages/web-app/src/**/*.{js,jsx,ts,tsx}:
  Duration: 45,230.55ms
  Files/sec: 27.58
  Errors: 156, Warnings: 89

Analyzing 834 files in packages/api-server/src/**/*.{js,ts}:
  Duration: 32,123.22ms
  Files/sec: 25.97
  Errors: 23, Warnings: 12

改善後:

bash$ node scripts/eslint-performance.js

Analyzing 1,247 files in packages/web-app/src/**/*.{js,jsx,ts,tsx}:
  Duration: 8,456.33ms
  Files/sec: 147.45
  Errors: 156, Warnings: 89

Analyzing 834 files in packages/api-server/src/**/*.{js,ts}:
  Duration: 5,234.11ms
  Files/sec: 159.33
  Errors: 23, Warnings: 12

改善手法:

javascript// .eslintrc.js (最適化版)
module.exports = {
  root: true,
  cache: true, // キャッシュ有効化
  cacheLocation: '.eslintcache',

  // 不要なプラグインを削除
  plugins: [
    'import', // 必要最小限
  ],

  // パフォーマンスの良いルールセットを使用
  extends: [
    'eslint:recommended', // 軽量な基本セット
  ],

  // 重いルールを無効化
  rules: {
    'import/no-cycle': 'off', // 重い循環依存チェックを無効化
    'import/no-unused-modules': 'off', // 重い未使用モジュールチェックを無効化
  },

  // ignorePatterns を活用してファイル数を削減
  ignorePatterns: [
    'node_modules/',
    'dist/',
    'build/',
    'coverage/',
    '**/*.d.ts',
    'packages/*/lib/',
  ],
};

スケーリング時の課題対応

モノレポの規模拡大に伴う課題と対応策です。

段階的な課題と対応

フェーズパッケージ数主要課題対応策
小規模3-5 個基本設定の統一共通.eslintrc.js の作成
中規模6-15 個パフォーマンス劣化キャッシュ有効化、並列実行
大規模16-50 個設定管理の複雑化設定継承の階層化
超大規模50+個CI/CD 時間の増大差分実行、分散処理

超大規模環境での対応例

javascript// scripts/distributed-lint.js
const {
  Worker,
  isMainThread,
  parentPort,
  workerData,
} = require('worker_threads');
const os = require('os');
const glob = require('glob');

if (isMainThread) {
  // メインスレッド: ワーカーの管理
  async function distributedLint() {
    const allFiles = glob.sync(
      'packages/*/src/**/*.{js,jsx,ts,tsx}'
    );
    const numWorkers = os.cpus().length;
    const chunkSize = Math.ceil(
      allFiles.length / numWorkers
    );

    const workers = [];
    const promises = [];

    for (let i = 0; i < numWorkers; i++) {
      const start = i * chunkSize;
      const end = Math.min(
        start + chunkSize,
        allFiles.length
      );
      const files = allFiles.slice(start, end);

      if (files.length === 0) continue;

      const worker = new Worker(__filename, {
        workerData: { files },
      });

      const promise = new Promise((resolve, reject) => {
        worker.on('message', resolve);
        worker.on('error', reject);
      });

      workers.push(worker);
      promises.push(promise);
    }

    const results = await Promise.all(promises);

    // 結果の集約
    const totalErrors = results.reduce(
      (sum, result) => sum + result.errors,
      0
    );
    const totalWarnings = results.reduce(
      (sum, result) => sum + result.warnings,
      0
    );

    console.log(`Total errors: ${totalErrors}`);
    console.log(`Total warnings: ${totalWarnings}`);

    workers.forEach((worker) => worker.terminate());
  }

  distributedLint().catch(console.error);
} else {
  // ワーカースレッド: ESLintの実行
  const { ESLint } = require('eslint');
  const { files } = workerData;

  async function lintFiles() {
    const eslint = new ESLint({ cache: true });
    const results = await eslint.lintFiles(files);

    const errors = results.reduce(
      (sum, result) => sum + result.errorCount,
      0
    );
    const warnings = results.reduce(
      (sum, result) => sum + result.warningCount,
      0
    );

    parentPort.postMessage({ errors, warnings });
  }

  lintFiles().catch(console.error);
}

まとめ

モノレポ環境での ESLint 管理は、単一プロジェクトとは大きく異なる複雑な課題を抱えています。しかし、適切な設定戦略と運用手法を採用することで、これらの課題は効果的に解決できます。

重要なポイントの再確認

設定継承の階層化により、共通ルールと個別要件のバランスを取ることができます。技術スタック別の管理手法を導入することで、React、Vue、Node.js などの異なる環境でも統一された開発体験を提供できます。

パフォーマンス最適化については、キャッシュ活用と並列実行により大幅な高速化が可能です。変更検知による差分実行を組み合わせることで、大規模プロジェクトでも実用的な実行時間を実現できます。

運用面では、設定変更の影響範囲管理と新パッケージ追加の自動化により、継続的な改善サイクルを構築できます。チーム間での合意形成プロセスを確立することで、組織全体での品質向上につながります。

実際の問題解決においては、設定競合やパフォーマンス問題への対処法を習得することで、安定した運用が可能になります。スケーリング時の課題への準備も重要で、段階的な対応策を講じることで、プロジェクトの成長に対応できます。

モノレポでの ESLint 管理は継続的な改善が必要な分野です。定期的な設定見直しとチーム内での知識共有により、より効率的で品質の高い開発環境を構築していくことができるでしょう。

関連リンク