T-CREATOR

ESLint シェアラブル設定の設計術:単一ソースで Web/Node/React をカバー

ESLint シェアラブル設定の設計術:単一ソースで Web/Node/React をカバー

複数のプロジェクトで同じ ESLint ルールを使いたいと思ったことはありませんか。Web アプリケーション、Node.js サーバー、React コンポーネントなど、異なる環境のプロジェクトを管理していると、それぞれに ESLint 設定を書くのは非効率です。

シェアラブル設定を活用すれば、一度設定を作るだけで複数のプロジェクトに適用でき、保守性も大幅に向上します。本記事では、単一の ESLint シェアラブル設定パッケージで Web・Node.js・React の環境をカバーする設計術をご紹介しましょう。

背景

ESLint シェアラブル設定とは

ESLint シェアラブル設定は、ESLint のルール設定を npm パッケージとして配布し、複数のプロジェクトで再利用できる仕組みです。eslint-config- というプレフィックスをつけて公開することで、他のプロジェクトから簡単に読み込めるようになります。

有名なものには eslint-config-airbnbeslint-config-standard などがありますね。これらは何千ものプロジェクトで使われており、その有用性が証明されています。

単一パッケージで複数環境をサポートする意義

従来は環境ごとに別々の設定パッケージを作ることもありましたが、以下の課題がありました。

  • パッケージが増えるほど管理コストが上がる
  • 共通ルールの変更時に複数パッケージを修正する必要がある
  • バージョン管理が煩雑になる

単一パッケージ内で複数の環境設定を提供することで、これらの課題を解決できます。

以下の図は、シェアラブル設定パッケージと各プロジェクトの関係を示しています。

mermaidflowchart TB
  pkg["eslint-config-myteam<br/>(シェアラブル設定パッケージ)"]

  subgraph configs["設定ファイル群"]
    base["base.js<br/>(共通設定)"]
    web["web.js<br/>(Web 用)"]
    node["node.js<br/>(Node.js 用)"]
    react["react.js<br/>(React 用)"]
  end

  subgraph projects["各プロジェクト"]
    pj1["Web アプリ"]
    pj2["Node.js API"]
    pj3["React SPA"]
  end

  pkg --> configs
  base --> web
  base --> node
  base --> react

  web --> pj1
  node --> pj2
  react --> pj3

このアーキテクチャにより、共通ルールは base.js に集約され、環境固有の設定だけを各ファイルに記述できます。

課題

環境ごとに異なるルールセット

Web ブラウザ、Node.js、React では、それぞれ利用できる API や推奨されるコーディングスタイルが異なります。

#環境特徴必要なルール例
1Web ブラウザDOM API、グローバル変数(window, document)no-restricted-globals
2Node.jsCommonJS、Node API(fs, path)node​/​no-unsupported-features
3ReactJSX 構文、Hooks ルールreact​/​jsx-uses-react, react-hooks​/​rules-of-hooks

これらの違いを考慮しつつ、共通部分はなるべく重複させずに設計する必要があります。

依存関係の肥大化

すべての環境に対応しようとすると、多くの ESLint プラグインをインストールすることになります。しかし、Node.js プロジェクトで React プラグインは不要ですよね。

無駄な依存関係を避けつつ、必要なプラグインだけをインストールできる仕組みが求められます。

以下の図は、依存関係の最適化イメージを示したものです。

mermaidflowchart LR
  subgraph opt["最適化後"]
    direction TB
    node_pj["Node.js<br/>プロジェクト"] -->|必要なもののみ| node_deps["eslint<br/>eslint-plugin-node"]
  end

  subgraph before["最適化前"]
    direction TB
    node_pj2["Node.js<br/>プロジェクト"] -->|すべて| all_deps["eslint<br/>eslint-plugin-node<br/>eslint-plugin-react<br/>..."]
  end

  before -.->|改善| opt

解決策

パッケージ構造の設計

単一パッケージ内で複数の設定ファイルを提供し、プロジェクトが必要な設定だけを選択できるようにします。以下のようなディレクトリ構造が効果的です。

