【検証】oxlint vs ESLint 10 — 3,000ファイルの lint 速度を実測

速度の違う2つのリンターを、横並びのバーの長さで表した抽象的なアイキャッチ画像
目次

この記事が解決する悩み

CI の lint が遅くて、プッシュするたびに待たされる。そんな悩みを抱えている人は多いですね。

  • ESLint の実行に毎回数秒かかり、CI の待ち時間とクラウドの課金が気になっている
  • oxlint に乗り換えたいけれど、ESLint と比べて何が検出できて何が抜けるのか分からない
  • 「ESLint より速い」という話は見かけるけれど、自分のリポジトリの規模で何倍になるのか分からない

対象読者: ESLint を CI かエディタで使っている JavaScript / TypeScript の開発者。

前提知識: flat config(eslint.config.js)の書き方と、npm スクリプトから lint を回した経験。

結論

3,000ファイル(216,000行)を同じ7ルールで lint したところ、oxlint 1.85.0 が 113.2ms、ESLint 10.11.0 が 7,315.0ms でした。64.6倍の差です。

試すのは2行で済みます。

npm install -D oxlint
npx oxlint .
# 実行結果(同じコーパスを1回ずつ実行したときの実測時間)
# oxlint 1.85.0   113.2ms
# ESLint 10.11.0  7315.0ms

ルールを合わせれば検出結果は ESLint と完全に一致したので、乗り換えで見落としは出ませんでした。

何をどう測ったか

比較の条件を揃えるために、コーパス(lint させるソース)と設定を自作しました。

  • コーパス: テンプレートから生成した 3,000ファイル / 216,000行。1ファイルあたり約72行
  • 混ぜた問題: no-unused-vars / eqeqeq / prefer-const / no-console / no-explicit-any / no-debugger / no-empty / no-var
  • 測り方: ウォームアップ1回のあと5回実行して中央値を採用。どちらも node 経由で起動

ESLint は flat config に recommended の2つを読み込ませ、oxlint にも同じ3ルール(eqeqeq / prefer-const / no-console)を足して、条件を揃えています。

コーパスはテンプレートから生成した

手書きのサンプルを1つ lint しても、規模の話にはなりません。通し番号を差し込んで同じ形のファイルを量産しました。

for i in range(FILES):
    body = (TEMPLATE
            .replace("@IDX@", str(i))
            .replace("@MUL@", str(i % 7 + 1))
            .replace("@N@", str(4 + i % 5)))

    with open(os.path.join(OUT, f"module{i:04d}.ts"), "w", encoding="utf-8") as f:
        f.write(body)

# 実行結果
# 生成: 3000ファイル / 216000行(src/gen/)

1ファイルの中身は、意図的に8種類の問題を混ぜた約72行です。中身の抜粋がこちら。

export function buildItems0(seed: number): Item[] {
    const items: Item[] = [];
    const unused0 = seed * 1;
    let total = 0;

    for (let i = 0; i < 4; i++) {
        total += i * 1;

        if (seed == 0) {
            items.push({ id: i, name: `item-${i}-${total}` });
        }
    }

    console.log("built", items.length);

    return items;
}

ルール設定も揃えた

ESLint 側は recommended に加えて3ルールを有効にしています。

import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default [
    { ignores: ["node_modules/**"] },
    js.configs.recommended,
    ...tseslint.configs.recommended,
    {
        rules: {
            eqeqeq: "error",
            "prefer-const": "error",
            "no-console": "warn",
        },
    },
];

oxlint 側の設定ファイルはこれだけです。

{
  "rules": {
    "eqeqeq": "error",
    "prefer-const": "error",
    "no-console": "warn"
  }
}

速度の実測結果

ファイル数を変えながら測ると、差の出方がはっきり見えます。

対象 ESLint 10.11.0 oxlint 1.85.0 倍率
1ファイル 515.9ms 51.9ms 9.9倍
50ファイル 743.4ms 47.6ms 15.6倍
300ファイル 1,348.9ms 48.1ms 28.0倍
3,000ファイル 7,315.0ms 113.2ms 64.6倍

注目したいのは1ファイルの行です。ESLint はほぼ何も lint していない状態で約0.5秒かかります。

