⚓️

oxlint 入門および ESLint との併用方法

に公開

これはなに

次世代の JavaScript / TypeScript 向け Linter である oxlint の基礎知識と、既に ESLint が導入されているプロジェクトへ導入し併用する方法についてまとめたものです。

oxlint 概要

https://oxc.rs/docs/guide/usage/linter.html

oxlint[1] とは、oxc プロジェクトが開発している次世代の JavaScript / TypeScript 向け Linter です。oxc は Rust 製の高速なコード解析ツールチェーンであり、その一部として提供されているのが oxlint です。ちなみに oxc には Linter の他に Prettier 互換のコードフォーマッターである oxfmt も存在します。

Rust 製の次世代ツールチェーンというと Biome が有名ですが、Linter と Formatter の両方を単一のツールで提供する点で oxc とは異なります。oxc には Linter、Formatter、Transformer など様々な機能が存在しますが、あくまでそれらは個別のツール(Node モジュール)として提供されており、ユーザーは必要なものだけを選んでインストールおよび利用する形となります。

ESLint を踏襲したルール群

2025年12月時点の最新バージョンである v1.31.0 で既に 600 以上のルールが実装されていますが、基本的に各ルールの名称は ESLint のそれをそのまま踏襲しています。また、ESLint の各ルールが提供するオプションも同様にサポートされているものが多く存在します(ただし全てではない)。このため、ESLint からの乗り換えも比較的容易と言えます。

ESLint プラグインをサポート

oxlint は Rust で実装されていながら JavaScript で実装されたプラグインもサポートしており、その API は ESLint との互換性を有しています。つまり、既存の ESLint プラグインを oxlint 上でそのまま利用可能ということです。

ESLint との併用

先述のとおり oxlint は ESLint との互換性を前面に押し出しているのと同時に、双方の併用も考慮されています。以下は公式ドキュメントからの引用です。

At this stage, Oxlint can be used to fully replace ESLint in small to medium projects.

For larger projects, our current advice is to turn off ESLint rules via eslint-plugin-oxlint, and run Oxlint before ESLint in your local or CI setup for a quicker feedback loop.

「現時点で oxlint は小規模なプロジェクトであれば ESLint を完全に置き換えることができますが、より大きなプロジェクトであれば oxlint 実行直後に eslint-plugin-oxlint を読み込んだ ESLint を実行してください」と言及しています。

eslint-plugin-oxlint は oxlint がサポート済みのルールのみを無効化する ESLint 設定プリセットです。 つまり eslint-config-prettier のようなものです。これを活用することで既存の Lint ルール設定の全体構成はそのままに、oxlint への段階的な移行が可能となります。

oxlint を使ってみる

ここでは TypeScript をベースとしたプロジェクトに oxlint を導入する手順を説明します。

インストール

npm install --save-dev oxlint oxlint-tsgolint

oxlint 本体に加えて oxlint-tsgolint をインストールします。oxlint-tsgolint は、TypeScript 向けのルールのうち特に型情報を必要とするもの、型情報を意識したもの(Type-Aware Linting)を提供するプラグインです。typescript-go を利用して型情報を取得するため、非常に高速なのが特徴です。 oxlint 本体のみでも基本的な Linting は可能ですが、使用可能なルールが限られてしまうため、TypeScript をベースとしたプロジェクトにおいては oxlint-tsgolint の併用が推奨されます。

oxlint CLI 実行時に --type-aware オプションを指定することで型情報を利用した Linting が実行されます。

実行例
npx oxlint --type-aware

設定ファイルの作成

プロジェクトルートディレクトリーに .oxlintrc.json という名前で設定ファイルを作成します。ESLint のように JavaScript 形式( .js, .cjs, .mjs)はサポートされていませんが、インラインコメントは利用可能です。

.oxlintrc.json
{
  // $schema を指定することで IDE の入力補完が効くようになる。
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "rules": {
    "no-unused-vars": "error",
    "typescript/await-thenable": "warn",
    "unicorn/no-await-in-promise-methods": "off"
  }
}