csharpeslint-config-myteam/
├── package.json
├── index.js          # デフォルト(base と同じ)
├── base.js           # 共通設定
├── web.js            # Web ブラウザ用
├── node.js           # Node.js 用
└── react.js          # React 用

各設定ファイルは独立して読み込めるため、プロジェクトは .eslintrc.js で必要なものだけを指定できます。

共通設定の抽出

すべての環境で共通するルールを base.js に集約します。これにより、ルール変更時の修正が一箇所で済みますね。

共通ルールには以下のようなものが含まれます。

  • 構文エラーを検出するルール(no-undef, no-unused-vars など)
  • コードスタイルルール(indent, quotes, semi など)
  • ベストプラクティス(eqeqeq, no-eval など)
javascript// base.js - 共通設定
module.exports = {
  env: {
    es2021: true,
  },
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  rules: {
    // 構文エラー検出
    'no-undef': 'error',
    'no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
      },
    ],

    // コードスタイル
    indent: ['error', 2],
    quotes: ['error', 'single'],
    semi: ['error', 'always'],

    // ベストプラクティス
    eqeqeq: ['error', 'always'],
    'no-eval': 'error',
    'no-implied-eval': 'error',
  },
};

この設定は、どの環境でも有効な普遍的なルールのみを含んでいます。

環境別設定の継承

各環境の設定ファイルは base.js を継承し、環境固有のルールだけを追加します。ESLint の extends 機能を使えば、継承関係を簡潔に表現できますね。

以下は Web ブラウザ用の設定例です。

javascript// web.js - Web ブラウザ用設定
module.exports = {
  // 共通設定を継承
  extends: ['./base.js'],

  env: {
    browser: true,
  },

  rules: {
    // ブラウザ特有のルール
    'no-restricted-globals': [
      'error',
      'event', // 非推奨のグローバル変数
      'fdescribe', // テストフレームワークの誤用防止
    ],
  },
};

browser: true を指定することで、windowdocument などのグローバル変数がエラーにならなくなります。

次に Node.js 用の設定を見てみましょう。

javascript// node.js - Node.js 用設定
module.exports = {
  extends: ['./base.js'],

  env: {
    node: true,
  },

  plugins: ['node'],

  rules: {
    // Node.js 特有のルール
    'node/no-unsupported-features/es-syntax': [
      'error',
      { version: '>=14.0.0' },
    ],
    'node/no-missing-require': 'error',
    'node/no-extraneous-require': 'error',
  },
};

Node.js 環境では、CommonJS の require や Node API に関するルールを追加します。

最後に React 用の設定です。

