Try! oxlint

に公開

OxCというRust製のJavaScriptコンパイラを使っているLinter, Formatterがそれぞれoxlint, oxfmtです. 今回この記事ではESLintからoxlintへの移行を試してみます.

Rust製のコンパイラということで他のJavaScriptプラグインに比べてコードのastの変換やastの解析が早く, 処理がすぐ終わるのが売りっぽいです. ということで, 既存のESLint, Prettierを置き換えていきます.

https://oxc.rs/docs/guide/introduction.html

Linterの章を見ると, 大規模プロジェクトでは置き換えできないものの, 小規模〜中規模のプロジェクトであれば置き換えできるとのことでしたのでそれを信じてTryします.

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 now supports JS plugins with an ESLint-compatible API. JS plugins support is currently experimental but, once stabilized, users will be able to migrate fully to Oxlint, running any ESLint rules/plugins that Oxlint doesn't support natively as JS plugins.

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

前提環境

  • 利用しているESLintプラグインです.
import eslint from "@eslint/js";
import eslintPluginNext from "@next/eslint-plugin-next";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintConfigTurbo from "eslint-config-turbo/flat";
import * as eslintPluginImport from "eslint-plugin-import";
import eslintPluginReact from "eslint-plugin-react";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss";
import eslintPluginUnusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
import tseslint from "typescript-eslint";
  • packageManagerpnpm@10.xです
  • node24です
  • turborepo+pnpm workspaceでmonorepo環境を構築しています
  • プロジェクトのLintingにかかった時間は以下の通りです.(turborepoを使っているのでその分のオーバーヘッドはあると思います.)
 Tasks:    8 successful, 8 total
Cached:    0 cached, 8 total
  Time:    7.192s 

oxlintを導入する

oxlintの導入にあたって大事なのが, ESLintプラグインへの対応もしくはoxlint版のプラグインを探すことです. ですがなんとoxlintはESLint向けにJavaScriptで記述されたプラグインを実行することができます.

https://oxc.rs/docs/guide/usage/linter/js-plugins.html

対応しているESLintのAPIも以下の通りで大体のESLintプラグインが動きそうですね.
https://oxc.rs/docs/guide/usage/linter/js-plugins.html#api-support

以下のDiscussionsでESLintのプラグインごとの対応状況が見れます(上がっているESLintプラグインだけ)
https://github.com/oxc-project/oxc/discussions/14862

全部見てやるのが正しいんですがそれだと答えがわかってつまらないので(?)1つずつ対応していきます.

TypeAware Linting

その前に, oxlintであってもTypeScriptのLintingは一筋縄ではいきません. ESLintの場合はtypescript-eslintを利用しますが, oxlintは現時点でalpha版であるもののTypeScriptに対応してLintingすることが可能です.

詳しいことは公式ドキュメントTypeAwareの章で書かれています.
https://oxc.rs/docs/guide/usage/linter/type-aware.html

oxlintからtsgolintを呼び出す形でLintingしているとのことです

oxlintをインストール

まずはoxlintをインストールします. と言いたいんですが、npx oxlint@latestの実行可能形式でコマンドを呼ぶ前提でドキュメントが書かれているので一旦それに従います. が、TypeAware Lintingを行う場合はローカルにoxlint-tsgolintがインストールされている必要があるのでまずはそれを入れていきます.

pnpm add -D oxlint-tsgolint@latest

一旦実行してみます. 実行時に--type-awareオプションをつけることで有効化されるそうです.
実行してみたところ, 思ったよりも時間がかかってそうだったので動いてないのか?と思いOXC_LOG=debugを有効化したところ大量にログが出ていたのでしっかりと動いていそうでした.

適切にファイルをignoreしたい

プロジェクト直下で実行するとdistファイルなどLintingの対象でないものまで含まれるため, configの設定をしていきます. oxlint自体, cliでもfile,path単位のignoreができるそうですが, 列挙すると面倒なのでファイルで記述していきます.

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

oxlintではconfigファイルをJSONまたはJSONCで記述できます. .oxlintrc.jsonのファイル名だと自動でoxlintが認識してくれるそうなのでそれに従います.

まず解析対象のファイルを設定していくのですが, oxlintはデフォルトで以下のファイル形式のものを解析してくれるとのこと.

.js
.jsx
.mjs
.cjs
.ts
.tsx
.mts
.cts
.astro *
.svelte *
.vue *

*がついているものは<script>タグの中身だけ

https://oxc.rs/docs/guide/usage/linter/config.html#specifying-files-to-process

とはいえ, であれば一旦はignoreを書くだけで良さそうですね. JSON設定ファイルにignorePatternsという形でignoreしたいfileやpathをglobパターンで書けます.

.oxlintrc.json
{
  "ignorePatterns": ["vendor", "test/snapshots/**", "test.js"],
}

monorepo設定

今回turborepoとpnpm-workspaceを利用したmonorepo構成になっています. monorepo向けの対応はESLintの場合flat configのconfig単位でfilesを指定して行うことができました.

oxlintの場合はlegacy configに近い形で, 各packagesでconfigを変更したい範囲で.oxlintrc.jsonを作成することになりそうです.

