1
/
5

ESLint v9 対応奮闘記

Photo by Max Chen on Unsplash

こんにちは。ウォンテッドリーで Frontend Chapter Leader をしている原 剛士です。

この記事は Wantedly Advent Calendar 2024 の3日目の記事です。昨日は要さんの「開発組織における「自ら律する」ということ」でした。初日、2日目はそれぞれ CTO, VPoE から、カルチャーやマネジメントのお話でしたが、本日は現場の実務に寄った内容になります。

この記事では、ESLint v8 の EOL を受けて社内用の ESLint パッケージの v9 対応のためにやったことを振り返ります。ESLint v9 にアップデート対応に苦労している方々のお役に立てれば幸いです。

目次

  • frolint とは

  • ESLint v9 対応の必要性

  • frolint での ESLint v9 対応奮闘記

  • eslint-plugin-wantedly の plugin 実装の v9 ランタイム対応

  • eslint-config-wantedly-* の Flat configuration へのマイグレーション

  • frolint スクリプトの v9 対応

  • あと片付け

  • ◇ 余談: コミットメッセージと実体の食い違い

  • 各リポジトリでの対応奮闘記

  • Flat config への移行指南書

  • ◇ 変更内容

  • ◇ 動作確認

  • 参考資料

  • 公式ドキュメント類

  • 有名ライブラリ・フレームワークの ESLint v9 対応 PR

  • 関連ブログ

  • まとめ

frolint とは

frolint とはウォンテッドリーがメンテナンスしている、ESLint sharable config と plugin と、ESLint, prettier をラップしたスクリプトを集めたリポジトリのことです。このリポジトリは公開リポジトリで、関連パッケージは npm に公開されています。frolint は以下のようなパッケージを含むモノリポです。

  • eslint-config-wantedly: JS 向けの推奨 ESLint ルールセット
  • eslint-config-wantedly-typescript: TS 向けの推奨 ESLint ルールセット
  • eslint-plugin-wantedly: ウォンテッドリー社内向けのカスタムルール plugin。主に GraphQL 使用箇所のためのルールを含んでいる。
  • frolint: Git の pre-commit 時に ESLint, Prettier を実行するためのスクリプト。社内リポジトリでは husky, lint-staged に移行が決定しており、新規の使用は非推奨。

ウォンテッドリーの社内リポジトリではこれらを使用することを基本としています。もちろん個別リポジトリの状況に応じてルールをオーバーライドすることもありますが、どこのリポジトリも基本ルールセットは同じものを使用することで全体として大体同じようなコードになるようになっています。

ESLint v9 対応の必要性

ESLint v8 は 2024/10/05 をもって End of life を迎えると公式から発表があり、v9 へのアップデートが求められています。
https://eslint.org/blog/2024/09/eslint-v8-eol-version-support/

EOL を迎えても、まったく使えなくなるということはありませんが、v8 系については今後一切のアップデートはありません。放置しておくとどんどんアップデートが難しくなっていくのが JS のライブラリ群です。なるべく最新に追従していくために9月〜10月にかけてアップデートの対応を行いました。

また、ESLint v9 からは設定ファイル周りで大きな変更があります。いわゆる "Flat configuration" と呼ばれるものがデフォルトになりました。
これまでの設定ファイル(Legacy Configuration と呼ばれます)は JS, JSON, YAML, package.json の eslint フィールドなど、さまざまなフォーマットで記述が可能でした。
Flat configuration では JS で記述することが必須となり、ファイル名も eslint.config.js (eslint.config.cjseslint.config.mjs) になりました。
記述スタイルもこれまでと大きく異なり、ESLint が内部で持っていたモジュール読み込みシステムを使わず、JS のモジュール読み込みシステムを使用することができるため、外部の config や plugin の使用がより分かりやすくなりました。
Flat config 自体の詳細な話は公式ドキュメントや、他の記事に説明を譲ります。(本記事の最後に参考にしたドキュメントやブログなどのリンク集をまとめています。)

