Next.js 15のCompatを使わないFlat Config移行
自分のブログをNest.js 15に上げるので Flat Configも対応する
現状は以下の構成
- eslint
- typescript-eslint
- eslint-config-next
- eslint-plugin-import
- eslint-plugin-jsx-a11y
- eslint-plugin-storybook
eslint-config-next
pnpm create next-app@canary
の成果物でもFlatCompat
が使われている
eslint.config.mjs
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;
コメントにあるように、@next/eslint-plugin-next
から直接pluginの指定をすればFlatCompatを回避できる
import pluginNext from "@next/eslint-plugin-next";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint-plugin-next
{
files: ["**/*.{ts,tsx}"],
plugins: {
"@next/next": pluginNext,
},
rules: {
...pluginNext.configs.recommended.rules,
...pluginNext.configs["core-web-vitals"].rules,
},
},
]
依存パッケージはeslint-config-next
を消して@next/eslint-plugin-next
だけでいい
eslint-config-next
は以下を内包してるのでこれらを別途設定していく
eslint-plugin-react
eslint-plugin-import
eslint-plugin-jsx-a11y
typescript-eslint
TypeScriptの解析ができないのでtypescript-eslintを先頭に追加する
ドキュメントではtseslint.config
を
使用しているが型はTSDocで推論されるし中身を見ても特別必要性を感じなかったので使ってない
{...tseslint.configs.recommended}
とすると3つの配列が展開されparser
周り、通常eslintのoverride、@typescript-eslint
周りのルール追加がされる。
極力どんな設定が何によってされているかを設定ファイルから知れるようにしておきたいので自分はそれぞれ手動で設定した。recommended
が配列の末尾指定になるのが微妙
import tseslint from "typescript-eslint";
/** @type {import("eslint").Linter.Config[]} */
export default [
// typescript-eslint
...tseslint.configs.recommended
// or
{
name: tseslint.configs.base.name,
languageOptions: tseslint.configs.base.languageOptions,
plugins: tseslint.configs.base.plugins,
rules: {
...tseslint.configs.eslintRecommended.rules,
// https://github.com/typescript-eslint/typescript-eslint/blob/cb0ef9ceb73057bc90fa54229e2d4828432530b5/packages/eslint-plugin/src/configs/recommended.ts
...tseslint.configs.recommended.at(-1).rules,
// custom rules...
},
},
// eslint-plugin-next
{...},
]
eslint
通常eslintのrecommendedも設定
typescript-eslintより前に設定する
import eslint from "@eslint/js";
/** @type {import("eslint").Linter.Config[]} */
export default [
{
rules: {
...eslint.configs.recommended.rules,
// custom rules...
},
},
// typescript-eslint
{...},
// eslint-plugin-next
{...},
]
テストは都度print-configで行う
npx eslint --print-config src/xxx
eslint-plugin-react
ドキュメントを参考にする
例によってreactPlugin.configs.flat.recommended
をそれぞれのプロパティで指定している
ここでの工夫ポイントはeslint-config-next
で指定されていたreact関係のルールを設定すること
不要なら設定しなくてよい
import pluginReact from "eslint-plugin-react";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint
{...},
// typescript-eslint
{...},
// eslint-plugin-react
{
files: ["**/*.tsx"],
plugins: pluginReact.configs.flat.recommended.plugins,
languageOptions: pluginReact.configs.flat.recommended.languageOptions,
rules: {
...pluginReact.configs.flat.recommended.rules,
// custom rules...
// https://github.com/vercel/next.js/blob/f348241e25767e1e08909de19cdab032d63e369b/packages/eslint-config-next/index.js#L62-L79
"react/no-unknown-property": "off",
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/jsx-no-target-blank": "off",
},
settings: {
react: {
version: "detect",
},
},
},
// eslint-plugin-next
{...},
]
eslint-plugin-react-hooks
現状の構成に入ってないことに気づいたので追加する
5.0.0でflat configの対応が入った。
コメントを参考に設定する
import pluginReactHooks from "eslint-plugin-react-hooks";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint
{...},
// typescript-eslint
{...},
// eslint-plugin-react
{...},
// eslint-plugin-react-hooks
{
files: ["**/*.{ts,tsx}"],
plugins: {
"react-hooks": pluginReactHooks,
},
rules: {
...pluginReactHooks.configs.recommended.rules,
},
},
// eslint-plugin-next
{...},
]
eslint-plugin-jsx-a11y
ドキュメントを参考に設定する
例によってjsxA11y.flatConfigs.recommended
をそれぞれ指定する。
ここでもeslint-config-next
で設定されていたルールがあったので追加する
import pluginJsxA11y from "eslint-plugin-jsx-a11y";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint
{...},
// typescript-eslint
{...},
// eslint-plugin-react
{...},
// eslint-plugin-react-hooks
{...},
// eslint-plugin-next
{...},
// eslint-plugin-jsx-a11y
{
files: ["**/*.{ts,tsx}"],
plugins: pluginJsxA11y.flatConfigs.recommended.plugins,
rules: {
...pluginJsxA11y.flatConfigs.recommended.rules,
// https://github.com/vercel/next.js/blob/f348241e25767e1e08909de19cdab032d63e369b/packages/eslint-config-next/index.js#L67-L78
"jsx-a11y/alt-text": [
"warn",
{
elements: ["img"],
img: ["Image"],
},
],
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-proptypes": "warn",
"jsx-a11y/aria-unsupported-elements": "warn",
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-supports-aria-props": "warn",
},
},
]
eslint-plugin-import
ドキュメントを参考に設定する
例によって importPlugin.flatConfigs.recommended
をそれぞれ指定する。
ここでもeslint-config-next
の設定を追加する
eslint-import-resolver-typescript
を依存関係に追加しないとTypeScriptで機能しない。
ここら辺はeslint-config-next
がよしなにしていた
import pluginImport from "eslint-plugin-import";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint
{...},
// typescript-eslint
{...},
// eslint-plugin-react
{...},
// eslint-plugin-react-hooks
{...},
// eslint-plugin-next
{...},
// eslint-plugin-jsx-a11y
{...},
// eslint-plugin-import
{
files: ["**/*.{ts,tsx}"],
plugins: pluginImport.flatConfigs.recommended.plugins,
rules: {
...pluginImport.flatConfigs.recommended.rules,
// https://github.com/vercel/next.js/blob/f348241e25767e1e08909de19cdab032d63e369b/packages/eslint-config-next/index.js#L63
"import/no-anonymous-default-export": "warn",
// custom rules...
},
settings: {
"import/resolver": {
typescript: true,
},
},
},
]
eslint-plugin-storybook
対象ファイルごとにオブジェクトが分かれているのでここでは...pluginStorybook.configs["flat/recommended"]
とした
import pluginStorybook from "eslint-plugin-storybook";
/** @type {import("eslint").Linter.Config[]} */
export default [
// eslint
{...},
// typescript-eslint
{...},
// eslint-plugin-react
{...},
// eslint-plugin-react-hooks
{...},
// eslint-plugin-next
{...},
// eslint-plugin-jsx-a11y
{...},
// eslint-plugin-import
{...},
// eslint-plugin-storybook
// https://github.com/storybookjs/eslint-plugin-storybook/blob/6202eb23901fc249758cd225f8a10b3032d6a9b8/lib/configs/flat/recommended.ts
...pluginStorybook.configs["flat/recommended"],
]
おわり
全体像はこちら
flat config関係ないけどGUIで設定を確認できるのが便利だったnpx eslint --inspect-config
eslint-plugin-next
の設定でjs
とmjs
(eslint.configの拡張子)をfiles
に設定しないとそれぞれerror とwarningが出る
設定ファイルの拡張子を追加しないとnextがプラグインの存在を見つけられずThe Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/app/api-reference/config/eslint#migrating-existing-config
というwarningが報告され続ける
issueたてました
// eslint-plugin-next
{
// include js because of reporting `Key "rules": Key "@next/next/no-html-link-for-pages": Could not find plugin "@next/next".`
// include mjs because of https://github.com/vercel/next.js/issues/73655
files: ["**/*.{js,mjs,ts,tsx}"],
plugins: {
"@next/next": pluginNext,
},
rules: {
...pluginNext.configs.recommended.rules,
...pluginNext.configs["core-web-vitals"].rules,
},
},