2024年9月 俺の eslint.config.js
はじめに
「Flat Config が何かはわかったからサンプルをくれ!」という方向けの記事です。
筆者が普段使用している eslint.config.js をご紹介します。
Flat Config については良記事がたくさんあるため ↓ などを参照してください。
今回ご紹介する設定は、React × TypeScript を使用し、フォーマットは Prettier に任せる前提になっています。
しかし、Vue やサーバー Node.js でも使いまわせる箇所は多いと思います。
eslint.config.js
まずは結論から。
// @ts-check
import { fixupConfigRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import eslint from '@eslint/js';
import vitestPlugin from '@vitest/eslint-plugin';
import prettierConfig from 'eslint-config-prettier';
import * as importPlugin from 'eslint-plugin-import';
import jestDomPlugin from 'eslint-plugin-jest-dom';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import perfectionistPlugin from 'eslint-plugin-perfectionist';
import reactPlugin from 'eslint-plugin-react';
import spellcheckPlugin from 'eslint-plugin-spellcheck';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
import globals from 'globals';
import tseslint from 'typescript-eslint';
const flatCompat = new FlatCompat();
export default tseslint.config(
{
// このオブジェクトは ignores プロパティだけにする必要あり
ignores: ["dist"], // ESLint のチェック対象外 (node_modules と .git はデフォルトで対象外)
},
{
languageOptions: {
globals: globals.browser,
},
},
// Shareable Configs を有効化
eslint.configs.recommended,
...tseslint.configs.strict, // strict は recommended よりも厳しめな設定
reactPlugin.configs.flat.recommended,
reactPlugin.configs.flat['jsx-runtime'],
jsxA11yPlugin.flatConfigs.recommended,
vitestPlugin.configs.recommended,
jestDomPlugin.configs['flat/recommended'],
// Flat Config 未対応のプラグインは FlatCompat を使用
...flatCompat.extends('plugin:react-hooks/recommended'),
// ESLint v9 で削除された API "context.getScope" を内部で使用しているプラグインは fixupConfigRules で対応
...fixupConfigRules(
flatCompat.extends(
// "plugin:import/recommended", // TODO: 現時点だと色々動かないので eslint-plugin-import が Flat Config に対応したら有効化する
'plugin:testing-library/react',
'plugin:storybook/recommended'
)
),
{
// eslint-plugin-react の設定
settings: {
react: {
version: 'detect',
},
},
// recommended に含まれていない eslint-plugin-react のルールを有効化
rules: {
'react/destructuring-assignment': 'error', // Props などの分割代入を強制
'react/function-component-definition': [
// コンポーネントの定義方法をアロー関数に統一
'error',
{
namedComponents: 'arrow-function',
unnamedComponents: 'arrow-function',
},
],
'react/hook-use-state': 'error', // useState の返り値の命名を [value, setValue] に統一
'react/jsx-boolean-value': 'error', // boolean 型の Props の渡し方を統一
'react/jsx-fragments': 'error', // React Fragment の書き方を統一
'react/jsx-curly-brace-presence': 'error', // Props と children で不要な中括弧を削除
'react/jsx-no-useless-fragment': 'error', // 不要な React Fragment を削除
'react/jsx-sort-props': 'error', // Props の並び順をアルファベット順に統一
'react/self-closing-comp': 'error', // 子要素がない場合は自己終了タグを使う
'react/jsx-pascal-case': 'error', // コンポーネント名をパスカルケースに統一
'react/no-danger': 'error', // dangerouslySetInnerHTML を許可しない
'react/prop-types': 'off', // Props の型チェックは TS で行う & 誤検知があるため無効化
},
},
{
// eslint-plugin-react-hooks の設定
rules: {
'react-hooks/exhaustive-deps': 'error', // recommended では warn のため error に上書き
},
},
{
// eslint-plugin-import の設定
plugins: { import: importPlugin },
rules: {
'import/order': [
// import の並び順を設定
'warn',
{
groups: [
'builtin',
'external',
'internal',
['parent', 'sibling'],
'object',
'type',
'index',
],
'newlines-between': 'always',
pathGroupsExcludedImportTypes: ['builtin'],
alphabetize: { order: 'asc', caseInsensitive: true },
pathGroups: [
{
pattern: 'react',
group: 'external',
position: 'before',
},
],
},
],
},
},
{
// eslint-plugin-unused-imports の設定
plugins: { 'unused-imports': unusedImportsPlugin },
rules: {
'@typescript-eslint/no-unused-vars': 'off', // 重複エラーを防ぐため typescript-eslint の方を無効化
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'error',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
},
},
{
// @vitest/eslint-plugin の設定
rules: {
'vitest/consistent-test-it': ['error', { fn: 'test' }], // it ではなく test に統一
},
},
{
// eslint-plugin-spellcheck の設定
plugins: { spellcheck: spellcheckPlugin },
rules: {
'spellcheck/spell-checker': [
'error',
{
minLength: 5, // 5 文字以上の単語をチェック
// チェックをスキップする単語の配列
skipWords: [
"noreferrer",
"compat",
"vitest",
"tseslint",
"globals",
"fixup",
],
},
],
},
},
{
// eslint-plugin-perfectionist の設定
plugins: { perfectionist: perfectionistPlugin },
rules: {
'perfectionist/sort-interfaces': 'warn', // interface のプロパティの並び順をアルファベット順に統一
'perfectionist/sort-object-types': 'warn', // Object 型のプロパティの並び順をアルファベット順に統一
},
},
prettierConfig // フォーマット は Prettier で行うため、フォーマット関連のルールを無効化
);
サンプルとして Vite プロジェクトに導入したリポジトリはこちら
使用ライブラリ
eslint
ESLint 本体
@eslint/js
ESLint 公式の Shareable Configs
README によると JavaScript 固有の機能を ESLint から分離する取り組みの始まり
らしい。
@eslint/@compat
README より
このパッケージには、ESLint v8.x で使用することを目的とした既存の ESLint ルール、プラグイン、および構成をラップして、ESLint v9.x でそのまま動作できるようにする関数が含まれています。
typescript-eslint
ESLint で TypeScript を解析するためのツール群。
@vitest/eslint-plugin
先日 Vitest の公式ライブラリになり、eslint-plugin-vitest から @vitest/eslint-plugin に変わった。
Jest 使っている人は eslint-plugin-jest を使おう。
eslint-config-prettier
フォーマット関連のルールを無効化する。設定ファイルの一番最後に書く。
eslint-plugin-prettier は非推奨なのでこっちを使おう。
eslint-plugin-import
🚧 2024 年 9 月 18 日時点で Flat Config 未対応。
import 文の並び順を揃えるために入れている。
eslint-plugin-jest-dom
@testing-library/jest-dom の使い方をチェックする。
例えば ↓ のようなルールがある
// prefer-to-have-text-content ルール
expect(button.textContent).toBe('ユーザー名取得'); // NG
expect(button).toHaveTextContent('ユーザー名取得'); // OK
eslint-plugin-jsx-a11y
アクセシビリティチェックしてくれる。Storybook の A11y チェックも合わせて使っている。
eslint-plugin-perfectionist
色々並び替えしてくれる。この記事の設定では interface と type のプロパティをアルファベット順にしている。
eslint-plugin-react
recommended 以外にも有用なルールが結構あるので有効化している。詳しくは ↓
eslint-plugin-react-hooks
🚧 2024 年 9 月 18 日時点で Flat Config 未対応。
公式の設定ではなぜか exhaustive-deps
が warn
なので、error
に上書きして放置されないようにしよう。
eslint-plugin-spellcheck
スペルチェックしてくれる。チェックの対象外にする単語を skipWords
に追加するのが少し手間だが、レビューで typo を指摘するよりはいいと思っている。
eslint-plugin-storybook
🚧 2024 年 9 月 18 日時点で Flat Config 未対応。
Storybook 入れるならとりあえず入れておく。
eslint-plugin-testing-library
🚧 2024 年 9 月 18 日時点で Flat Config 未対応。
Testing Library ファミリーの使い方をチェックする。
例えば不要な await をエラーにする。
expect(await screen.getByRole('heading')).toHaveTextContent('Hello'); // NG
expect(screen.getByRole('heading')).toHaveTextContent('Hello'); // OK
// 逆に findBy は await がないとエラーになる
expect(screen.findByRole('heading')).toHaveTextContent('Hello'); // NG
expect(await screen.findByRole('heading')).toHaveTextContent('Hello'); // OK
eslint-plugin-unused-imports
未使用 import を自動削除してくれる。
globals
各環境のグローバル変数を定義している。
使い方
- ライブラリのインストール
# eslint-plugin-import と eslint-plugin-testing-library の peerDependencies の影響で --force をつける必要あり
npm install -D --force \
eslint \
@eslint/js \
@eslint/compat \
typescript-eslint \
@vitest/eslint-plugin \
eslint-config-prettier \
eslint-plugin-import \
eslint-plugin-jest-dom \
eslint-plugin-jsx-a11y \
eslint-plugin-perfectionist \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-spellcheck \
eslint-plugin-storybook \
eslint-plugin-testing-library \
eslint-plugin-unused-imports \
globals
-
eslint.config.js ファイルを作成して、記事に記載のコードを貼り付ける
- package.json の
type
がmodule
でない場合は、拡張子を .mjs にする
- package.json の
-
package.json を修正
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
- 実行
npm run lint
npm run lint:fix
その他
- eslint-config-next : Next.js やるなら必須
- eslint-plugin-react-native : React Native やるなら入れてもいい
- eslint-plugin-playwright : Playwright 使うなら入れたい
導入しようか検討中のもの
- eslint-plugin-promise : Promise 関連のルール
- eslint-plugin-unicorn : 良さげなルールがたくさんある (雑)
- eslint-plugin-n : Node.js 関連のルール
必要なのか検証中のもの
- eslint-plugin-react-refresh : Vite でプロジェクトを作成するとデフォルトで入っている。ルール的に Fail になるコードを書いても、ステートを維持したまま Fast Refresh が機能するので、どんな場合に役立つか調べたい。(Fast Refresh の仕様を調べる必要がありそう)
まとめ
改めて設定ファイルを見直してみると、ESLint の設定はやはり複雑で手間がかかると感じます。特に現在は Flat Config への移行期ということもあり、各ライブラリのドキュメントや、場合によってはライブラリのコードを読む必要が出てきます。
そこで、Biome を使うことでこの課題をどう解消できるか、また同様のルールを再現できるかを現在検証中です。Biome の検証結果も記事にしたいと思います。
最後までお読みいただきありがとうございました 🙇♂️
Discussion
色々と知らないプラグインも多く参考になりました。
実は FlatCompatのあとにもESLintから色々とツールがリリースされてまして、
この中の
fixupPluginRules
を用いたところ自分の環境では importPlugin も動かせましたのでよろしかったらお試しください。