バンドルされているプラグイン

https://oxc.rs/docs/guide/usage/linter/rules.html

oxlint に実装されている各種ルールは、標準でバンドルされているプラグインというスコープ単位で提供されています。例えば no-unused-varseslint プラグインに、 typescript/await-thenabletypescript プラグインに属しています。設定したいルールに応じてそれらが属するプラグインを有効化します。

.oxlintrc.json
{
  "plugins": ["eslint", "typescript", "unicorn"],
  "rules": {
    "no-unused-vars": "error",
    "typescript/await-thenable": "warn",
    "unicorn/no-await-in-promise-methods": "off"
  }
}

なお、plugins フィールドの初期値は ["eslint", "typescript", "unicorn", "oxc"] のため、上記の例に限っては省略可能です。各ルールの初期値はすべて "warn" であり、その上で必要に応じて "error""off" で上書きします。

ルールを設定してもそれが属するプラグインが有効化されていない場合、そのルールは無視されるので注意してください。

NG 例
{
  "plugins": ["eslint"],
  "rules": {
    "node/no-new-require": "error" // node プラグインが有効化されていないため無視される
  }
}

categories フィールドによる一括有効化/無効化

https://oxc.rs/docs/guide/usage/linter/config.html#enabling-groups-of-rules-categories

標準実装されているルールは先述したプラグイン単位とは別に categories という単位でもグルーピングされており、これを利用して一括で有効化/無効化することも可能です。例えば以下のように設定すると、 correctness カテゴリーに属する全てのルールが有効化されます。

{
  "categories": {
    "correctness": "error"
  }
}
Name Summary
correctness 絶対的に間違っているか無意味なコードを検知するルール。
suspicious correctness ほどではないが誤りか無意味である可能性が高いコードを検知するルール。
pedantic 全体的に厳しめな Linter にしたい人向けのルール。その分誤検知の可能性も出てくる。
perf パフォーマンス向上を目的としたルール。
style メンテナンス性やコーディングスタイルの一貫性を検知するルール。
restriction 特定のパターン、構文、機能を検出し、禁止にするルール。
nursery Linter 側としてまだ枯れていない(将来変更される可能性がある)ルール。

なお、 categories フィールドを使用しても、そのルールが属するプラグインが有効化されていないと無視される点に注意してください。また、 categories フィールドと rules フィールドとでは rules の設定が優先されます。

no-new-require を使った例
{
  "plugins": ["node"],
  "categories": {
    // node/no-new-require を含むが、 rules フィールドでの設定で上書きされる
    "correctness": "error"
  },
  "rules": {
    "node/no-new-require": "warn"
  },
}

ESLint との併用

ここでは既に ESLint が導入されているプロジェクトへ oxlint を導入し、両者を併用する方法について説明します。

0. 前提条件

  • 多くの ESLint プラグインに依存しており、ルール設定が膨大である
  • ESLint 設定ファイルは Flat Config 形式である
  • oxlint および oxlint-tsgolint はインストール済みである

1. ESLint 設定ファイルから oxlint 設定ファイルを生成する

oxlint-migrate というツールを利用して既存の ESLint 設定ファイルから oxlint 設定ファイルを自動生成します。

# 基本的に一度しか使わないので npx 経由実行で十分
npx @oxlint/migrate eslint.config.js --type-aware --output-file oxlintrc.json

これにより、eslint.config.js に基づいた .oxlintrc.json が生成されます。 --type-aware オプションを指定することで oxlint-tsgolint のルールも有効化されます。oxlint がサポートしていないルールは生成ファイルに含まれず無視されますが、コマンド実行時にログとして出力されるため、どのルールが未対応(もしくは将来実装される)かを確認できます。