javascript// react.js - React 用設定
module.exports = {
  extends: [
    './base.js',
    './web.js', // React は Web 環境なので web.js も継承
  ],

  parserOptions: {
    ecmaFeatures: {
      jsx: true, // JSX 構文を有効化
    },
  },

  plugins: ['react', 'react-hooks'],

  settings: {
    react: {
      version: 'detect', // インストール済みの React バージョンを自動検出
    },
  },

  rules: {
    // React 特有のルール
    'react/jsx-uses-react': 'error',
    'react/jsx-uses-vars': 'error',
    'react/prop-types': 'warn',

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

React 設定は web.js も継承することで、ブラウザ環境のルールも適用されます。

以下の図は、設定ファイル間の継承関係を示しています。

mermaidflowchart TB
  base["base.js<br/>(共通ルール)"]
  web["web.js<br/>(ブラウザ環境)"]
  node["node.js<br/>(Node.js 環境)"]
  react["react.js<br/>(React)"]

  base -->|継承| web
  base -->|継承| node
  base -->|継承| react
  web -->|継承| react

  style base fill:#e1f5ff
  style web fill:#fff4e1
  style node fill:#e8f5e1
  style react fill:#ffe1f5

図で理解できる要点:

  • すべての設定は base.js を起点とする
  • react.jsweb.jsbase.js の両方を継承
  • 継承により重複コードを排除できる

peerDependencies の活用

各環境で必要なプラグインは peerDependencies として定義します。これにより、プロジェクト側で必要なプラグインだけをインストールできますね。

json{
  "name": "eslint-config-myteam",
  "version": "1.0.0",
  "description": "Shareable ESLint config for Web/Node/React",
  "main": "index.js",
  "peerDependencies": {
    "eslint": ">=8.0.0"
  },
  "peerDependenciesMeta": {
    "eslint-plugin-node": {
      "optional": true
    },
    "eslint-plugin-react": {
      "optional": true
    },
    "eslint-plugin-react-hooks": {
      "optional": true
    }
  }
}

peerDependenciesMetaoptional: true を指定することで、プロジェクトが必要に応じてインストールできるようになります。

エントリーポイントの設定

index.js をデフォルトのエントリーポイントとし、最も汎用的な設定を提供します。

javascript// index.js - デフォルト設定
module.exports = {
  extends: ['./base.js'],
};

シンプルに base.js を継承するだけで、環境を指定しないプロジェクトでも利用できます。

具体例

パッケージの公開

作成したシェアラブル設定を npm に公開します。パッケージ名は eslint-config- で始める必要があります。

bash# パッケージディレクトリで実行
yarn init

# package.json を編集後
yarn publish

公開後は、他のプロジェクトから yarn add でインストールできるようになりますね。

Web プロジェクトでの利用

Web ブラウザ向けプロジェクトでは、シェアラブル設定の web 部分を指定します。

bash# 必要なパッケージをインストール
yarn add -D eslint eslint-config-myteam

プロジェクトの .eslintrc.js で設定を読み込みます。

javascript// .eslintrc.js - Web プロジェクト
module.exports = {
  extends: ['myteam/web'],

  // プロジェクト固有の設定があれば追加
  rules: {
    // 例: このプロジェクトでは console.log を許可
    'no-console': 'off',
  },
};

eslint-config- プレフィックスは省略でき、myteam​/​web と書くだけで eslint-config-myteam パッケージの web.js が読み込まれます。

Node.js プロジェクトでの利用

Node.js サーバーアプリケーションでは、node 設定を使用します。

bash# Node.js 用プラグインも追加
yarn add -D eslint eslint-config-myteam eslint-plugin-node

プロジェクトの .eslintrc.js で Node.js 用設定を指定します。

javascript// .eslintrc.js - Node.js プロジェクト
module.exports = {
  extends: ['myteam/node'],

  // プロジェクト固有の設定
  rules: {
    // 例: 開発中は console.log を許可
    'no-console':
      process.env.NODE_ENV === 'production'
        ? 'error'
        : 'off',
  },
};

Node.js 環境特有のルールが適用され、CommonJS や Node API の使用が適切にチェックされます。

React プロジェクトでの利用

React アプリケーションでは、React と React Hooks のプラグインも必要です。

bash# React 用プラグインを含めてインストール
yarn add -D eslint eslint-config-myteam eslint-plugin-react eslint-plugin-react-hooks

.eslintrc.js で React 設定を指定します。

javascript// .eslintrc.js - React プロジェクト
module.exports = {
  extends: ['myteam/react'],

  // プロジェクト固有の設定
  rules: {
    // 例: prop-types は TypeScript を使うので無効化
    'react/prop-types': 'off',
  },
};

JSX 構文や Hooks のルールが適用され、React のベストプラクティスに沿った開発ができますね。

以下の図は、各プロジェクトでの設定読み込みフローを示しています。

mermaidsequenceDiagram
  participant PJ as プロジェクト
  participant RC as .eslintrc.js
  participant PKG as eslint-config-myteam
  participant BASE as base.js
  participant ENV as 環境設定(web/node/react)

  PJ->>RC: ESLint 実行
  RC->>PKG: extends: ['myteam/web']
  PKG->>ENV: web.js を読み込み
  ENV->>BASE: base.js を継承
  BASE-->>ENV: 共通ルールを提供
  ENV-->>PKG: 環境固有ルールを追加
  PKG-->>RC: 統合された設定を返す
  RC-->>PJ: リントを実行

図で理解できる要点:

  • プロジェクトは環境別設定を指定するだけでよい
  • 継承関係により共通ルールが自動的に適用される
  • 各段階で設定が統合され、最終的な設定が生成される

複数環境の組み合わせ

一つのプロジェクトで複数の環境を使い分けることもできます。例えば、Node.js サーバーに React の管理画面が含まれる場合などです。

javascript// .eslintrc.js - ハイブリッドプロジェクト
module.exports = {
  // デフォルトは Node.js 設定
  extends: ['myteam/node'],

  overrides: [
    {
      // src/client ディレクトリは React 設定
      files: ['src/client/**/*.{js,jsx}'],
      extends: ['myteam/react'],
    },
  ],
};

overrides を使えば、ファイルパスに応じて異なる設定を適用できますね。

TypeScript 対応の追加

TypeScript プロジェクトにも対応させるには、TypeScript 用の設定ファイルを追加します。

javascript// typescript.js - TypeScript 用設定
module.exports = {
  extends: ['./base.js'],

  parser: '@typescript-eslint/parser',

  plugins: ['@typescript-eslint'],

  rules: {
    // JavaScript のルールを無効化し、TypeScript 版を使用
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
      },
    ],

    // TypeScript 特有のルール
    '@typescript-eslint/explicit-function-return-type':
      'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
  },
};

