ESLint設定ファイルのFlat Config移行手順

GMOメイクショップ コアグループ エンジニアの森です。
我々のプロジェクトではメンバー間でのコードの一貫性を保つためにESLintを導入しています。
このESLintですがバージョンアップに伴い、設定ファイルの記法をFlat Configという形式にする必要が出てきました。今回はこの移行手順について書いていきます。

ESLint・Flat Configについて

ESLintとはJavaScriptやTypeScriptなどのコードの静的解析ツールです。コードスタイル等のルールを作成し、従っているかどうかを検証することができます。

eslint.org

従来の設定ファイルはeslintrcの形式で書かれていましたが、ESLintのバージョンアップに伴いFlat configがデフォルトとなり、2024年末から2025年初頭にかけてリリース予定のv10.0.0移行はeslintrcが完全に廃止される見込みです。
そのため2024年中にはeslintのFlat Config化が必要となります。 eslint.org

移行手順

ESLint設定の各プロパティの移行方法について、修正の前後を比較しながら説明します。
ここで紹介するコードは、実際に使用しているファイルから一部修正・省略したものです。

1.plugins

Pluginsプロパティは、rulesで使用するパッケージを登録します。 eslintrcではパッケージごと決められた命名で直接pluginsに追加していましたが、 Flat Configでは任意の名前でimportしてから追加する必要があります。

plugins: ['vue', 'import', 'unused-imports'],
import vue from 'eslint-plugin-vue'
import eslintPluginImport from 'eslint-plugin-import'
import eslintPluginUnusedImports from 'eslint-plugin-unused-imports'

  plugins: {
    vue,
    eslintPluginImport,
    eslintPluginUnusedImports,
  },

2.extends

extendsプロパティに追加すると、パッケージの共有設定を適用することができます。 Flat Configではextendsプロパティがなくなっているため、export defaultに直接追加します。

パッケージがFlat Configに対応していない場合

Flat Configに対応しているパッケージの場合はimportを直接書くことで移行できますが、未対応のパッケージには、FlatCompat()を使う必要があります。 eslint.org

パッケージがFlat Configに対応しているかはドキュメントやソースを確認する必要があります。 例えばeslint-plugin-vueの場合、ドキュメントにFlat Configでの書き方が記載されています。
eslint.vuejs.org

  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:vue-scoped-css/vue3-recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'eslint-config-prettier',
    'plugin:vuetify/base',
  ],
const compat = new FlatCompat({
  baseDirectory: dirname(fileURLToPath(import.meta.url)),
})


export default [
  eslintJs.configs.recommended,
  ...vue.configs['flat/recommended'],
  ...eslintPluginVueScopedCSS.configs['flat/recommended'],
  ...compat.extends('plugin:@typescript-eslint/eslint-recommended'),
  eslintConfigPrettier,
  ...compat.extends('plugin:vuetify/base'),
       ///  省略  ////
]

3.env・parser

envはグローバル変数を設定するためのオプションでしたが、Flat Configではglobalsに置き換わり、 parserと一緒にlanguageOptionsプロパティに定義します。
なお globals は事前にyarn add globalsコマンドでインストールする必要があります。

vue/setup-compiler-macros

なお移行したタイミングで'vue/setup-compiler-macros': trueがなくなっていますが、これはvue-eslint-parserのv9.0.0以降を使用している場合は不要になるため削除しました。 eslint.vuejs.org

  env: {
    'es6': true,
    'node': true,
    'mocha': true,
    'es2022': true,
    'vue/setup-compiler-macros': true,
  },
  parserOptions: {
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    ecmaVersion: 'latest',
    ecmaFeatures: {
      jsx: true,
      modules: true,
    },
  },
import vueEslintParser from 'vue-eslint-parser'
       ///  省略  ////
  languageOptions: {
    globals: {
      ...globals.node,
      ...globals.es2021,
    },
    parser: vueEslintParser,
    parserOptions: {
      ecmaVersion: 'latest',
    },
  },

4.rules

