23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

remixのESLintの設定をFlat Configで記述し直す

Last updated at Posted at 2024-03-19

はじめに

ESLintのバージョン9.0.0からFlat Configという設定ファイルの新たな記法がデフォルトで有効になり、元の記法が非推奨となります。そしてバージョン10.0.0では元の記法がサポートされなくなります。

サポートが切れるため元の記法で記述していた場合はFlat Configへ書き直す必要があります。しかし、同じ設定を記述するには記法が大きく変化したため慣れや工夫が必要となります。

この記事ではFlat Configに移行することへ慣れるための練習として、create-remixで生成されるremixのESLintの設定をFlat Configに書き直します。
remixの設定はReactを開発する上で一般的なルールが記載されているのでeslint-config-nextのような設定がまとまったパッケージを使っていない多くの環境にとって参考になると考えています。

成果物はGitHubで公開しています。

Flat Configについての背景や設定について正確に知りたい場合はこれらのサイトを閲覧することがおすすめです。

移行前の環境準備

まずはcreate-remixでremixの環境を作成します。

npx create-remix@latest

以下がremixのバージョン2.8.1におけるESLintの設定です。

.eslintrc.cjs
/**
 * This is intended to be a basic starting point for linting in your app.
 * It relies on recommended configs out of the box for simplicity, but you can
 * and should modify this configuration to best suit your team's needs.
 */

/** @type {import('eslint').Linter.Config} */
module.exports = {
  root: true,
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    ecmaFeatures: {
      jsx: true,
    },
  },
  env: {
    browser: true,
    commonjs: true,
    es6: true,
  },

  // Base config
  extends: ["eslint:recommended"],

  overrides: [
    // React
    {
      files: ["**/*.{js,jsx,ts,tsx}"],
      plugins: ["react", "jsx-a11y"],
      extends: [
        "plugin:react/recommended",
        "plugin:react/jsx-runtime",
        "plugin:react-hooks/recommended",
        "plugin:jsx-a11y/recommended",
      ],
      settings: {
        react: {
          version: "detect",
        },
        formComponents: ["Form"],
        linkComponents: [
          { name: "Link", linkAttribute: "to" },
          { name: "NavLink", linkAttribute: "to" },
        ],
        "import/resolver": {
          typescript: {},
        },
      },
    },

    // Typescript
    {
      files: ["**/*.{ts,tsx}"],
      plugins: ["@typescript-eslint", "import"],
      parser: "@typescript-eslint/parser",
      settings: {
        "import/internal-regex": "^~/",
        "import/resolver": {
          node: {
            extensions: [".ts", ".tsx"],
          },
          typescript: {
            alwaysTryTypes: true,
          },
        },
      },
      extends: [
        "plugin:@typescript-eslint/recommended",
        "plugin:import/recommended",
        "plugin:import/typescript",
      ],
    },

    // Node
    {
      files: [".eslintrc.cjs"],
      env: {
        node: true,
      },
    },
  ],
};

ESLintの実行はnpm run lintで実行できます。package.jsonscriptsを確認するとnpx eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .で実行されていることがわかります。

Flat Configへの移行

create-remixで生成された.eslintrc.cjsを元にeslint.config.jsを記述します。remixのパッケージではデフォルトがESMとなっているためESM形式を用います(package.jsontypemoduleになっていない場合は拡張子をmjsにした方が良いかもしれません)。

テストファイルを作る

Flat Configへ移行した後に設定が有効になっていることを確かめるため以下のようなファイルを作成します。

import { Link, NavLink } from "@remix-run/react";
import { useEffect } from "react";

// violate eslint recommendation
for (let i = 0; i < 10; i--) {
  console.log(i);
}

// violate react recommendation
() => {
  return <>{
    [1, 2, 3].map((i) => <div>{i}</div>)
  }</>;
};

// violate react-hooks recommendation
const _ = () => {
  const flag = true;
  if (flag) {
    useEffect(() => {
      console.log('effect');
    }, []);
  }
  return <div />;
};
_;

// violate jsx-a11y recommendation
const App = () => {
  return (
    <>
      <a>Link</a>
      <Link to="/about">About</Link>
      <NavLink to="/contact">Contact</NavLink>
    </>
  );
};

// violate typescript-eslint recommendation
Array(0, 1, 2);

eslint.config.jsを作成する前にnpm run lintを実行して、それぞれ異なるパッケージからのエラーが5つ出ることを確認していください。