ESLint v9 ではデフォルトで Flat config を使用するとなっていますが、移行期ということで ESLINT_USE_FLAT_CONFIG という環境変数を false にして実行すると以前の設定ファイルをそのまま利用できます。
最初はこの機能を使い、Flat config への移行を先送りしようと考えていました。つまり、ESLint v9 までバージョンアップするが設定ファイルはそのままのフォーマットを使用、ESLINT_USE_FLAT_CONFIG=false をつけて実行することで、 flat configuration へのマイグレーションを先送りにする予定でした。(v10 のリリースまでに対応予定でした。)
しかしながら実際この方法を取ろうとしたところ、手元でうまく動かすことができなかったため、このタイミングで ESLint v9 へのアップデートと Flat config へのマイグレーションを同時に行うこととしました。

frolint での ESLint v9 対応奮闘記

ここからは frolint の ESLint v9 対応のコミットログを追いながら、どういったことに対応していったのかを見ていきます。
大きく次のようなセクションに分かれます。

  • eslint-plugin-wantedly の plugin 実装の v9 ランタイム対応
  • eslint-config-wantedly-* の Flat configuration へのマイグレーション
  • frolint スクリプトの v9 対応
  • あと片付け

それぞれ詳しく見ていきます。

eslint-plugin-wantedly の plugin 実装の v9 ランタイム対応

c98945e 〜 8b8fe7d

まずは eslint-plugin-wantedly にあるカスタムルールのアップデートを行いました。v9 になって使用できなくなった関数を使用していた箇所があっため、これを代替関数に書き換える対応、プラグインの定義を最新に合わせるという対応を行いました。各ルールごとに対応していったのでコミットが積み重なっています。
数はありましたが書き換え作業自体はシンプルだったので、そこまで時間はかからなかったように思います。

具体的にやったこととしては次のとおりです。

  1. ESLint v9 にあげてテストを実行し、テストが失敗することを確認。
  2. 各ルールについて ESLint v9 で使用できなくなった関数を代替関数に書き換え、プラグインの記述方法をv9に対応。
  3. 再度テストを実行し成功することを確認。

2についてより詳細に説明すると、defineRule という関数が実行できないという旨のエラーがテスト時に表示されていたため、これを使わないようにしつつ、eslint パッケージから export されている Rule.RuleModule という型に合うように書き換えました。さらに、ルールの option 指定方法が JSON Schema を使用するようになっていたため、これも指定方法を変更しました。

eslint-config-wantedly-* の Flat configuration へのマイグレーション

次に共通ルールセットである eslint-config-wantedly と eslint-config-wantedly-typescript での Flat config へのマイグレーションを実施しました。

aa2d32f 〜 a5b7db4

eslint-config-wantedly は主に2つのルールセットを提供しています。

  • A. React 未使用箇所向けのルールセット。(昔々からある React を使っていないコードベース向けのルールセット)
  • B. 上記をベースにReact 関連のルールを追加したルールセット(メインで使用するルールセット)

eslint-config-wantedly は JavaScript 向けのルールセットになっています。(TypeScript 使用箇所ではそのままでは利用不可) ウォンテッドリーのコードベースはほぼ TypeScript に移行しているため、この config はほとんど使用されていないものです。(使用箇所も残されているが、対象範囲はほぼ最近メンテナンスされていない昔のコードベースです。)

TypeScript 利用箇所に向けては eslint-config-wantedly-typescript を提供しています。上記と同じような構成になっており、TypeScript コードベース向けの parser の設定などが追加されています。最近はこちらをメインに使用しています。

※ Flat config になったことによって、パッケージを分ける必要はなくなったかなと考えています。いずれ config, plugin 合わせてパッケージの整理ができればいいなと考えています。

このマイグレーションは半分以上機械的に完了しました。というのも ESLint 公式がマイグレーション用のスクリプトを用意しているため、これを実行することで機械的にマイグレーションすることができました。

npx @eslint/migrate-config index.js

ある程度はこれで完了しますが、外部 config や plugin が v9 対応していない場合、下方互換性を維持するための compat と呼ばれる関数を通す必要があります。 migrate コマンド実行後にこれらを一つずつ対応していきました。

frolint スクリプトの v9 対応

このフェーズはある意味迷走期と言ってもいいかもしれません。
frolint リポジトリはそれ自体の ESLint 実行を frolint スクリプトに依存しています。frolint スクリプトを ESLint v9 対応しつつ、frolint リポジトリ全体の config を Flag config に移行するというがうまくいかず苦戦しました。タイトルを奮闘記としている所以はここです。