my-project/
├── .oxlintrc.json
├── src/
│   ├── index.js
├── package1/
│   ├── .oxlintrc.json
│   └── index.js
└── package2/
    ├── .oxlintrc.json
    └── index.js

個人的にはESLintのflat configが好きな反面, oxlint自体がTypeAwareな機能を持っていたり, JSX形式に対応していることからプロジェクトごとにそこまで細かい設定をしなくていい以上, 各packagesの単位で.oxlintrc.jsonを作成する方式なのかもなと

個別のプラグインを精査する

改めて現在使っているpluginを並べます.

import eslint from "@eslint/js";
import eslintPluginNext from "@next/eslint-plugin-next";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintConfigTurbo from "eslint-config-turbo/flat";
import * as eslintPluginImport from "eslint-plugin-import";
import eslintPluginReact from "eslint-plugin-react";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss";
import eslintPluginUnusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
import tseslint from "typescript-eslint";

ここからoxlintの標準機能でサポートされていたり, oxfmtで代替出来るものを外していきます.

import eslintPluginNext from "@next/eslint-plugin-next";
import eslintConfigTurbo from "eslint-config-turbo/flat";
import * as eslintPluginImport from "eslint-plugin-import";
import eslintPluginReact from "eslint-plugin-react";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss";
import eslintPluginUnusedImports from "eslint-plugin-unused-imports";

ちょっと減りましたね. globalsに関しては本当か?と思いつつ手元で動かしていてなくても動いている様子から, 問題ないと判断しています.

ESLintのpluginを使いたい場合, .oxlintrc.jsonjsPluginsを書いていくと良さそうです.

{
  "jsPlugins": ["./path/to/my-plugin.js", "eslint-plugin-whatever"],
}

一旦全て移して実行すると以下のエラーが発生しました.

  × Plugin name 'react' is reserved, and cannot be used for JS plugins.

  │ The 'react' plugin is already implemented natively in Rust within oxlint.
  │ Using both the native and JS versions would create ambiguity about which rules to use.

  │ To use an external 'react' plugin instead, provide a custom alias:

  │ "jsPlugins": [{ "name": "react-js", "specifier": "eslint-plugin-react" }]

  │ Then reference rules using your alias:

  │ "rules": {
  │   "react-js/rule-name": "error"
  │ }

  │ See: https://oxc.rs/docs/guide/usage/linter/js-plugins.html

そりゃそうですよね.と思ったのも束の間公式ドキュメントのSupported Pluginsに以下の通り書いてありoxlintプラグインで移植されているため, これらを使うとさらにESLintプラグインを減らせそうです.


https://oxc.rs/docs/guide/usage/linter/plugins.html#supported-plugins

最終的に以下の状態までpluginを整理できました.
これである程度は正常に動きエラーも出ていました.

{
  "jsPlugins": [
    "eslint-plugin-turbo",
    "eslint-plugin-tailwindcss",
    "eslint-plugin-unused-imports"
  ],
  "plugins": [
    "react",
    "import",
    "nextjs",
    "typescript",
    "eslint",
    "node",
    "oxc"
  ],
}

VSCodeのOxC拡張機能を利用する

ESLintのもう1つ良い点が, ESLintのVSCode拡張機能です.source.fixAll.eslintで全てが解決される世界線, いいですよね...

https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode

なんとVSCode Extensionがあります. 一緒に入れてしまいましょう.
ドキュメントにはかいてませんが, TypeAwareの機能を使う場合は, oxc.typeAwareの有効化が必須です.

.vscode/settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.oxc": "explicit"
  },
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "oxc.typeAware": true,
  "eslint.enable": false, // ESLintとエラーがかぶるため
  "oxc.fixKind": "all"
}

jsPluginsのエラーが表示されない問題

jsPluginsのうちunused-imports/no-unused-vars等のエラーが正しくVSCode上で表示されませんでした.

VSCodeではこう表示されますが...

CLI経由では正しくエラーが出ます

ということで...

細かいところでは動かなかったりしますが, 一旦欲しいLintingはできていそうです.

実行結果は以下の通りです.
若干速くなりましたね...(というかほとんどturboのmonorepo実行時間な気がする...)

 Tasks:    8 successful, 8 total
Cached:    0 cached, 8 total
  Time:    6.098s 

今回は速度よりも比較的ライトなケースで, oxlintが動くことを確かめられたので良かったです.

最後に.oxlintrc.jsonを貼って終わりです.

.oxlintrc.json
{
  "jsPlugins": [
    "eslint-plugin-turbo",
    "eslint-plugin-tailwindcss",
    "eslint-plugin-unused-imports"
  ],
  "plugins": [
    "react",
    "import",
    "nextjs",
    "typescript",
    "eslint",
    "node",
    "oxc"
  ],
  "ignorePatterns": [
    "**node_modules/**",
    "**/*.config.{js,mjs,ts}",
    "**/dist/**",
    "packages/**"
  ],
  "rules": {
    "typescript/no-floating-promises": "error",
    "import/order": "error",
    "eslint/no-unused-vars": "off",
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": "error",
    "@next/next/no-img-element": "off"
  }
}
chot Inc. tech blog

Discussion