ログ出力例
$ npx @oxlint/migrate eslint.config.js --type-aware --output-file oxlintrc.json
Packages: +10
++++++++++
Progress: resolved 30, reused 9, downloaded 7, added 10, done
unsupported rule: dot-notation
unsupported rule: no-implicit-coercion
unsupported rule: no-implicit-globals
unsupported rule: no-implied-eval
unsupported rule: no-invalid-this
unsupported rule: no-loop-func
unsupported rule: no-octal
unsupported rule: no-octal-escape
unsupported rule: no-restricted-properties
unsupported rule: no-sequences
unsupported rule: no-useless-return
unsupported rule: prefer-regex-literals
unsupported rule, but in development: getter-return
unsupported rule: no-dupe-args
unsupported rule, but in development: no-misleading-character-class
unsupported rule: no-promise-executor-return
unsupported rule, but in development: no-unreachable
unsupported rule: no-unreachable-loop
unsupported rule: arrow-spacing
unsupported rule, but in development: constructor-super
unsupported rule: no-new-symbol
unsupported rule: no-restricted-exports
unsupported rule: object-shorthand
unsupported rule: prefer-arrow-callback
unsupported rule: prefer-const
unsupported rule: camelcase
unsupported rule: no-new-object
unsupported rule: no-restricted-syntax
unsupported rule: no-underscore-dangle
unsupported rule: one-var
unsupported rule, but in development: no-undef
unsupported rule: no-undef-init
unsupported rule: import/no-unresolved
unsupported rule, but in development: import/named
unsupported rule, but in development: import/export
unsupported rule: import/no-extraneous-dependencies
unsupported rule: import/order
unsupported rule: import/newline-after-import
unsupported rule: import/no-useless-path-segments
unsupported rule: import/no-import-module-exports
unsupported rule: import/no-relative-packages
unsupported rule, but in development: promise/no-return-in-finally
unsupported rule: unicorn/expiring-todo-comments
unsupported rule: unicorn/import-style
unsupported rule: unicorn/no-for-loop
unsupported rule: unicorn/no-named-default
unsupported rule: unicorn/no-unnecessary-polyfills
unsupported rule: unicorn/prefer-default-parameters
unsupported rule: unicorn/prefer-export-from
unsupported rule: unicorn/prefer-keyboard-event-key
unsupported rule: unicorn/prefer-module
unsupported rule: unicorn/prefer-single-call
unsupported rule: unicorn/prefer-switch
unsupported rule: unicorn/prefer-ternary
unsupported rule: unicorn/prevent-abbreviations
unsupported rule: unicorn/relative-url-style
unsupported rule: unicorn/template-indent
unsupported rule: @typescript-eslint/no-invalid-void-type
unsupported rule: @typescript-eslint/no-unnecessary-condition
unsupported rule: @typescript-eslint/no-unnecessary-type-parameters
unsupported rule: @typescript-eslint/unified-signatures
unsupported rule: @typescript-eslint/class-literal-property-style
unsupported rule: @typescript-eslint/consistent-type-assertions
unsupported rule: @typescript-eslint/dot-notation
unsupported rule: @typescript-eslint/prefer-find
unsupported rule: @typescript-eslint/prefer-nullish-coalescing
unsupported rule: @typescript-eslint/prefer-optional-chain
unsupported rule: @typescript-eslint/prefer-regexp-exec
unsupported rule: @typescript-eslint/prefer-string-starts-ends-with
unsupported rule: @typescript-eslint/naming-convention
unsupported rule: react/no-deprecated
unsupported rule: react/prop-types
unsupported rule, but in development: react/require-render-return
unsupported rule: react/jsx-closing-bracket-location
unsupported rule: react/jsx-closing-tag-location
unsupported rule: react/jsx-curly-spacing
unsupported rule: react/jsx-indent-props
unsupported rule: react/jsx-max-props-per-line
unsupported rule: react/jsx-no-bind
unsupported rule: react/jsx-sort-props
unsupported rule: react/no-did-update-set-state
unsupported rule: react/no-will-update-set-state
unsupported rule: react/prefer-stateless-function
unsupported rule: react/sort-comp
unsupported rule: react/jsx-wrap-multilines
unsupported rule: react/jsx-first-prop-new-line
unsupported rule: react/jsx-equals-spacing
unsupported rule: react/jsx-indent
unsupported rule: react/no-unused-prop-types
unsupported rule: react/jsx-tag-spacing
unsupported rule: react/no-unused-state
unsupported rule: react/no-typos
unsupported rule: react/jsx-one-expression-per-line
unsupported rule: react/destructuring-assignment
unsupported rule: react/no-access-state-in-setstate
unsupported rule: react/jsx-child-element-spacing
unsupported rule: react/no-this-in-sfc
unsupported rule: react/jsx-props-no-multi-spaces
unsupported rule: react/jsx-curly-newline
unsupported rule: react/static-property-placement
unsupported rule: react/jsx-props-no-spreading
unsupported rule: react/function-component-definition
unsupported rule: react/jsx-no-constructed-context-values
unsupported rule: react/no-unstable-nested-components
unsupported rule: react/no-arrow-function-lifecycle
unsupported rule: react/no-invalid-html-attribute
unsupported rule: react/hook-use-state
unsupported rule: react/jsx-no-leaked-render
unsupported rule: jsx-a11y/aria-proptypes
unsupported rule: jsx-a11y/no-interactive-element-to-noninteractive-role
unsupported rule: jsx-a11y/no-noninteractive-element-to-interactive-role
unsupported rule: jsdoc/check-alignment
unsupported rule: jsdoc/check-param-names
unsupported rule: jsdoc/check-types
unsupported rule: jsdoc/check-values
unsupported rule: jsdoc/multiline-blocks
unsupported rule: jsdoc/no-multi-asterisks
unsupported rule: jsdoc/no-types
unsupported rule: jsdoc/require-description
unsupported rule: jsdoc/require-hyphen-before-param-description
unsupported rule: jsdoc/require-jsdoc
unsupported rule: jsdoc/require-returns-check
unsupported rule: jsdoc/require-yields-check
unsupported rule: jsdoc/sort-tags
unsupported rule: jsdoc/tag-lines
unsupported rule: jsdoc/valid-types
unsupported rule: vitest/valid-title
unsupported rule: jest-dom/prefer-checked
unsupported rule: jest-dom/prefer-empty
unsupported rule: jest-dom/prefer-enabled-disabled
unsupported rule: jest-dom/prefer-focus
unsupported rule: jest-dom/prefer-in-document
unsupported rule: jest-dom/prefer-required
unsupported rule: jest-dom/prefer-to-have-attribute
unsupported rule: jest-dom/prefer-to-have-class
unsupported rule: jest-dom/prefer-to-have-style
unsupported rule: jest-dom/prefer-to-have-text-content
unsupported rule: jest-dom/prefer-to-have-value
unsupported rule: testing-library/await-async-events
unsupported rule: testing-library/await-async-queries
unsupported rule: testing-library/await-async-utils
unsupported rule: testing-library/no-await-sync-events
unsupported rule: testing-library/no-await-sync-queries
unsupported rule: testing-library/no-container
unsupported rule: testing-library/no-debugging-utils
unsupported rule: testing-library/no-dom-import
unsupported rule: testing-library/no-global-regexp-flag-in-query
unsupported rule: testing-library/no-manual-cleanup
unsupported rule: testing-library/no-node-access
unsupported rule: testing-library/no-promise-in-fire-event
unsupported rule: testing-library/no-render-in-lifecycle
unsupported rule: testing-library/no-unnecessary-act
unsupported rule: testing-library/no-wait-for-multiple-assertions
unsupported rule: testing-library/no-wait-for-side-effects
unsupported rule: testing-library/no-wait-for-snapshot
unsupported rule: testing-library/prefer-find-by
unsupported rule: testing-library/prefer-presence-queries
unsupported rule: testing-library/prefer-query-by-disappearance
unsupported rule: testing-library/prefer-screen-queries
unsupported rule: testing-library/render-result-naming-convention

