Read OSS

Oxc コードベースを読み解く:アーキテクチャとクレートマップ

中級

前提知識

  • Rust の基礎知識(cargo ワークスペース、クレート、モジュール)
  • JavaScript ツールチェーンの基本概念(パーサー、リンター、バンドラー)への理解

Oxc コードベースを読み解く:アーキテクチャとクレートマップ

JavaScript プロジェクトのビルドパイプラインを眺めながら「もっと速くできないか」と思ったことがあるなら、Oxc プロジェクトはまさにその問いに対する Rust の回答です。Oxc(The Oxidation Compiler)は、パーサー・リンター・トランスフォーマー・ミニファイアー・フォーマッター・コードジェネレーターを揃えた、モジュール式の高性能 JavaScript / TypeScript ツールチェーンです。すべて Rust で実装されており、アリーナアロケーターで管理された単一の AST を共有しています。Rust ベースの Vite バンドラーである Rolldown を支え、VoidZero エコシステムの中核を担っています。本記事は全 6 回シリーズの第 1 回として、全体像の俯瞰から個々の最適化テクニックまで、Oxc の内側を順を追って掘り下げていきます。

プロジェクトのミッションとエコシステムにおける位置づけ

JavaScript ツールチェーンの処理は本質的に CPU バウンドです。Oxc が Rust で書かれているのは、予測可能なゼロオーバーヘッドのパフォーマンスを実現するためです。アーキテクチャドキュメントに掲げられた目標は明確です。

  • パフォーマンス: 既存の JavaScript ツールと比較して 10〜100 倍の高速化を実現する
  • 正確性: ECMAScript および TypeScript 標準との完全な互換性を維持する
  • モジュール性: ユーザーが必要なツールを自由に組み合わせられるようにする
  • 開発者体験: 質の高いエラーメッセージとツール統合を提供する

Oxc のアーキテクチャの核心にあるのは「共有」という考え方です。ESLint・Babel・Terser がそれぞれ独自の AST 表現を持っているのとは対照的に、Oxc は単一の AST を単一のメモリアリーナ上に確保し、すべてのツールがその AST に対してシーケンシャルに処理を行います。これにより、シリアライゼーションのオーバーヘッドと重複するパース処理を完全になくしています。

flowchart LR
    subgraph VoidZero Ecosystem
        Oxc[Oxc Toolchain]
        Rolldown[Rolldown Bundler]
        Vite[Vite]
    end
    Oxc -->|parser, transformer, minifier| Rolldown
    Rolldown -->|bundling| Vite
    Oxc -->|linter| Oxlint[oxlint CLI]
    Oxc -->|NAPI bindings| Node[Node.js Tools]

リポジトリのレイアウトとワークスペース構成

リポジトリはそれぞれ役割の異なる 4 つのトップレベルディレクトリで構成されています。

ディレクトリ 役割
apps/ エンドユーザー向け CLI バイナリ oxlintoxfmt
crates/ コアライブラリクレート(公開対象) oxc_parseroxc_linteroxc_allocator
napi/ Node.js NAPI バインディング parsertransformminify
tasks/ 開発ツール・コードジェネレーター ast_toolscoveragebenchmark

ワークスペースは Cargo.toml で設定されており、Rust 2024 エディションとリゾルバー v3 を採用しています。

[workspace]
resolver = "3"
members = ["apps/*", "crates/*", "napi/*", "tasks/*"]

MSRV は 1.92.0 で、N-2 ポリシー(最新の安定版から約 12 週遅れ)に従っています。最新の Rust 機能を使いたいコントリビューターと、安定性を求めるダウンストリームの利用者、両者のバランスを取る現実的な判断です。

Cargo.toml#L272-L280 のリリースプロファイルは、実行時パフォーマンスを最大化するよう設定されています。opt-level = 3、fat LTO、シングル codegen ユニット、そして panic = "abort" です。

