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の構成としては、ワークスペースはapps
とpackages
の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/js
とglobals
をインストールする必要があります。
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.js ・ESLint を eslintrc から Flat Config に移行する、ハマりポイントを添えて。 ・[v14まで] Next.js で ESLint の flat config を設定してみる