TypeScript では JavaScript のルールの一部を無効化し、TypeScript 対応版のルールに置き換える必要があります。

React + TypeScript の組み合わせも簡単に対応できます。

javascript// react-typescript.js - React + TypeScript 用設定
module.exports = {
  extends: ['./typescript.js', './react.js'],

  rules: {
    // TypeScript を使う場合は prop-types は不要
    'react/prop-types': 'off',
  },
};

このように、既存の設定を組み合わせることで、新しい環境への対応も効率的に行えますね。

設定のテスト

シェアラブル設定が正しく動作するか、テストを書くことをお勧めします。

javascript// test/configs.test.js
const { ESLint } = require('eslint');
const path = require('path');

describe('ESLint Configs', () => {
  test('base config loads successfully', async () => {
    const eslint = new ESLint({
      useEslintrc: false,
      baseConfig: require('../base.js'),
    });

    const results = await eslint.lintText('const x = 1;');
    expect(results[0].errorCount).toBe(0);
  });

  test('web config detects browser globals', async () => {
    const eslint = new ESLint({
      useEslintrc: false,
      baseConfig: require('../web.js'),
    });

    // window はエラーにならない
    const results = await eslint.lintText(
      'window.addEventListener("load", () => {});'
    );
    expect(results[0].errorCount).toBe(0);
  });
});

テストを書くことで、設定変更時の影響を確認でき、品質を保てます。

バージョン管理とリリース

シェアラブル設定のバージョンは Semantic Versioning に従うべきです。

#バージョン変更内容
1Major(例: 1.0.0 → 2.0.0)既存ルールの厳格化、破壊的変更
2Minor(例: 1.0.0 → 1.1.0)新しいルールの追加、非破壊的変更
3Patch(例: 1.0.0 → 1.0.1)バグ修正、ドキュメント更新

ルールを厳しくする変更は、既存プロジェクトで新たなエラーを引き起こす可能性があるため、Major バージョンアップとして扱います。

bash# Minor バージョンアップの例
yarn version minor
yarn publish

CHANGELOG.md を用意し、変更内容を記録することも重要ですね。

まとめ

ESLint のシェアラブル設定を単一パッケージで設計することで、複数の環境に対応しつつ保守性の高いコード品質管理が実現できます。

本記事でご紹介した設計パターンのポイントは以下の通りです。

  • 共通ルールを base.js に集約し、環境別設定で継承する
  • 複数の設定ファイルを提供し、プロジェクトが必要なものを選択できるようにする
  • peerDependenciespeerDependenciesMeta で依存関係を最適化する
  • overrides を活用して、一つのプロジェクト内で複数環境に対応する

このアプローチにより、組織全体でコードスタイルを統一しながら、各プロジェクトの特性に応じた柔軟な設定が可能になります。新しい環境への対応も既存設定の組み合わせで実現でき、拡張性も高いですね。

ぜひあなたのチームでもシェアラブル設定を導入し、効率的なコード品質管理を実現してください。

関連リンク