2. ESLint ルールのうち oxlint でサポート済のものを無効化する

oxlint がサポートするルールを ESLint 側で再度実行しないために、 eslint-plugin-oxlint をインストールして ESLint 設定ファイルに組み込みます。

npm install --save-dev eslint-plugin-oxlint
eslint.config.js
import oxlint from 'eslint-plugin-oxlint';

export default [
  // ... 既存の設定 ...
  ...oxlint.configs['flat/all'], // oxlint プラグインを組み込む
];

上記の例では oxlint がサポートするルールを全て無効化していますが、 flat/recommendedcorrectness 相当)や oxlint プラグイン単位での無効化も可能です。

https://github.com/oxc-project/eslint-plugin-oxlint?tab=readme-ov-file#all-configs

3. oxlint と ESLint を直列実行する

あとは CI やローカル環境で oxlint と ESLint を直列実行すれば完了です。例えば以下のように lint スクリプトを定義します。

package.json
{
  "scripts": {
    "lint": "oxlint --type-aware && eslint ."
  }
}

パフォーマンス比較

oxlint と ESLint とで同一のコードベースを同一のルールセットで Linting した場合のパフォーマンス比較を行いました。比較対象は以下のとおりです。

コードベース

筆者が日頃趣味や学習目的で使用している個人開発用リポジトリーで検証します。

  • ファイル種別: JavaScript / TypeScript ( *.{js,ts,tsx} )
  • ファイル数: 479
  • 総行数: 28,178

