2024.12.14

pnpm workspace+TurborepoでモノレポらしくESLint Flat Configをやる

はじめに

スーパーハムスターでは、フロントエンドもバックエンドもTypeScriptで実装する技術スタック、いわゆるフルスタックTSを採用することが多く、pnpmとTurborepoを用いてMonorepo環境で開発しています。

本記事では、Monorepo環境でESLint Flat Configを設定する方法を解説します。

今回の実装内容はテンプレートリポジトリとして公開しています。 andmohiko/next-hono-monorepo

Eslint Flat Configとは

ESLint v9.0.0から、Flat Configという新しい設定ファイル形式がデフォルトとなります。それに伴い、これまで使われていた.eslintrc形式は非推奨となり、v10.0.0(公式の予定では2024年末から2025年初頭)で完全に削除される予定です。

eslintrc形式からの変更点

従来は.eslintrc.eslintrc.jsなどのファイルに設定を記述していましたが、Flat Configではeslint.config.jsに記述するよう変更されました。 eslintrc形式からFlat Configへの仕組み的な変更点をまとめると、overrideやextendsという仕組みをやめることと、JavaScriptっぽい書き方になったことの2つになります。記法的な変更点としてはpluginやlanguageOptionsの書き方が変わりました。 詳しくは 仕組みと嬉しさから理解するeslint FlatConfig対応 の解説がわかりやすかったです。

Monorepoの構成

今回は、pnpm workspaceとTurborepoを使用したMonorepo環境を構築します。フロントエンドにはNext.js、バックエンドにはHonoを利用します。使用した各ライブラリのバージョンは以下の通りです。

・TypeScript: 5.7.2 ・Next.js: 15.1.0 ・Hono: 4.6.13 ・ESLint: 9.16.0

Monorepoの構成としては、ワークスペースはappspackagesの2種類を用意します。appsはWebアプリケーションとしてビルド・デプロイするプロジェクトを配置し、packagesはWebアプリケーション以外の共通ライブラリやツール、設定用のパッケージを配置します。

このとき、ESLintの設定ファイルの置き場所として、 ・Monorepoのrootに一つだけ置く ・各パッケージごとに設定ファイルを置く という選択肢があります。

Monorepoを使用する場合、コードや設定を共通化することで、Monorepoのメリットをより感じることができます。 そのため、ESLintの設定をパッケージ化し、pnpmのworkspaceプロトコルを使用して、appsや他のpackagesにインストールできるようにします。 各パッケージではESLintの設定パッケージを使用することでconfigを共通化しつつ、独自のeslint.config.jsを持つことでパッケージごとの設定も記述することができます。

最終的に以下のような構成を目指します。

Monorepo
├── apps
│   ├── backend // Hono
│   │   ├── src
│   │   ├── eslint.config.mjs
│   │   └── package.json
│   └── frontend // Next.js
│       ├── src
│       ├── eslint.config.mjs
│       └── package.json
└── packages
    ├── common // 共通で使用するもの
    │   ├── src
    │   ├── eslint.config.mjs
    │   └── package.json
    └── eslint-config // ESLintの共通の設定
        ├── .gitignore
        ├── index.js // 実際にFlat Configを書くファイル
        └── package.json

実装していく

ESLintのパッケージを作成する

まずはMonorepo内に以下のようなパッケージを追加します。

Monorepo
└── packages
    └── eslint-config // ESLintの設定
        ├── .gitignore
        ├── index.js // 実際にflat configを書いていくファイル
        └── package.json

eslintrc形式からの大きな変更点としてまず@eslint/jsglobalsをインストールする必要があります。

package.jsonはこのようになりました。

{
  "name": "@next-hono-monorepo/eslint-config",
  "version": "0.0.0",
  "publishConfig": {
    "access": "public"
  },
  "devDependencies": {
    "@eslint/compat": "^1.2.4",
    "@eslint/js": "^9.16.0",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "eslint": "9.16.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-import": "^2.29.1",
    "globals": "^15.13.0",
    "typescript-eslint": "^8.18.0"
  }
}

Flat Configを書いてみる

それでは先ほどインストールしたライブラリを使って実際にFlat Configを書いていきます。

変更点がわかりやすいところだけピックアップしました。実際に書いたファイルはこちらで見ることができます。