このファイルはエラーが発生することを確認するために作っただけなので、他のファイルを適当に準備しても問題ありません。

このファイルは移行前後で設定が同一であることを保証するものではないです。それぞれの代表的なルールが有効であることを確かめるために記述していますので、厳密にやりたい場合は他の方法を検討してください。
ESLint Config Inspectorでは有効なルールを可視化してくれるのでおすすめです(記事)。

設定ファイルを作る

eslint.config.jsファイルを作ります。中身は以下のように記述してください。

eslint.config.js
/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default [];

作成したらESLintは.eslintrc.cjsではなくeslint.config.jsを設定ファイルとして読みにいきます。npx eslint --debugを実行してLoading config from /xxx/eslint.config.jsと表示されることを確認してください。

Flat Configでは設定を有効するファイル単位ごとや、複数の設定を同時に記述するために配列で設定を記述します。

無視するファイル

前の記法では無視するファイルをcliで--ignore-path .gitignoreのように渡していました。Flat Configでは無視するファイルを設定ファイルに記述します。
以下のようにignoresに無視するファイルの一覧を詰め込んで配列に流し込むことで無効化されます。

eslint.config.js
/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default [
  {
    ignores: ['node_modules', '.cache', 'build', 'public/build', '.env'],
  },
];

配列に記述された設定は最終的にマージされて1つの設定ファイルとなるので、これによってignoresに記述したファイルに対して実行されなくなります。

package.jsonscriptsからも--ignore-pathが不要になったので消します。

package.json
  "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",

Base(プロジェクト全体の設定)

元ファイルのコメントに合わせてBaseReactTypeScriptNodeに分けて移行します。
まずはBaseです。Baseは特別にコメントの範囲を超えてoverrides以外の全ての設定を記述します。

root
.eslintrc.cjs
  root: true,

前の記法は実行されるファイルからルートのまでのすべての設定ファイルを読み込んでいましたが、Flat Configは最も近い祖先のファイルだけを読み込むのでルートファイルという概念がなくなりroot: trueの宣言自体が不要になりました。

parserOptions
.eslintrc.cjs
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    ecmaFeatures: {
      jsx: true,
    },
  },

ecmaVersionsourceTypeparserだけではなく構成等にも反映するためlanguageOptionsへ書かれるようになり、parserOptions自体もlanguageOptionsへ記述されるようになりました。

eslint.config.js
{
  languageOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    parserOptions: {
      ecmaFeatures: {
        jsx: true
      },
    },
  },
},
env
.eslintrc.cjs
  env: {
    browser: true,
    commonjs: true,
    es6: true,
  },

Flat Configではenvを削除して、languageOptionsglobalsに記述するようになりました。そして、globalsパッケージを利用して環境の情報をよりわかりやすく提供するようになりました(globals.jsonに環境ごとの設定が列挙されており、それを展開しています)。

eslint.config.js
import globals from 'globals';
...
...
{
  languageOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    globals: {
      ...globals.browser,
      ...globals.commonjs,
      ...globals.es6,
    },
    parserOptions: {
      ecmaFeatures: {
        jsx: true
      },
    },
  },
},

globalsパッケージはeslintをインストールした時の依存パッケージとしてインストールされているのでそのままでも利用出来ますが、直接インポートするのでdevDependenciesに追加した方が良いです。

npm install --save-dev globals

筆者の環境では@remix-run/devに依存する@babel/traverseが古いglobalsを利用しているのでインストールしなければ動きませんでした。
スクリーンショット 2024-03-18 17.10.15.png

eslint recommended
eslintrc.cjs
  // Base config
  extends: ["eslint:recommended"],

eslintは当然のことながらFlat Configに対応しているのでドキュメント通りに実装すれば良いです。
@eslint/jsからすでに定義済みの設定を持ってきて先ほどまで記述していた箇所に展開します。定義された内容はeslintのGitHubから確認できます。rulesを記述しているだけだったので、languageOptionsは上書きするような形で配置しました。

eslint.config.js
import globals from 'globals';
import eslint from '@eslint/js';
...
...
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        },
      },
    },
  },
まとめ

ここまでの移行で設定ファイルは以下のようになりました。

eslint.config.js
import globals from 'globals';
import eslint from '@eslint/js';

/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default tseslint.config(
  {
    ignores: ['node_modules', '.cache', 'build', 'public/build', '.env'],
  },
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        },
      },
    },
  },
);