Linter 設定ファイル

既にリポジトリー全体のコードベースを対象とした eslint.config.js が存在しているため、これを元に先述の oxlint-migrate を利用して .oxlintrc.json を生成します。その際に oxlint がサポートしていないルールは全て eslint.config.js から除外します。これで oxlint と ESLint で同一のルールセットが構成されます。

計測

シェルの組み込みコマンドである time を使って実行時間を計測します。

time npx eslint .
time npx oxlint --type-aware

結果

Linter 結果
ESLint 20.06s user 1.99s system 153% cpu **14.352 total**
oxlint 0.79s user 0.33s system 163% cpu **0.686 total**

ESLint が20秒超えであるのに対し、oxlint は1秒未満と非常に高速であることが分かります。それぞれ5回ほど実施しましたが、ほぼ同じような結果に。今回のケースでは約25倍のパフォーマンス差となりました。

速さの理由として、oxlint が Rust 製であることに加えて、oxlint-tsgolint による型情報の取得が非常に高速であることが挙げられます。ESLint は型情報を取得するために tsc を起動するため、その分のオーバーヘッドが大きな差となって表れています。

ちなみに TypeScript ESLint 開発チームは過去に tsgolint という typescript-go をベースとしたツールを実験的に開発しており、これを使用することで ESLint のパフォーマンスが20〜40倍向上したことが報告されています[3]oxlint-tsgolint はこの tsgolint を引き継いだものなので、同様の高速化が実現されているわけです。

締め

oxlint は ESLint との高い互換性を持ちながらも、Rust 製であることによる高速なパフォーマンスが魅力の次世代 Linter です。既に ESLint が導入されているプロジェクトへも段階的に導入できるよう配慮されているため、興味がある方はぜひ試してみると良いでしょう。

脚注
  1. 公式ドキュメントによると、正しい読み方は「オーエックスリント」だそうです。 ↩︎

  2. 本件について X に投稿したら、oxc の開発組織である void(0) 社の方からリプライを頂きました。具体的な言及はありませんでしたが、将来的には設定プリセットの読み込みもサポートされるかもしれません。
    https://twitter.com/TheAlexLichter/status/1995183441600446529 ↩︎

  3. https://github.com/typescript-eslint/tsgolint?tab=readme-ov-file#speedup-over-eslint ↩︎

Discussion