はじめに
フロントエンド領域で開発を行っている赤星です。
弊社はcommit時にgitのpre-commitフックによりESLint+Prettier+tscを走らせ、フォーマット+コードチェックを行っていたのですが、最近これをESLint+Oxlint+Oxfmt+tsgoの構成に変更を行いました。
しかし、移行にあたり、様々な障害にぶつかって詰まったので「理想と現実」「その乗り越え方」をここで紹介します。
プロダクトのESLint+Prettierが重くOxlint+Oxfmtに移行しようと検討されている方の一助になればと思います。
目次
結論
結論から言います。大変ですが移行してよかったです。「理想と現実」というタイトルをつけている以上「やめとけ」みたいな文脈にも見えてしまうので、あらかじめ断っておきます。事実commitをする度に20秒待たされていたのが5秒で終わるようになっています。4倍速くなったということです。これは大きな開発生産性の向上であり、移行して非常によかったと感じています。
Oxlint+Oxfmtのすごいところ(理想)
圧倒的な速さ
私がOxlintというツールを知った時非常に驚いたのはその圧倒的な速さです。Oxlintと同じRust製であるBiomeのLint機能はESLintの10倍以上高速と言われています。しかしOxlintはそのBiomeより2倍速いそうです。
ESLintやESLintプラグインの高い互換性
さらにOxlintはESLintのメジャーなプラグイン(eslint-plugin-reactやeslint-plugin-jsx-a11y、typescript-eslint等)とも高い互換性があり(対応プラグイン一覧)、Oxlintの内部プラグインとしてRustで移植されています。さらにOxlintに移植されていないプラグインも外部プラグインとして実行できる仕組みが用意されています。(参考)
そしてESLintはコメントでのルール制御をする際、例えば
// eslint-disable-next-line react/no-array-index-key
と書くと思いますが、Oxlintは
// oxlint-disable-next-line react/no-array-index-key
と書きます。eslintの部分がoxlintになっただけであり、これは非常にとっつきやすいです。
移行コマンドの存在
Oxlint+Oxfmtを開発しているVoidZeroはoxlint-migrateというESLintからの移行コマンドを用意しています。flat-configで書かれたESLintのみの対応ですが、これにより、
npx @oxlint/migrate eslint.config.js --type-aware
とコマンドを叩くだけで既存のESLint設定を元に.oxlintrc.jsonを生成してくれます。
ESLint併用機能
Oxlintはまだまだ開発中です。既存のESLintルールやプラグインの中でまだ対応されていないルールもちらほらあります。(対応 or 非対応一覧はこのissueから確認可能)
そのためOxlintはESLintとの併用機能が用意されており、eslint-plugin-oxlintというESLintの拡張機能が用意されています。
これを既存のESLintに導入すると、ESLintで定義されているルールのうち、Oxlintで実装済みのルールをスキップすることができます。
移行奮闘記 ~ぶち当たった現実~
ESLintと何も考えずに併用すると思ったより速くならない
Oxlintを導入する前、ESLintの全体走査は約40秒程度かかっていました。試しに移行コマンドを行い、eslint-plugin-oxlintを導入した後、ワクワクしながらどれくらい短くなったのかとESLint+Oxlint併用での全体走査をしました。果たして何秒になったでしょうか。
なんと約30秒です🫠
速くなったと言われれば速くなりましたが、大して速くなりませんでした...
調査してみたところtypescript-eslintプラグイン(ESLint側)に大きな落とし穴がありました。
ESLintのtypescript-eslintプラグインは名前の通りTypeScript向けのESLintプラグインですが、TypeScriptにおける型情報のルール違反検知を実現するために、内部でTypeScriptのコンパイラエンジンを利用しており、ローカルのtsconfig.jsonを参考に動作します。これが非常に重く実行時間の増加の原因となっていました。Oxlintのtypescript-eslintプラグインはこれを回避するために内部にtsgoを利用したtsgolintを利用しているのですが、Oxlintはtypescript-eslintプラグインを全て移行しきれている訳ではないので、ESLint側のtypescript-eslintが動作し、結果的に実行時間を上げていました。
弊社OfferBoxの環境ではESLint側で残っていたtypescript-eslint系は
- typescript-eslint/no-throw-literal
- typescript-eslint/dot-notation
だけであり、それぞれOxlintに別名で実装済み / 近い将来正式実装される ことがわかったので、思い切って削除をすることでこの問題を解決しました。
結果的に全体走査にかかる時間を約20秒まで縮めることに成功しました。(本当はもっと速くしたいんですがね...)
Oxlintの仕様をある程度は理解しないといけない
「何も考えずにESLintからコマンド1発置き換え🎵」みたいなのは少し厳しいです。単純なESLint設定ファイルならまだしも、問題はeslint-config-airbnbのような設定パッケージを利用していたり、あるいは単純に複雑なESLint設定を書いている場合です。
分かりやすい例としてno-throw-literalというルールがあります。これは簡単に言うと「throwを書くときはErrorオブジェクトを利用してね」というルールなのですが、Oxlintでは非推奨となっています(参考)。代わりに似たルールであるtypescript/only-throw-errorを使うことが推奨されているのですが、これを利用するにはOxlintの設定に手動で書き込まなくてはいけません。
もう一点分かりやすい例としてはESLintのreact-hooksプラグインです。Oxlintはreact-hooksプラグインを完全移行済みなのですが、このプラグインはルールが2つしかないこともあり、reactプラグインに統一されています(該当issue)。なので、これを知らないと生成された.oxlintrc.jsonを見て「あれ...?react-hooksプラグインが見当たらないぞ...?」となります。(ちなみにこちらは移行コマンド実行時にreact/exhaustive-deps: "error"のようにreactプラグインに変換されているので特別な対応は必要ありません)
後は
- Oxlintにtypescript-eslintをしっかり見てもらうには
--type-awareオプションをつける必要がある (参考)
等細かい仕様の違いがあります。
Oxlintの未発見バグに3種類当たった(Oxlint最新版で修正済み)
Oxlintへの変換コマンドを実行したのち、試しに実行してみたところ、ESLintとの差分(ESLintの時は検出されなかったのがOxlintでルール違反として検出される、あるいはその反対)が発生しました。
最初は最新のESLintのバージョンを利用していなかったのが原因なのかなと考えていたのですが、Oxlintに指摘された(あるいは指摘されなかった)コードをみたところどうやらOxlintのバグであることが分かりました。
実際に当たったバグはそれぞれ以下であり、issueに報告しました。
※keyの値をテンプレートリテラル等で加工した場合、配列のindexをkeyに利用していてもreact/no-array-index-keyが引っかからない
※imgタグのaltにhelloImageのようにImageという単語が使われているが前の単語とスペースなしでくっついている場合の時、jsx-a11y/img-redundant-altの誤検知が発生する
※ asを利用して「型」としてuseEffectの外にある変数が利用されている場合、react/exhaustive-depsの誤検知が発生する
Oxlint側の対応がされるまでの一時的な対応策として、誤検知が引っかからないようなコードに書き直したり、// oxlint-disable-next-line-***を書くことで対応しました。
やはり新しいOSSということもあり、仕方ないのですが、特定条件の時バグにちらほら遭遇した印象です。
その他
上記全てを対応したところ、pre-commitは10秒程度になりました。個人的にはまだ速くしたいと思って調べてみたところ、tscもどうやら重そうということが分かり、ここの記事の本題から逸れるので詳細は書きませんが、tsgoを導入し、tsc --noEmitからtsgo --noEmitにすることでさらに5秒短縮に成功しました。
まとめ
Oxlint+Oxfmtの仕様を調べるためにGitHubのissueを探したりリファレンス漁ったり非常に大変でした...
しかし、前述したようにpre-commitの時間がかなり短縮され、開発体験を上げることに成功したので、やった甲斐はあったなと感じています。
さらに、新しいOSSということもあって積極的に開発が行われており、実際私が上記に挙げたreact/no-array-index-keyのバグはissueを出してから1時間以内に対応され、mainブランチにマージされていました(すごい)。ESLintルール移植もどんどんされており、今後が非常に楽しみなツールです。
いつかESLintを完全廃止できたらいいなぁと思いつつこの記事を締めたいなと思います。