内訳は node の起動、設定の読み込み、typescript-eslint のパーサ初期化です。oxlint の固定費は約50msで、こちらはほぼ node の起動時間ですね。

ファイルを増やすと ESLint だけが比例して伸びます。1ファイルあたり約2.3ms(6,799ms ÷ 2,999ファイル)です。oxlint は 3,000ファイルでも 113.2ms で、1ファイルのときから 61.3ms しか増えていません。

oxc のドキュメントは「ESLint より50〜100倍速い」と書いています。手元の 3,000ファイルでは 64.6倍で、この範囲に入りました。

ESLint 側の対策と、oxlint の並列度

ESLint にもキャッシュがあります。2回目以降の実行は 1,451.7ms まで下がりました。

それでも oxlint の 113.2ms とは 12.8倍の差が残ります。CI のように毎回ファイルが変わる場面では、キャッシュはあまり効きません。

oxlint は既定でマルチスレッドです。1スレッドに絞ると 330.0ms で、既定の 2.9倍かかりました。

node node_modules/eslint/bin/eslint.js src/gen --cache
node node_modules/oxlint/bin/oxlint src/gen --threads=1

# 実行結果(3,000ファイル・5回の中央値)
# ESLint --cache       1451.7ms
# oxlint --threads=1    330.0ms

検出差分

既定の設定で同じコーパスを lint した結果です。

ルール ESLint 10.11.0 oxlint 1.85.0(既定)
no-unused-vars 3,000件 3,000件
eqeqeq 6,000件 6,000件
no-console 3,000件 3,000件
no-debugger 3,000件 3,000件
no-empty 3,000件 0件
no-explicit-any 3,000件 0件
no-var 3,000件 0件
合計 24,000件 / 7ルール 15,000件 / 4ルール

oxlint の既定はcorrectness カテゴリだけです。抜けた3ルールは restriction カテゴリにあるので、明示しない限り動きません。

設定ファイルにその3ルールを足すと、24,000件 / 7ルールで ESLint と完全に一致しました。

{
  "rules": {
    "eqeqeq": "error",
    "prefer-const": "error",
    "no-console": "warn",
    "no-var": "error",
    "no-empty": "error",
    "typescript/no-explicit-any": "error"
  }
}

# 実行結果
# oxlint(ルールを合わせた設定)  24000件 / 7ルール
# ESLint だけが検出: なし
# oxlint だけが検出: なし

ネイティブ実装が無い ESLint プラグインは、JS プラグイン(2026年3月から alpha)で読み込ませられます。プラグイン依存が強いプロジェクトは、ここが移行の判断点ですね。

ESLint から移行する

設定の書き換えは公式の移行ツール(@oxlint/migrate)がやってくれます。

npx @oxlint/migrate eslint.config.mjs

# 実行結果
# .oxlintrc.json created with 85 rules.
#
#    Skipped 4 rules:
#      -   2 Nursery         (Experimental: no-undef, no-useless-assignment)
#      -   2 Unsupported     (Won't be implemented: no-dupe-args, no-octal)

既存の flat config を読んで、85ルールの設定を生成しました。未対応のルールも理由つきで報告されます。

生成された設定には categories の correctness を off にしたうえで、ESLint 側にあったルールが個別に並びます。TypeScript ファイル向けの overrides には no-var の error も入っていました。

この設定で同じコーパスを lint したところ、24,000件 / 7ルールで ESLint と完全に一致しました。手で書いた設定と同じ結果です。

いきなり全部置き換えたくない場合は、重複するルールだけ ESLint 側で切って両方を走らせる段階移行もできます。oxc のドキュメントでは eslint-plugin-oxlint が案内されています。

つまずきポイント

実際に踏んだものを3つ挙げます。

1. TypeScript 7 と typescript-eslint 8.70 はまだ噛み合わない

typescript-eslint 8.70.1 の peer は TypeScript 6.1.0 未満です。最新の 7.0.2 を入れると npm が警告を出します。

npm install --dry-run typescript@7.0.2

# 実行結果
# npm warn ERESOLVE overriding peer dependency
# npm warn Found: typescript@6.0.2
# npm warn   peer typescript@">=4.8.4 <6.1.0" from @typescript-eslint/eslint-plugin@8.70.1
# npm warn Could not resolve dependency:
# npm warn peer typescript@">=4.8.4 <6.1.0" from typescript-eslint@8.70.1