npx eslint . --debugで関係ないファイルが実行対象になっていないこと、エラー確認用のコードから期待したエラーが返ることを確認できます。

React

次にReactです。eslint-plugin-reacteslint-plugin-react-hookseslint-plugin-jsx-a11y等の設定をします。

.eslintrc.cjsではoverridesを用いて特定のファイルに対するルールを記述していましたが、Flat Configでは他のルールと同じように配列に追加して、設定内のfilesによって実行対象のファイルを制限します。

eslint.config.js
/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default tseslint.config(
  {
    ...
  },
  {
    ...
  },
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...
  },
);

eslint-plugin-react

eslint-plugin-reactREADMEのFalt Configのサポートについてを参考に実装します。
eslint-plugin-reactに関連する部分を.eslintrc.cjsから抜き出すと以下のようになります(今後部分ごとに参照する場合は対象の箇所を抜き出した状態で紹介します)。

.eslintrc.cjs
    // React
    {
      files: ["**/*.{js,jsx,ts,tsx}"],
      plugins: ["react"],
      extends: [
        "plugin:react/recommended",
        "plugin:react/jsx-runtime",
      ],
      settings: {
        react: {
          version: "detect",
        },
        formComponents: ["Form"],
        linkComponents: [
          { name: "Link", linkAttribute: "to" },
          { name: "NavLink", linkAttribute: "to" },
        ],
      },
    },

settingsはこれまで通り記述できるので、recommendedjsx-runtimeの箇所を書き換えるだけです。

eslint.config.js
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
...
... 
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
    },
  },

recommendedjsx-runtimeに実装された内容を元にしてそれぞれ展開しています。React17以降のJSXの変換へ対応するためにrecommendedのルールをjsx-runtimeのルールで上書きして欲しいのでjsx-runtimeの方を後から宣言しました。
rulesの内容をそれぞれ確認するとrecommendedの方は'react/react-in-jsx-scope': 2に対してjsx-runtimeでは'react/react-in-jsx-scope': 0となっていてこの記述では0が有効になります。

eslint-plugin-react-hooks

eslint-plugin-reactとは異なり、パッケージ自体でFlat Confingの対応が行われていないのでFlat Configで扱えるように変換する必要があります。
このように対応されていないパッケージとの互換性のためにFlatCompatというクラスが準備されています(使い方についてのブログ)。

.eslintrc.cjs
    // React
    {
      files: ["**/*.{js,jsx,ts,tsx}"],
      extends: [
        "plugin:react-hooks/recommended",
      ],
    },

new FlatCompat()で生成したcompatを元にeslint-plugin-react-hooksrecommendedをFlat Configで利用できるように最適化します。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
...
const compat = new FlatCompat();
...
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    extends: [
      ...compat.config(reactHooksPlugin.configs.recommended),
    ],
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
    },
  },
eslint-plugin-jsx-a11y

このライブラリもeslint-plugin-react-hooksと同じくFlat Configの対応が完了しておりません。

.eslintrc.cjs
    // React
    {
      files: ["**/*.{js,jsx,ts,tsx}"],
      plugins: ["jsx-a11y"],
      extends: [
        "plugin:jsx-a11y/recommended",
      ],
    },

先ほどと異なる点はpluginsがあることです。pluginsは対応するパッケージに対応させるように宣言します。

eslint.config.js

import { FlatCompat } from '@eslint/eslintrc';
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
...
const compat = new FlatCompat();
...
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    plugins: {
      ['jsx-a11y']: jsxA11yPlugin,
    },
    extends: [
      ...compat.config(reactHooksPlugin.configs.recommended),
      ...compat.config(jsxA11yPlugin.configs.recommended),
    ],
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
    },
  },
まとめ

対応していないsettings"import/resolver"を新しいファイルに移した段階でのファイルは以下のようになっています。

eslint.config.js
import globals from 'globals';
import eslint from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
import reactHooksPlugin from 'eslint-plugin-react-hooks';

const compat = new FlatCompat();

/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default tseslint.config(
  {
    ignores: ['node_modules', '.cache', 'build', 'public/build', '.env'],
  },
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        },
      },
    },
  },
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    plugins: {
      ['jsx-a11y']: jsxA11yPlugin,
    },
    extends: [
      ...compat.config(reactHooksPlugin.configs.recommended),
      ...compat.config(jsxA11yPlugin.configs.recommended),
    ],
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
      "import/resolver": {
        typescript: {},
      },
    },
  },
);

