ESLint+PrettierからOxlint+Oxfmtへ — REALITYのモノレポでlintを9.3秒→2.95秒にした全手順

約15分で読めます by ぽんたぬき
ESLint+PrettierからOxlint+Oxfmtへ — REALITYのモノレポでlintを9.3秒→2.95秒にした全手順

ESLint+PrettierからOxlint+Oxfmtへ — REALITYのモノレポでlintを9.3秒→2.95秒にした全手順

約460ファイルのモノレポでESLint+PrettierをOxlint+Oxfmtに置き換えた結果、lint実行時間が**9.29秒→2.95秒(約68%削減)**を達成しました。依存パッケージは14種から3種へ、設定ファイルも大幅にシンプルになっています。速度ベンチマークでは最速だったBiomeをあえて採用しなかった判断軸、Tailwindカスタムユーティリティのソート解決策、そして「規約をコード化する三層運用」まで、実例をもとに解説します。

この記事でわかること

  • 移行前後のベンチマーク比較(ESLint+Prettier 9.29秒 / Biome 2.69秒 / Oxlint+Oxfmt 2.95秒)
  • Oxlint・Oxfmtの最小構成設定
  • Biomeを選ばなかった判断軸と2026年時点の再評価ライン
  • Tailwind CSSカスタムユーティリティのソート設定手順
  • コーディング規約をformatter / linter / ドキュメントに振り分ける運用パターン

1. なぜlintツールを見直したのか

1.1 モノレポの構成とスケール

REALITYのフロントエンドモノレポはTurborepoで管理されており、packages/design-systemを含む複数のワークスペースで構成されています。総ファイル数は約460ファイルと中規模ですが、ワークスペースが増えるにつれてlintの実行時間がじわじわと伸びていました。

1.2 ESLint+Prettierのボトルネック