今回は TypeScript 6.0.2 に固定して検証しました。

2. 何も指定しないと correctness の重大度は warning で、CI が通る

ここが一番はまりました。oxlint は correctness カテゴリのルールを既定で warning として報告します。warning の exit code は 0 なので、CI は落ちません。

error として扱わせるには、categories に correctness: error を書くか、-D correctness を付けて実行します。

# 同じファイル(no-unused-vars と no-console だけを含む)を4通りで lint した結果
# 設定ファイルなし(親ディレクトリにも無し)  no-unused-vars=warning  exit=0
# -D correctness を付ける                    no-unused-vars=error    exit=1
# --deny-warnings を付ける                   no-unused-vars=warning  exit=1
# categories に correctness: error を明示    no-unused-vars=error    exit=1

oxlint は親ディレクトリの .oxlintrc.json も読むので、モノレポでは上の階層の設定が下の階層の実行にも効きます。最初にはまったのはここでした。

3. eslint と @eslint/js のバージョン番号は揃わない

eslint が 10.11.0 でも @eslint/js は 10.0.1 です。同じ番号を指定すると ETARGET で落ちます。

npm install --dry-run @eslint/js@10.11.0

# 実行結果
# npm error code ETARGET
# npm error notarget No matching version found for @eslint/js@10.11.0.

@eslint/js は本体とは別にバージョンが振られるので、番号が揃わないのが正常です。

検証環境

OS Windows 11 (build 10.0.26200)
CPU AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics
メモリ 28GB
言語/ツール Node.js 26.8.2(公式のポータブル配布 node-v26.8.2-win-x64.zip を一時ディレクトリに展開) / ESLint 10.11.0 と @eslint/js 10.0.1 / typescript-eslint 8.70.1 / TypeScript 6.0.2 / oxlint 1.85.0(npm で一時ディレクトリ内に install)
使用コマンド python bench.py(3,000ファイル / 216,000行を ESLint と oxlint で各5回・中央値) / node_modules/oxlint/bin/oxlint(–threads=1・–format=json) / npx @oxlint/migrate eslint.config.mjs
測定日 2026-09-24

上記の環境で実際に実行して確認した結果です。環境が異なる場合は挙動が変わることがあります。

同じ3,000ファイル(216,000行)を ESLint 10.11.0 と oxlint 1.85.0 に lint させた実測ログ。ファイル数ごとの実行時間(1・50・300・3,000ファイル、各5回の中央値)、ESLint の --cache と oxlint の --threads=1、検出件数とルールの突き合わせ、@oxlint/migrate が生成した設定での再実行、設定ファイルの有無で重大度と exit code が変わること、TypeScript 7 の peer 衝突までを1枚にまとめたもの
同じ3,000ファイル(216,000行)を ESLint 10.11.0 と oxlint 1.85.0 に lint させた実測ログ。ファイル数ごとの実行時間(1・50・300・3,000ファイル、各5回の中央値)、ESLint の –cache と oxlint の –threads=1、検出件数とルールの突き合わせ、@oxlint/migrate が生成した設定での再実行、設定ファイルの有無で重大度と exit code が変わること、TypeScript 7 の peer 衝突までを1枚にまとめたもの

まとめ

同じコーパスで測ると、oxlint は ESLint の 64.6倍速く、ルールを揃えれば検出結果は完全に一致しました。

観点 ESLint 10 oxlint 1.85
既定のルール recommended 相当が有効 correctness のみ
ESLint プラグイン そのまま使える JS プラグイン(alpha)
型情報を使うルール typescript-eslint tsgo 経由で対応
設定の移行 基準になる側 @oxlint/migrate が変換
向いている場面 プラグイン依存が強い 大きなリポジトリの CI

CI の待ち時間に困っているなら、まず oxlint を入れて ESLint と並走させるのが安全ですよ。検出を落とさずに、待ち時間だけを先に削れます。

【広告】XServerドメインならドメイン取得も運用もお得。

参考

本文の記述は以下の一次情報を確認して書いています。

あわせて読みたい

【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次