この記事が解決する悩み
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 |
上記の環境で実際に実行して確認した結果です。環境が異なる場合は挙動が変わることがあります。

まとめ
同じコーパスで測ると、oxlint は ESLint の 64.6倍速く、ルールを揃えれば検出結果は完全に一致しました。
| 観点 | ESLint 10 | oxlint 1.85 |
|---|---|---|
| 既定のルール | recommended 相当が有効 | correctness のみ |
| ESLint プラグイン | そのまま使える | JS プラグイン(alpha) |
| 型情報を使うルール | typescript-eslint | tsgo 経由で対応 |
| 設定の移行 | 基準になる側 | @oxlint/migrate が変換 |
| 向いている場面 | プラグイン依存が強い | 大きなリポジトリの CI |
CI の待ち時間に困っているなら、まず oxlint を入れて ESLint と並走させるのが安全ですよ。検出を落とさずに、待ち時間だけを先に削れます。
【広告】XServerドメインならドメイン取得も運用もお得。![]()
参考
本文の記述は以下の一次情報を確認して書いています。
- Oxlint の概要(Oxc 公式 / 50〜100倍というベンチマークの記載元)
- Configuration(Oxlint 公式 / .oxlintrc.json と categories の書き方)
- Migrate from ESLint(Oxlint 公式 / @oxlint/migrate と eslint-plugin-oxlint)
- Type-aware linting(Oxlint 公式 / tsgo を使う型情報ルール)
- Oxlint JS Plugins Alpha(Oxc 公式ブログ / 2026年3月11日)
- ESLint v10.0.0 released(ESLint 公式ブログ / 2026年2月6日)
- ESLint v10.11.0 released(ESLint 公式ブログ / 2026年9月)
- Configuration Files(ESLint 公式 / flat config の書き方)
- Getting Started(typescript-eslint 公式 / peer の対応範囲)
- Releases(oxc GitHub / バージョンごとの変更履歴)
あわせて読みたい
- 【Tailwind CSS v4】「Cannot apply unknown utility class」の直し方 — @apply が効かない4つの原因
- 【JavaScript】Jest から Vitest 5 へ移行した記録 — ESM で壊れた所と直し方
- 【pnpm】ERR_PNPM_IGNORED_BUILDS の直し方 — allowBuilds へ移行
- 【Node.js】ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX の直し方
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!![]()

コメント