ESLint+Prettierが遅かった主因は2点です。まずNode.jsの起動コストです。Turborepoはワークスペースごとにプロセスを起動するため、そのオーバーヘッドが積み重なります。もう1点が依存パッケージのメンテナンス負荷です。eslint-plugin-*や@typescript-eslint/*などが絡み合い、バージョンアップのたびに相互依存の解消に時間を取られていました。

1.3 AIエージェント時代の要件変化

近年、AIコーディングエージェントを開発フローに組み込むケースが増えています。エージェントは短いサイクルでコードを生成し、lintを実行してフィードバックを受けます。この反復速度にlintの実行時間が直結するため、「9秒待つ」コストが以前より大きく感じられるようになっていました。

「規約を人がレビューで指摘する」から「ツールが即座に直す・エラーで教える」へという思想転換も、今回の移行の背景にあります。


2. Oxlint・Oxfmtとは

2.1 OxlintとOxfmtの位置づけ

OxlintはRust製のJavaScript/TypeScriptリンターです。並列実行を前提とした設計で、ESLintと比較して数倍から数十倍の速度が出ることが多いです。設定ファイルは.oxlintrc.jsonで管理します。

デフォルトで有効になるのはcorrectnessカテゴリのルールのみです。TypeScript固有ルール(@typescript-eslint/*相当)を使うには"plugins": ["typescript"]を指定したうえで個別に有効化する必要があります。型情報を使った高度なチェックはoxlint-tsgolintが担います。

OxfmtはRust製のフォーマッターです。oxfmt --initで設定ファイルを生成し、oxfmt --migrate prettierを実行するとPrettierの設定を自動変換してくれます。2026年時点でPrettier v3.8との互換性100%を達成しており、フォーマット差分なしで移行が可能です。

ただし1点注意が必要です。OxfmtのデフォルトprintWidthは100ですが、Prettierのデフォルトは80です。差分を出したくない場合はoxfmt.jsonに明示的に"printWidth": 80を指定してください。

2.2 依存関係が14種→3種に

移行後の構成はシンプルです。

oxlint          # リンター本体
oxfmt           # フォーマッター本体
oxlint-tsgolint # 型情報を使うルール(任意)

ESLint時代に必要だったeslint-config-*、eslint-plugin-import、@typescript-eslint/eslint-plugin、@typescript-eslint/parser、prettier-plugin-tailwindcssなどが一掃されます。


3. ベンチマーク比較

計測は約460ファイルのモノレポで、キャッシュなし条件で実施しました。

ツール 実行時間 備考
ESLint + Prettier 9.29秒 移行前
Biome 2.69秒 最速だが不採用
Oxlint + Oxfmt 2.95秒 採用

約3倍の高速化です。この数値はREALITY固有ではなく、他社事例でも再現性があります。RevCommの事例やi-plugの事例(20秒→5秒)など、おおむね3〜4倍の短縮が報告されています。

注目すべきは、Biomeが最速でありながら不採用になった点です。次章でその理由を詳しく説明します。


4. Biomeを不採用にした理由

4.1 判断の前提:既存コードの形を変えない

移行において最優先したのは「既存コードのフォーマットを変えないこと」でした。差分が大きいほどレビューが困難になり、移行そのものが頓挫するリスクがあります。

Biomeを実際に試したところ、2つの深刻な問題が判明しました。

4.2 問題①:ワークスペースパッケージが外部パッケージと同グループになる

モノレポでは@repo/uiや@repo/configのような内部パッケージを、reactやnextなどの外部パッケージと分けてimport順序を管理したいケースが多いです。

Biomeのimportグループ設定では、@repo/*スコープを外部パッケージと分離することができませんでした。結果として85ファイルに差分が発生し、既存スタイルとの乖離が生じました。

4.3 問題②:Tailwindクラスの並び順が変わる

より深刻だったのがTailwind CSSのクラスソートです。71ファイルでクラスの並び順が変わり、cn()やcva()の呼び出しを含めると271ファイルに波及しました。

4.4 根本原因:BiomeはプロジェクトのCSSを読まない

この問題の本質は設定の不足ではなく、アーキテクチャの構造的差異にあります。

BiomeはTailwindの既定クラス順序をツール内部に保持しており、プロジェクトのスタイルシートを読み込みません。そのためtext-title-lgのようなカスタムユーティリティは「未定義クラス」として扱われ、既定クラスの末尾に追いやられてしまいます。これはどう設定しても回避できない制約です。

4.5 2026年時点のBiome再評価

Biome v2.6ではuseTailwindSortedClassesルールが改善され、CSSベースのデザインシステム設定を読み込めるようになりました。Tailwind v4への対応も進んでいます。

ただし、プラグインに由来するカスタムユーティリティへの対応は依然として制限があります。今から新規プロジェクトを始めるなら再評価する価値はありますが、既存コードの完全維持を最優先とする移行には向いていません。


5. TailwindカスタムユーティリティのソートをOxfmtで解決する

5.1 設定方法

oxfmt.jsonに以下を追加します。

{
  "sortTailwindcss": {
    "stylesheet": "packages/design-system/src/styles.css",
    "functions": ["cn", "cva"]
  }
}

stylesheetにプロジェクトのCSSファイルパスを指定することで、Oxfmtはそのファイルを読み込み、@themeで定義されたカスタムユーティリティやvariantを含む正確なクラス順序を再現します。これはprettier-plugin-tailwindcssと同一の並び順になります。

5.2 functions指定を忘れると起きること

functionsを指定しないと、cn()やcva()の引数内文字列がソート対象外になります。クラス名が直接JSXのclassNameに書かれているケースはソートされますが、ユーティリティ関数を介した箇所にソート漏れが混在することになります。cnとcvaは必ず明示してください。


6. ESLint→Oxlint移行の4段階手順

コミットを4段階に分割することで、差分を追いやすくなります。

ステップ1:ツール追加と設定ファイル作成

既存のESLint+Prettierはまだ動かしたまま、Oxlint・Oxfmtをdevdependenciesに追加し、設定ファイルを作成します。この段階では何も変わりません。

ステップ2:Oxfmtで全体整形(整形差分専用コミット)

oxfmt --write .を実行し、フォーマット差分だけのコミットを作ります。このコミットにはロジック変更を一切混ぜません。コードレビュアーが「フォーマット差分コミット」と認識できるタイトルにしておくと、後から追跡しやすくなります。

ステップ3:Oxlintの自動修正

oxlint --fixを実行します。import typeへの書き換えなど、自動修正できる違反が修正されます。このあとは必ずテストを実行してください。 型チェックだけでは自動修正による意味変更を検出できないケースがあります。たとえばvitest/prefer-called-withのような自動修正は、テストのアサーション意図を変えてしまうことがあります。

ステップ4:スクリプト・CI設定の置き換え

package.jsonのlintスクリプトとCI設定を更新します。TurborepoのlintタスクはNode.js起動コストが不要になるため、設定を見直す機会でもあります。


7. 移行でハマったポイントと回避策

副作用importの直後で空行が消える

import './styles.css'のような副作用importの直後に空行が入るケースで、Oxfmtが空行を削除することがあります。side_effectグループをimportグループ設定に追加して制御してください。

importグループのカスタム設定

@repo/*を外部パッケージと分離するには以下のように設定します。

{
  "sortImports": {
    "customGroups": [
      { "groupName": "workspace", "elementNamePattern": ["@repo/**"] }
    ],
    "groups": ["builtin", "react", "next", "external", "workspace", "internal"]
  }
}

ルールは1つずつ有効化する

Oxlintのルールを一括で"all": trueにすると差分が大量発生し、何が起きているか追えなくなります。1ルールずつ有効化し、offにしたルールには理由をコメントで残す習慣をつけましょう。

ESLintに未実装ルールが残る場合

OxlintはESLintの全ルールを実装しているわけではありません。複雑なカスタムルールや高頻度に使う特定のプラグインルールが未実装の場合は、Oxlintをメインにしつつ該当ルールだけESLintと併用する段階的構成が現実的です。


8. コーディング規約を「三層」に振り分ける運用

今回の移行を通じて確立したのが、規約を三層に分類する運用です。これが「速度改善」で終わらない、本質的な価値です。

Formatter層(Oxfmt):議論の余地がないもの

import順序、Tailwind CSSクラス順序など、正解が一意に決まるものはOxfmtに任せます。自動修正されるため、コードレビューで「ここのimport順が」と指摘する必要がなくなります。

Linter層(Oxlint):違反は検出できるが文脈判断が必要なもの

禁止import(特定パッケージを使わないルール)、ファイル名の命名規則など、自動修正が難しいものをOxlintのカスタムルールで検出します。エラーメッセージに「なぜ禁止か」と「どう直すか」を書くのがポイントです。開発者はエラーを読めば自己解決できます。

ドキュメント層:判断理由と背景

「なぜそのルールを採用したのか」「どんなトレードオフがあるか」「例外はどんな場合か」はドキュメントに書きます。レビューで議論するのはこの層だけに絞ることで、レビューの質が上がります。

モノレポとAIエージェントへの効果

この三層分けはモノレポと特に相性が良いです。新規パッケージを追加した瞬間からルールが自動で適用されます。AIエージェントにとっても重要です。「規約ドキュメントを読んで守る」ではなく「ツールに矯正される」構造になるため、エージェントが生成するコードのスタイルが自然に揃います。


9. よくある疑問(FAQ)

Q. BiomeではなくOxlintを選ぶ理由は? 最大の理由はTailwindカスタムユーティリティのソートと、既存コードフォーマットの完全維持です。新規プロジェクトかつカスタムユーティリティが少なければBiomeも有力候補になります。

Q. 速度はどれくらい変わる? 約3倍が目安です。REALITYは9.3秒→2.95秒、i-plugは20秒→5秒と、規模に関わらず3〜4倍の短縮が多く報告されています。

Q. ESLintの全ルールをOxlintで再現できる? 未実装ルールが存在します。高頻度のルールはOxlintで代替でき、複雑なものはESLintとの併用が現実的です。

Q. Oxfmtは本当にPrettierと同じ結果になる? Prettier v3.8互換100%を達成しています。ただしPrettierプラグインは非対応です。またprintWidthのデフォルト値がOxfmt(100)とPrettier(80)で異なるため、差分を出さないなら明示指定が必要です。

Q. 小規模プロジェクトでも移行する価値はある? 速度差より「依存3パッケージ」「設定1ファイル」の保守性メリットで判断することをお勧めします。依存関係の更新コストと設定の複雑さに疲れているなら、ファイル数が少なくても移行は有効です。


10. まとめ

REALITYのモノレポでの移行結果を改めて整理します。

  • lint実行時間:9.29秒 → 2.95秒(約68%削減)
  • 依存パッケージ:14種 → 3種(oxlint / oxfmt / oxlint-tsgolint)
  • Biomeは最速(2.69秒)だったが、Tailwindカスタムユーティリティの構造的制約で不採用

今日から試せる最小手順は次の3ステップです。

  1. oxfmt --migrate prettierで設定を変換し、フォーマット差分を1コミットにまとめる
  2. Oxlintのルールを1つずつ有効化する
  3. 三層分け(Formatter / Linter / ドキュメント)の設計を決めてから設定を書く

速度改善は入口に過ぎません。「規約をツールに組み込み、レビューを判断の議論だけに絞る」ことが、今回の移行の本当のゴールです。


参考リンク

関連記事

Aurora/RDS オンデマンドキャパシティ予約でDR・マイグレーションの「キャパシティ不足」を防ぐ

Aurora/RDS オンデマンドキャパシティ予約でDR・マイグレーションの「キャパシティ不足」を防ぐ

Aurora/RDS オンデマンドキャパシティ予約でDR・マイグレーションの「キャパシティ不足」を防ぐ Amazon Aurora・RDSを本番運用しているエンジニアであれば、一度は耳にしたことがあるのではないでしょうか。フェイルオーバーや復旧手順を実行した直後に表示される エラーの恐ろしさを。2026年10月、そのリスクを根本から断ち切る新機能「オンデマンドキャパシティ予約(On-Demand...

GitLab に CVSS 10.0 の緊急パッチ ― 正規表現コンパイラの整数オーバーフローとは何か、今すぐやるべきこと

GitLab に CVSS 10.0 の緊急パッチ ― 正規表現コンパイラの整数オーバーフローとは何か、今すぐやるべきこと

GitLab に CVSS 10.0 の緊急パッチ ― 正規表現コンパイラの整数オーバーフローとは何か、今すぐやるべきこと はじめに:2026年10月21日、GitLab が7バージョン同時リリース 2026年10月21日、GitLab は 19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13 の7バージョンを同時リリースし...

Mistral Large 4とは?1兆パラメータMoE × 1Mコンテキストを徹底解説【価格・性能比較つき】

Mistral Large 4とは?1兆パラメータMoE × 1Mコンテキストを徹底解説【価格・性能比較つき】

Mistral Large 4とは?1兆パラメータMoE × 1Mコンテキストを徹底解説【価格・性能比較つき】 はじめに:1兆パラメータなのに「安い」モデルが出た 2026年10月、Mistralが投下した「Le Chonk」 2026年10月6日、フランスのAIスタートアップMistral AIは「Mistral Large 4」をパブリックプレビューとして公開しました。社内ニックネームは 「L...

【php2shell】PHP 8.5.12で修正された2件のRCE脆弱性と、PHP財団のセキュリティ体制強化を徹底解説

【php2shell】PHP 8.5.12で修正された2件のRCE脆弱性と、PHP財団のセキュリティ体制強化を徹底解説

【php2shell】PHP 8.5.12で修正された2件のRCE脆弱性と、PHP財団のセキュリティ体制強化を徹底解説 2026年10月21日、PHP財団は極めて深刻度の高いセキュリティリリースを公表しました。通称「php2shell」と呼ばれる2件のCritical脆弱性(CVE-2026-109618 / CVE-2026-109619)は、認証不要でリモートコード実行(RCE)が可能という最...

コメント

0/2000