frolint スクリプトは内部で ESLint のインスタンスを生成するのですが、これが v9 に上がると取れるオプションが変わります。 Flat config がデフォルトになったことで、ここも変更になったのかと推測しています。
これまで frolint は eslint-config-wantedly (または eslint-config-wantedly-typescript) を自動で読み込むになっていたのですが、v9 ではそれが実現できなくなってしまいました。(少なくとも自分が試した範囲では)
いろいろ考えた結果ここに Breaking change を入れることを決めました。
もともと eslint-config-wantedly を自動で読み込んでいたところ、自分で eslint.config.js に設定を追加しないといけないようにしました。
Breaking change を起こすコミット→ fix(frolint): Fix ESLint instantiation



なかなかこういったスクリプトを書いている人は少ないかと思いますが、参考になればと思います。

ちなみに前述した通り、社内では frolint スクリプトは非推奨扱いで現在 husky, lint-staged を使った pre-commit 時の ESLint, Prettier 実行を推奨しています。

あと片付け

上記までで大まかに ESLint v9 に対応はできたのですが、ルールセットの微調整などが必要だったためアップデートを行いました。
具体的には下記の対応です。

  • eslint-plugin-es の使用を中止 eslint-plugin-es-x へ移行
  • typescript-eslint の非推奨ルールのアップデート (名称の変更など)
  • 上記 frolint の breaking change に関して README の更新
  • オプション設定が変わったルールの設定変更

あとから振り返ると、意外とあと片付けコミットが積み重なっているなという印象です。
ESLint v9 に上げるための本質的な変更というよりも、関連パッケージのアップデートや最後のリファクタリング、不要な部分をなくしたりコードをきれいにしたりというところに時間を使いました。



◇ 余談: コミットメッセージと実体の食い違い

PR のコミットログを振り返っていて気付いたのですが、 https://github.com/wantedly/frolint/pull/1137 の最初のコミットは次のようなコミットメッセージになっています。

build(deps): Bump eslint to v9

メッセージだけを読むと依存の eslint を v9 までアップデートしたように見えます。しかしながら差分を見ると、v9 までアップデートされてません。嘘でした。やらかしました。

正しくは v8.56 までアップデートが正しいです。 (英語で書くと "build(deps): Bump eslint to latest v8.56" とかでしょうか…)
このとき何を考えていたのかまったく覚えていませんが、多分いきなり v9 まで上げるとなにかが壊れたので v8.56 まで上げたのだと思います。
みなさんはコミットメッセージを残すときには diff をちゃんと確認してからにしましょう。

各リポジトリでの対応奮闘記

共通ルールセットである eslint-config-wantedly, eslint-config-wantedly-typescript の対応が終わったので、各リポジトリに反映していきました。対応しないといけないリポジトリが多岐にわたるのと、自分だけに ESLint v9 対応の知見を集中させないために、メンバーに分担して対応を行いました。以下は社内メンバーに各リポジトリで ESLint v9 対応してもらうために記述した移行指南書です。社外秘の情報は含まれていないと判断してそのまま公開します。

Flat config への移行指南書

  • package.json の devDependencies にある eslint のバージョン指定を ^9.0.0 にします。
  • eslint-config-wantedlyeslint-config-wantedly-typescript を ^4.0.0 にします。
  • npx @eslint/migrate-config .eslintrc を実行して、ベースの eslint.config.mjs を作ってもらいます。
    • これで体感 60% くらいは変換がうまくいきます。
    • 対象ファイル名の .eslintrc は適宜環境に応じて変更してください。
  • .eslint.config.mjs を変更します。
    • Flat config に関してのドキュメントはここにあります。

◇ 変更内容

  • いろいろな設定やルールは eslint-config-wantedly と eslint-config-wantedly-typescript で巻き取るようにしたので、各リポジトリの eslint.config.mjs は最小限の設定になるはずです。
  • eslint-config-wantedly と eslint-config-wantedly-typescript からそれぞれ設定を import して、展開するとほぼ設定が完了します。
  • TypeScript 用の parserOptions なんかも eslint-config-wantedly-typescript の中で完結するようになっています。
  • 全体としては次のようになるはずです。