const eslint = require('@eslint/js')
const globals = require('globals')
const tsEsLintParser = require('@typescript-eslint/parser')
const tseslint = require('typescript-eslint')
const prettierConfig = require('eslint-config-prettier')
const importPlugin = require('eslint-plugin-import')

module.exports = tseslint.config(
  {
    ignores: ['node_modules', 'dist'],
  },
  {
    // 従来はparserOptionsに書いていたものの書き方が少し変わりました
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
      parser: tsEsLintParser,
      ecmaVersion: 'latest',
      sourceType: 'module',
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
  },
  // extends, pluginsに記述していたものもflatに書いていきます
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  // prettierの設定
  prettierConfig,
  {
    rules: {
      // 省略: ここは従来と同じ
    }
  }
)

eslint-configを各パッケージで使用する

先ほど実装したeslint-configパッケージをapps内で使用してみましょう。今回は、Next.jsのパッケージを例に説明します。

1. package.jsonにeslint-configパッケージを追加

package.jsonのdevDependenciesに、eslint-configパッケージを追加します。

"devDependencies": {
  "@next-hono-monorepo/eslint-config": "workspace:*" // 追加
}

2. eslint.config.mjsを作成し、eslint-configをインポート

apps/frontendディレクトリ内にeslint.config.mjsファイルを作成し、以下のようにeslint-configパッケージをインポートします。

import custom from '@next-hono-monorepo/eslint-config';

3. Flat Configを記述

この設定ファイルはNext.js専用のものとなるため、reactやnextのルールも追記します。ただし注意点として、eslint-plugin-react-hooksはFlat Configにまだ対応していないため、FlatCompatを使用する必要があります。

以下は完成したeslint.config.mjsの例です。全体像はこちらから見ることができます。

import custom from '@next-hono-monorepo/eslint-config'
import tseslint from 'typescript-eslint'
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'
import reactPlugin from 'eslint-plugin-react'
import nextPlugin from '@next/eslint-plugin-next'
import { FlatCompat } from '@eslint/eslintrc'

const flatCompat = new FlatCompat()

export default tseslint.config(
  ...custom,
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat['jsx-runtime'],
  jsxA11yPlugin.flatConfigs.recommended,
  // Flat Config未対応
  ...flatCompat.extends('plugin:react-hooks/recommended'),
  {
    ignores: [
      '**/node_modules/*',
      '**/out/*',
      '**/.next/*',
      'eslint.config.mjs',
    ],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    settings: {
      react: {
        version: 'detect',
      },
      next: {
        rootDir: './apps/frontend',
      },
      'import/resolver': {
        node: {
          extensions: ['.js', '.ts', '.json'],
        },
        typescript: {
          config: 'tsconfig.json',
          project: './apps/frontend',
          alwaysTryTypes: true,
        },
      },
    },
  },
  {
    name: 'next/core-web-vitals',
    plugins: {
      '@next/next': nextPlugin,
    },
    rules: {
      ...nextPlugin.configs.recommended.rules,
      ...nextPlugin.configs['core-web-vitals'].rules,
      '@next/next/no-html-link-for-pages': ['error', './src/pages/'],
    },
  },
  {
    rules: {
      // 省略: 従来と同様
    },
  },
)

さいごに

今回は、pnpm workspaceとTurborepoを活用したMonorepo環境において、Flat Configを使用したESLint設定の共通化方法を解説しました。この方法を採用することで、設定を各パッケージ間で一元管理でき、開発効率や保守性を高めることができます。

実際に書いてみると、今までoverridesやextendsによって入れ子になっていたルールが一次元の配列で記述することができ、flatなconfigであることが感じられると思います。

今回公開しているGitHubリポジトリには本記事で紹介しなかったパッケージのeslint.config.jsファイルも見ることができます。また、tsconfigの設定も共通化してみました。

よかったらみなさんのプロジェクトでも使ってみてください。

参考

monorepo環境でeslint flat configを導入してみた仕組みと嬉しさから理解するeslint FlatConfig対応2024年9月 俺の eslint.config.jsESLint を eslintrc から Flat Config に移行する、ハマりポイントを添えて。[v14まで] Next.js で ESLint の flat config を設定してみる