rulesはeslintrcもFlat Configも書き方は同じです。
ただしpluginsのimport名が変わっている場合は、それに合わせて修正が必要です。
import/ordereslintPluginImport/order

  rules: {
    'vue/no-deprecated-html-element-is': 2,
    'no-multi-spaces': 2,
    'import/order': [
      'error',
       ///  省略  ////
    ],
  },
  rules: {
    'vue/no-deprecated-html-element-is': 2,
    'no-multi-spaces': 2,
    'eslintPluginImport/order': [
      'error',
       ///  省略  ////
    ],
  },

5.overrides

overridesは対象のファイルに対して、別のルールを適用できるプロパティです。 Flat Configでは廃止されており、通常ルールと並べて配置することができます。 commonConfigexceptionConfigのようにルール毎に定義すると見やすくわかりやすいです。

  overrides: [
    {
      files: [
        'Exception.vue',
        'exception.ts',
      ],
      rules: {
        'no-restricted-imports': ['off'],
      },
    },
  ],
const exceptionConfig = {
  files: [
        'Exception.vue',
        'exception.ts',
  ],
  rules: {
    'no-restricted-imports': ['off'],
  },
}

export default [
       ///  省略  ////
  commonConfig,
  exceptionConfig,
]

6.ファイル全体

最終的に移行前後でファイル全体は以下のようになりました。

移行前 eslintrc

module.exports = {
  plugins: ['vue',  'import', 'unused-imports'],
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:vue-scoped-css/vue3-recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'eslint-config-prettier',
    'plugin:vuetify/base',
  ],
  env: {
    'es6': true,
    'node': true,
    'mocha': true,
    'es2022': true,
    'vue/setup-compiler-macros': true,
  },
  parserOptions: {
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    ecmaVersion: 'latest',
    ecmaFeatures: {
      jsx: true,
      modules: true,
    },
  },
  rules: {
    'vue/no-deprecated-html-element-is': 2,
    'no-multi-spaces': 2,
    'import/order': [
      'error',
       ///  省略  ////
    ],
  },
  overrides: [
    {
      files: [
        'Exception.vue',
        'exception.ts',
      ],
      rules: {
        'no-restricted-imports': ['off'],
      },
    },
  ],
}

移行後 Flat Config

import { fileURLToPath } from 'url'

import { FlatCompat } from '@eslint/eslintrc'
import eslintJs from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginImport from 'eslint-plugin-import'
import eslintPluginUnusedImports from 'eslint-plugin-unused-imports'
import vue from 'eslint-plugin-vue'
import eslintPluginVueScopedCSS from 'eslint-plugin-vue-scoped-css'
import globals from 'globals'
import { dirname } from 'pathe'
import vueEslintParser from 'vue-eslint-parser'

const compat = new FlatCompat({
  baseDirectory: dirname(fileURLToPath(import.meta.url)),
})

const commonConfig = {
  languageOptions: {
    globals: {
      ...globals.node,
      ...globals.es2021,
    },
    parser: vueEslintParser,
    parserOptions: {
      ecmaVersion: 'latest',
    },
  },
  plugins: {
    vue,
    eslintPluginImport,
    eslintPluginUnusedImports,
  },
  rules: {
    'vue/no-deprecated-html-element-is': 2,
    'no-multi-spaces': 2,
    'eslintPluginImport/order': [
      'error',
       ///  省略  ////
    ],
  },
}

const exceptionConfig = {
  files: [
        'Exception.vue',
        'exception.ts',
  ],
  rules: {
    'no-restricted-imports': ['off'],
  },
}

export default [
  eslintJs.configs.recommended,
  ...vue.configs['flat/recommended'],
  ...eslintPluginVueScopedCSS.configs['flat/recommended'],
  ...compat.extends('plugin:@typescript-eslint/eslint-recommended'),
  eslintConfigPrettier,
  ...compat.extends('plugin:vuetify/base'),
  commonConfig,
  exceptionConfig,
]

まとめ

eslintのFlat Config移行について書きましたが、これには既に先駆者が多くおり、私もたくさんの記事を参考にさせてもらいました。 環境によってはこの記事がマッチしないこともあると思いますので、他の記事と合わせて移行の参考にしていただければ嬉しいです。
今回のeslintに限らずツールやライブラリのバージョンアップには中々頭を悩ませることも多いので、余裕を持って対応しておきたいですね。

参考記事