Typescript

続いてTypeScriptです。typescript-eslinteslint-plugin-importの対応です。
まずはTypeScriptのファイルに対してルールを記述する場所を作ります。

eslint.config.js
/**
 * @type {import('eslint').Linter.FlatConfig[]}
 */
export default tseslint.config(
  {
    ...
  },
  {
    ...
  },
  {
    ...
  },
  {
    files: ["**/*.{ts,tsx}"],
    ...
  },
);
eslint-plugin-import

eslint-plugin-react-hooksと同じようにFlatCompatを利用して移行します。

.eslintrc.js
    {
      files: ["**/*.{ts,tsx}"],
      plugins: ["import"],
      settings: {
        "import/internal-regex": "^~/",
        "import/resolver": {
          node: {
            extensions: [".ts", ".tsx"],
          },
          typescript: {
            alwaysTryTypes: true,
          },
        },
      },
      extends: [
        "plugin:import/recommended",
        "plugin:import/typescript",
      ],
    },

settingsはそのまま移行して、plguinsextendsはパッケージをインポートして記述します。

eslint.config.js
import importPlugin from 'eslint-plugin-import';
...
...
  {
    files: ["**/*.{ts,tsx}"],
    plugins: {
      import: importPlugin,
    },
    extends: [
      ...compat.config(importPlugin.configs.recommended),
      ...compat.config(importPlugin.configs.typescript),
    ],
    settings: {
      "import/internal-regex": "^~/",
      "import/resolver": {
        node: {
          extensions: [".ts", ".tsx"],
        },
        typescript: {
          alwaysTryTypes: true,
        },
      },
    },
  },
typescript-eslint

typescript-eslintはFlat Configのサポートがされており、リリースブログを元に移行します。
まずは、利用するパッケージが変更されたのでこれまでの@typescript-eslint/parser@typescript-eslint/eslint-pluginをアンインストールしてtypescript-eslintを導入します。

npm uninstall @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install --save-dev typescript-eslint

そして、これまで記述してきたESLintの設定全体をtseslint.configで囲みます。配列は展開して関数の引数に渡します。

eslint.config.js
import tseslint from 'typescript-eslint';
...
...
export default tseslint.config(
  {
    ...
  },
  {
    ...
  },
  {
    ...
  },
  {
    ...
  },
);

これによって設定ファイル内の型やエディターの補完を強化してくれます。記述しなくても動きますが、typescript-eslintでは利用することを強く推奨しています。

typescript-eslintを利用する準備ができたので移行します。

.eslintrc.cjs

    {
      files: ["**/*.{ts,tsx}"],
      plugins: ["@typescript-eslint"],
      parser: "@typescript-eslint/parser",
      extends: [
        "plugin:@typescript-eslint/recommended",
      ],
    },

プラグインやパーサー等は自動で解決してくれるため不要です。extendsimportと合わせて記述します。

eslint.config.js
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';
...
...
  {
    files: ["**/*.{ts,tsx}"],
    plugins: {
      import: importPlugin,
    },
    extends: [
      ...tseslint.configs.recommended,
      ...compat.config(importPlugin.configs.recommended),
      ...compat.config(importPlugin.configs.typescript),
    ],
    settings: {
      "import/internal-regex": "^~/",
      "import/resolver": {
        node: {
          extensions: [".ts", ".tsx"],
        },
        typescript: {
          alwaysTryTypes: true,
        },
      },
    },
  },
まとめ

tslint.configが加わって全体的な変化が加わりました。

eslint.config.js
import globals from 'globals';
import eslint from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import importPlugin from 'eslint-plugin-import';

const compat = new FlatCompat();

export default tseslint.config(
  {
    ignores: ['node_modules', '.cache', 'build', 'public/build', '.env'],
  },
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        },
      },
    },
  },
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    plugins: {
      react: reactPlugin,
      ['jsx-a11y']: jsxA11yPlugin,
    },
    extends: [
      ...compat.config(reactHooksPlugin.configs.recommended),
      ...compat.config(jsxA11yPlugin.configs.recommended),
    ],
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
      "import/resolver": {
        typescript: {},
      },
    },
  },
  {
    files: ["**/*.{ts,tsx}"],
    plugins: {
      import: importPlugin,
    },
    extends: [
      ...tseslint.configs.recommended,
      ...compat.config(importPlugin.configs.recommended),
      ...compat.config(importPlugin.configs.typescript),
    ],
    settings: {
      "import/internal-regex": "^~/",
      "import/resolver": {
        node: {
          extensions: [".ts", ".tsx"],
        },
        typescript: {
          alwaysTryTypes: true,
        },
      },
    },
  },
);