[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
strip = "symbols"
panic = "abort"

Tip: panic = "abort" は意図的な設計です。スタックの巻き戻しに頼らず、エラーをきちんとハンドリングするコードを書くよう強制しています。Oxc でパニックが発生したとしたら、それはバグです。

3 層クレートアーキテクチャ

Oxc の約 31 クレートは、古典的なコンパイラ構成を模した 3 つの層に整理されています。基盤層・処理層・アプリケーション層です。この階層構造を理解することがコードベースを読み解く鍵になります。

flowchart TB
    subgraph Application["Application Layer"]
        oxlint[oxlint CLI]
        oxfmt[oxfmt CLI]
        lsp[Language Server]
        napi_parser[napi/parser]
        napi_transform[napi/transform]
    end
    subgraph Processing["Processing Layer"]
        parser[oxc_parser]
        semantic[oxc_semantic]
        linter[oxc_linter]
        transformer[oxc_transformer]
        minifier[oxc_minifier]
        codegen[oxc_codegen]
        mangler[oxc_mangler]
        formatter[oxc_formatter]
    end
    subgraph Foundation["Foundation Layer"]
        allocator[oxc_allocator]
        ast[oxc_ast]
        span[oxc_span]
        syntax[oxc_syntax]
        diagnostics[oxc_diagnostics]
    end
    
    Application --> Processing
    Processing --> Foundation

基盤層(Foundation Layer)

相互依存がほとんどなく、他のすべてのクレートが利用する型を定義するクレート群です。

  • oxc_allocator — アリーナベースのメモリアロケーター。ホットパスに Rc/Arc は登場しません。
  • oxc_spanu32 バイトオフセットによるソース位置(Span 型)、アトム、ソース種別を定義します。
  • oxc_syntax — トークン定義、キーワードマッピング、演算子型、スコープ/シンボル ID 型を提供します。
  • oxc_diagnosticsmiette をベースにした複数出力フォーマット対応のリッチなエラーレポート機能です。
  • oxc_ast — JavaScript / TypeScript AST の完全な型定義を収録しています。

処理層(Processing Layer)

AST データを入力として受け取り、変換・生成を行うクレート群です。

  • oxc_parser — JS・TS・JSX に対応した手書きの再帰下降パーサーです。
  • oxc_semantic — スコープチェーン構築、シンボルテーブル、参照解決を担います。
  • oxc_linter — 15 のプラグインカテゴリにわたる 730 以上のリントルールを実装しています。
  • oxc_transformer — Babel 互換の ES2015〜ES2026 変換、TypeScript 除去、JSX 変換を行います。
  • oxc_minifier — 不動点収束するピープホール最適化ループとデッドコード除去を提供します。
  • oxc_codegen — AST からソースコードへの出力とソースマップ生成を行います。
  • oxc_mangler — スコープと使用頻度の分析に基づいて識別子を短縮します。

アプリケーション層(Application Layer)

処理層のクレートを組み合わせてユーザー向けツールを構築するクレート群です。傘クレートである crates/oxc/src/lib.rs は、フィーチャーフラグを使ってサブクレートを再エクスポートします。

#[cfg(feature = "semantic")]
pub mod semantic {
    pub use oxc_semantic::*;
}

#[cfg(feature = "transformer")]
pub mod transformer {
    pub use oxc_transformer::*;
}

この設計により、ダウンストリームの利用者は必要な機能だけを取り込めます。パーサーのみを使うプロジェクトは、リンターやトランスフォーマーのコンパイルコストを一切払わずに済みます。

CompilerInterface パイプライン

すべての処理層クレートをつなぐ接着剤となるのが CompilerInterface トレイトです。crates/oxc/src/compiler.rs に定義されており、カスタマイズのためのフックポイントを備えた完全なコンパイルパイプラインを実現しています。

パイプラインは 7 つのステージを順に処理し、各ステージは設定メソッドによって有効・無効を切り替えられます。

sequenceDiagram
    participant User as Consumer
    participant CI as CompilerInterface
    participant P as Parser
    participant S as SemanticBuilder
    participant T as Transformer
    participant C as Compressor
    participant M as Mangler
    participant G as Codegen
    
    User->>CI: compile(source_text, source_type, path)
    CI->>P: parse()
    P-->>CI: ParserReturn (AST + errors)
    CI->>CI: after_parse() hook
    CI->>S: build(program)
    S-->>CI: Scoping (scopes + symbols)
    CI->>CI: after_semantic() hook
    CI->>T: build_with_scoping()
    T-->>CI: TransformerReturn
    CI->>CI: after_transform() hook
    CI->>C: build(program, options)
    CI->>M: build(program, options)
    CI->>G: build(program)
    G-->>CI: CodegenReturn (code + source map)
    CI->>CI: after_codegen() hook

compiler.rs#L117-L212compile メソッドはこのシーケンスに忠実に従っています。各ステージは対応するオプションメソッドによってガードされており、たとえば transform_options()None を返せば変換フェーズはまるごとスキップされます。

重要な設計上のポイントがあります。パイプライン全体を通して Scoping 構造体が受け渡されます。この構造体(詳細は第 3 回で解説します)はスコープツリー・シンボルテーブル・参照解決データを保持しており、セマンティック解析から変換(内部で更新されます)、インジェクション/定義プラグイン、そして最終的にマングラーとコードジェネレーターへと流れていきます。

let mut scoping = semantic_return.semantic.into_scoping();

// Transform updates scoping
if let Some(options) = self.transform_options() {
    let mut transformer_return =
        self.transform(options, &allocator, &mut program, source_path, scoping);
    scoping = transformer_return.scoping;
}

各フックメソッド(after_parseafter_semanticafter_transform)は ControlFlow<()> を返すため、利用者はパイプラインを途中で中断できます。これにより CompilerInterface は、フルビルドパイプラインとしてだけでなく、セマンティック解析後に処理を止めるリントのみのワークフローにも同等に対応できます。

開発ワークフローとツール

日々の開発は justfile を中心に回っています。標準化されたコマンドが定義されています。

コマンド 用途
just ready CI のフルチェック:フォーマット・リント・テスト・ドキュメント・AST コード生成
just ast AST 定義から派生するすべてのコードを再生成する
just test cargo test --all-features を実行する
just fmt Rust と JS コードをフォーマットし、未使用の依存関係を削除する
just new-rule name plugin 任意のプラグイン向けに新しいリントルールをスキャフォールドする

justfile#L36-L47just ready コマンドは CI が確認するすべての項目を実行します。PR を送る前に実行すべき、唯一のコマンドと言えます。

flowchart LR
    A[just ready] --> B[typos]
    B --> C[cargo lintgen]
    C --> D[just fmt]
    D --> E[just check]
    E --> F[just test]
    F --> G[just lint]
    G --> H[just doc]
    H --> I[just ast]

just ast コマンドも特に重要です。oxc_ast_tools というコードジェネレーターを実行し、アノテーションされた AST 型定義からビジター trait・ビルダーメソッド・derive 実装・レイアウトアサーションを自動生成します。これはプロセスマクロではなく、事前生成(ahead-of-time)方式で、生成結果は git にコミットされます。この仕組みの詳細は第 4 回で掘り下げます。

Tip: クローン直後に just ast が失敗する場合は、2 回実行してみましょう。1 回目のパスで他のジェネレーターが依存する Rust コードが生成されます。justfile はフォールバックでこれを処理しています。cargo run -p oxc_ast_tools || { cargo run -p oxc_ast_tools --no-default-features && cargo run -p oxc_ast_tools; }

次回以降の内容

本記事ではコードベース全体の地図を示しました。次の 5 回では、その地図を頼りに実際のコードに踏み込んでいきます。第 2 回では、Oxc を高速たらしめる基盤、アリーナアロケーターと AST 設計を深く掘り下げます。Box<'a, T>Vec<'a, T>Drop なしでどう機能するか、なぜ AST が ESTree の Identifier を 3 つの異なる型に分割しているのか、そして CloneInTakeIn トレイトがアリーナ上のデータを安全に変更可能にする仕組みを解説します。