import { base as configWantedly } from "eslint-config-wantedly";
import { react as configWantedlyTsReact } from "eslint-config-wantedly-typescript";

export default [
...configWantedly, // JS ファイル用の設定
...configWantedlyTsReact, // TS/React ファイル用の設定
{
plugins: {
// そのほかのプラグイン
},
},
{
rules: {
// 追加のルールやオーバライドするルール
},
},
];

■ 変更に関しての注意点

Flat config に対応していない config や plugin が存在します。

  • エラーが起きている場合は、ESLint 公式が出しているユーティリティ関数を通すようにしましょう。
  • Flat config に対応しているかどうかは各 config, plugin のリポジトリの README や issue を見れば分かるはずです。
  • config の場合は FlatCompat を使った compat (これはマイグレーションコマンドを実行したときに自動で作られると思います。)
  • plugin は fixupPluginRules を通しましょう。
  • eslint-plugin-es に関して
    • eslint-plugin-es はほぼメンテナンスされていない様子だったので、fork された新しいパッケージの eslint-plugin-es-x を使うように変えています。
    • es/なんとか というルールが出てきたら es-x/なんとか に書き換えてください。
    • とはいえ、 es-x のルールのいくつかを eslint-config-wantedlyeslint-config-wantedly-typescript で設定してあるので、自前でやらなくても問題ないかもしれません。
    • no-regexp-lookbehind-assertions と no-regexp-named-capture-groups は eslint-config-wantedly-typescript の方で設定済み。
    • cf. https://github.com/wantedly/frolint/blob/e5eccf63d2bfa71e809fba5cedeb3bd37fe863d7/packages/eslint-config-wantedly-typescript/base.js#L235-L236

◇ 動作確認

ESLint コマンドの直接実行と、VSCode の ESLint extension でのチェックが動くことを確認してください。

■ ESLint コマンドの直接実行

次のように直接コマンドでファイルを指定して実行し、ESLint のエラーが出ないことを確認してください。

./node_modules/.bin/eslint /path/to/適当なファイル.tsx

■ VSCode の ESLint extension の動作確認

VSCode の ESLint extension を flat config を使って動作させるためには、settings.json に次のような設定が必要です。
グローバルな設定にこれを指定してしまうと、V9 と flat config 対応していないリポジトリで正しく動かなくなってしまうので、リポジトリルートに .vscode/settings.json を設置してこれを記述することを推奨します。

※ Extension のバージョンによってはこの設定は不要です。

{
"eslint.useFlatConfig": true,
"eslint.options": {
"overrideConfigFile": "./eslint.config.mjs"
}
}

.eslint.config.mjs を書き換えてから ESLint extension がそれを読み込んで再度チェックが実行されるにはラグがあります。.eslint.config.mjs を書き換えたら extension を再起動して再度チェックを実行しましょう。
VSCode の Output で ESLint extension がエラーを吐いていないかも確認しましょう。下図は正しく使用できている場合です。

エラーが起きている場合は次のようになります。

参考資料

以下はこの対応をするために参考にしたドキュメントやPR, ブログなどです。Flat config の考え方や実際の設定ファイル記述方法など非常に参考になりました。執筆者の方々ありがとうございました!

公式ドキュメント類

有名ライブラリ・フレームワークの ESLint v9 対応 PR

関連ブログ

まとめ

ESLint v8 の EOL を受けて、社内共通ルールセット、プラグインの v9 対応をしたときの振り返りをしました。Flat config はこれまでの ESLint の独特な設定記述から開放され、分かりやすくなった部分も増えた反面、マイグレーションで詰まるところが結構ありました。同じようにアップグレードにツラミを抱えている人たちの参考になれば幸いです。


このストーリーが気になったら、遊びに来てみませんか?
今見ているWantedlyのWebフロントエンドをもっと良くしませんか?
Wantedly, Inc.では一緒に働く仲間を募集しています
13 いいね!
13 いいね!

同じタグの記事

今週のランキング

原 剛士さんにいいねを伝えよう
原 剛士さんや会社があなたに興味を持つかも