Node

最後にNodeです。設定ファイルに対するenvを記述しています。

esilntrc.js
    {
      files: [".eslintrc.cjs"],
      env: {
        node: true,
      },
    },

eslintの設定ファイル名だけ変えて、envlanguageOptions.globalsglobalsパッケージを使って記述します。

eslint.config.js
  {
    files: ['eslint.config.js'],
    languageOptions: {
      globals: {
        ...globals.node
      },
    },
  }

最終的なファイル

最終的なeslint.config.jsは以下のようになりました。

eslint.config.js
import globals from 'globals';
import eslint from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactRecommended from 'eslint-plugin-react/configs/recommended.js';
import reactJSXRuntime from 'eslint-plugin-react/configs/jsx-runtime.js';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import importPlugin from 'eslint-plugin-import';

const compat = new FlatCompat();

export default tseslint.config(
  {
    ignores: ['node_modules', '.cache', 'build', 'public/build', '.env'],
  },
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.es6,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        },
      },
    },
  },
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    ...reactRecommended,
    ...reactJSXRuntime,
    rules: {
      ...reactRecommended.rules,
      ...reactJSXRuntime.rules,
    },
    languageOptions: {
      ...reactRecommended.languageOptions,
      ...reactJSXRuntime.languageOptions,
    },
    plugins: {
      react: reactPlugin,
      ['jsx-a11y']: jsxA11yPlugin,
    },
    extends: [
      ...compat.config(reactHooksPlugin.configs.recommended),
      ...compat.config(jsxA11yPlugin.configs.recommended),
    ],
    settings: {
      react: {
        version: "detect",
      },
      formComponents: ["Form"],
      linkComponents: [
        { name: "Link", linkAttribute: "to" },
        { name: "NavLink", linkAttribute: "to" },
      ],
      "import/resolver": {
        typescript: {},
      },
    },
  },
  {
    files: ["**/*.{ts,tsx}"],
    plugins: {
      import: importPlugin,
    },
    extends: [
      ...tseslint.configs.recommended,
      ...compat.config(importPlugin.configs.recommended),
      ...compat.config(importPlugin.configs.typescript),
    ],
    settings: {
      "import/internal-regex": "^~/",
      "import/resolver": {
        node: {
          extensions: [".ts", ".tsx"],
        },
        typescript: {
          alwaysTryTypes: true,
        },
      },
    },
  },
  {
    files: ['eslint.config.js'],
    languageOptions: {
      globals: {
        ...globals.node
      },
    },
  }
);

npm run lintで実行できることを確認してください。エラー用のファイルで想定していたエラーとeslint.config.jseslint-plugin-importのエラーが表示されているはずです。
確認できたら、tseslintのインポート部分を以下のようにして再度表示されないことを確認したら移行完了です。

import { config, configs } from 'typescript-eslint';

おまけ

VSCodeを使っている方

VSCodeのESLintではFlat Configはまだ実験的に導入されているだけなので、利用している場合はuseFlatConfigtrueにすることを忘れないようにしてください。

.vscode/settings.json
{
  "eslint.experimental.useFlatConfig": true,
}

ルールをカスタマイズ

remixの初期設定は推奨されたまま使われていることが多かったので、明示的にルールを追加する方法を紹介します。
typescript-eslintno-unused-vars_だけ許可することを考えます。
その場合はplugins@typescript-eslinttypescript-eslintplguinと対応させてrulesでそのスコープからルールを持ってきて内容を定義すると有効になります。

import { config, configs, plugin } from 'typescript-eslint';
...
...
  {
    files: ["**/*.{ts,tsx}"],
    plugins: {
      ['@typescript-eslint']: plugin,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': [
        'error',
        {
          argsIgnorePattern: "^_",
          varsIgnorePattern: "^_",
          caughtErrorsIgnorePattern: "^_",
          destructuredArrayIgnorePattern: "^_"
        },
      ],
    },
  },

@typescript-eslintではない名前をスコープに定義できますが、@typescript-eslintで宣言することが公式で推奨されています。

23
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?