はじめに
JavaScriptのパーサーって、普段あまり意識しないツールですよね。
でも、リンター、バンドラー、トランスパイラー...これら全部の基盤になっているのがパーサーなんですよ。そしてここがボトルネックになると、開発体験全体が重くなる。
Oxc-Parserは、Rustで書かれた超高速なJavaScript/TypeScriptパーサーです。「SWCより3倍速い」という触れ込みで、実際にRolldownやNuxtといった主要プロジェクトが採用を始めています。
個人的には、JavaScriptツールチェーンが「Rust時代」に突入したな、という感覚ですね。
Oxc-Parserとは
Oxc(Oxidation Compiler)は、VoidZero Inc.が開発するJavaScriptツール群です。その中核を担うのがOxc-Parser。
特徴をまとめると:
- Rustで実装: C/C++並みのパフォーマンス
- ESTree準拠: 既存ツールとの互換性あり
- TypeScript対応:
.ts/.tsxもネイティブサポート - Test262完全対応: ECMAScriptの公式テストスイートをパス
ライセンスはMITで、安心して商用利用できます。
特徴・メリット
1. 圧倒的な速度
これが最大のメリットですね。
公式ベンチマークによると:
- SWCパーサーより 3倍高速
- Babelより 20〜50倍高速
VSCodeリポジトリ(4,800ファイル以上)を0.7秒でパースしたという実績もあります。大規模コードベースでの開発体験が劇的に変わる数字です。
2. メモリ効率の良さ
速度だけじゃなく、メモリ使用量も優秀です。
- SWCと比べて 20%少ないメモリ使用
- Babelと比べて 70%少ないメモリ使用
CIサーバーのコスト削減にも効いてきます。
3. パッケージサイズが小さい
node_modulesの肥大化、30代エンジニアなら誰もが経験してきた悩みだと思います。
Oxc-Parserのパッケージサイズは約2MB。SWCの37MBと比べると、かなりコンパクト。インストール時間も短縮できます。
4. ESM情報を直接返却
これ、意外と便利なポイントです。
普通のパーサーだと、import/export情報を取得するにはASTを再度ウォークする必要がある。でもOxc-Parserは、パース結果と一緒にESM情報を返してくれます。
staticImports: import文の情報staticExports: export文の情報dynamicImports: 動的importhasModuleSyntax: ESMかどうか
バンドラー実装とかで重宝する機能ですね。
5. ESTree/TS-ESTree互換
出力されるASTは業界標準に準拠しています。
- JSファイル: ESTree形式(Acornと同等)
- TSファイル: TS-ESTree形式(typescript-eslintと同等)
既存のAST操作ツールやESLintプラグインとの連携がしやすい。
インストール方法
Node.js環境
# npm
npm install oxc-parser
# pnpm
pnpm add oxc-parser
# yarn
yarn add oxc-parser
Rust環境
Cargo.tomlに追加:
[dependencies]
oxc_parser = "0.x"
oxc_ast = "0.x"
統合パッケージを使う場合:
[dependencies]
oxc = "0.x"
基本的な使い方
シンプルなパース
import { parseSync } from "oxc-parser";
const sourceCode = `
const greeting = "Hello, Oxc!";
console.log(greeting);
`;
const result = parseSync("example.js", sourceCode);
console.log(result.program); // AST
console.log(result.errors); // パースエラー
ファイル名の拡張子で、JS/TSどちらとしてパースするか自動判定されます。
TypeScriptのパース
import { parseSync } from "oxc-parser";
const tsCode = `
interface User {
id: number;
name: string;
}
const user: User = { id: 1, name: "Taro" };
`;
const result = parseSync("user.ts", tsCode);
console.log(result.program.body);
TypeScriptも特別な設定なしでパースできます。これ一択ですね。
パーサーオプション
import { parseSync } from "oxc-parser";
const result = parseSync("example.js", sourceCode, {
// ASTにTypeScript固有のプロパティを含める
astType: "ts",
// range情報を含める
range: true,
// 括弧の情報を保持
preserveParens: true,
// セマンティックエラーをチェック
showSemanticErrors: true
});
必要に応じてオプションを調整できます。
ESM情報の取得
import { parseSync } from "oxc-parser";
const moduleCode = `
import React from "react";
import { useState, useEffect } from "react";
import type { FC } from "react";
export const App: FC = () => {
return <div>Hello</div>;
};
export default App;
`;
const { module: esmInfo } = parseSync("App.tsx", moduleCode);
console.log(esmInfo.hasModuleSyntax); // true
console.log(esmInfo.staticImports); // import情報の配列
console.log(esmInfo.staticExports); // export情報の配列
ASTを走査せずにimport/export情報を取得できるのは、地味に時短になります。
実践的なユースケース
1. コード解析ツールの作成
import { parseSync } from "oxc-parser";
import fs from "fs";
function analyzeFile(filePath) {
const code = fs.readFileSync(filePath, "utf-8");
const { program, module: esmInfo } = parseSync(filePath, code);
return {
// 依存関係の抽出
dependencies: esmInfo.staticImports.map(imp => ({
source: code.slice(imp.moduleRequest.start, imp.moduleRequest.end),
specifiers: imp.entries
})),
// エクスポートの一覧
exports: esmInfo.staticExports.map(exp => ({
names: exp.entries.map(e => e.localName)
})),
// 関数の数
functionCount: countFunctions(program)
};
}
function countFunctions(node) {
let count = 0;
// ASTを再帰的に走査
// ...
return count;
}
2. import文の書き換え
magic-stringと組み合わせると、import文の書き換えが簡単にできます。
import { parseSync } from "oxc-parser";
import MagicString from "magic-string";
function rewriteImports(code, filename) {
const { module: esmInfo } = parseSync(filename, code);
const s = new MagicString(code);
for (const imp of esmInfo.staticImports) {
const { start, end } = imp.moduleRequest;
const originalModule = code.slice(start, end);
// 例: "./utils" を "./utils.js" に書き換え
if (originalModule.startsWith('"./') && !originalModule.endsWith('.js"')) {
const newModule = originalModule.slice(0, -1) + '.js"';
s.overwrite(start, end, newModule);
}
}
return s.toString();
}
3. カスタムリンターの実装
import { parseSync, Visitor } from "oxc-parser";
function lintNoConsole(code, filename) {
const { program } = parseSync(filename, code);
const warnings = [];
// Visitorパターンでノードを走査
const visitor = new Visitor();
visitor.visitCallExpression = (node) => {
if (node.callee.type === "MemberExpression" &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "console") {
warnings.push({
message: "console文は本番コードから削除してください",
line: node.loc.start.line
});
}
};
visitor.visit(program);
return warnings;
}
4. Rolldownでの利用
RolldownはViteの次期バンドラーで、内部でOxc-Parserを使用しています。
// rolldown.config.js
export default {
input: "./src/index.ts",
output: {
dir: "dist",
format: "esm"
}
};
Rolldownを使うと、間接的にOxc-Parserの恩恵を受けられます。
他のパーサーとの比較
正直なところ、用途によって選択肢が変わります。
| パーサー | 速度 | TypeScript | 成熟度 | ユースケース |
|---|---|---|---|---|
| Oxc-Parser | 最速 | ネイティブ | 発展中 | 高速処理が必要な場面 |
| SWC | 速い | ネイティブ | 成熟 | 汎用的な変換処理 |
| Babel | 普通 | プラグイン | 完成形 | プラグインエコシステム重視 |
| TypeScript Compiler | 遅め | 最高 | 完成形 | 型チェック必須の場面 |
30代になって思うのは、「最速が常に正解ではない」ということ。でもOxc-Parserの速度差は、大規模プロジェクトでは無視できないレベルです。
Oxcプロジェクト全体像
Oxc-Parserは、Oxcプロジェクトの一部です。他にもこんなツールがあります:
- oxlint: ESLintより50〜100倍速いリンター
- oxfmt: Prettierより30倍速いフォーマッター(アルファ版)
- oxc_transformer: Babel互換のトランスフォーマー
- oxc_minifier: 高性能ミニファイア(アルファ版)
- oxc_resolver: enhanced-resolveより28倍速いモジュール解決
将来的には、これらを組み合わせた統合ツールチェーンが完成する見込み。JavaScriptエコシステムの地図が書き換わる可能性があります。
まとめ
Oxc-Parserを使ってみて感じたこと:
- 速度: 体感で明らかに違う
- API: シンプルで使いやすい
- ESM情報: 追加走査不要で便利
- 将来性: Rolldown採用で注目度上昇中
正直なところ、個人プロジェクトでパーサーを直接使う機会は少ないかもしれません。でもRolldownやoxlintの普及により、間接的にOxcの恩恵を受ける場面は確実に増えていきます。
カスタムツールを作るなら、Oxc-Parserは有力な選択肢ですね。特に大規模コードベースを扱う場合、パフォーマンスの差は開発体験に直結します。
JavaScriptツールチェーンの「Rust化」の波、乗り遅れないようにしたいところです。
