Read OSS

Vite 8 アーキテクチャ:コードベースの全体像

中級

前提知識

  • フロントエンドビルドツール(webpack、Rollup、esbuild など)の基本的な理解
  • ES Modules と Node.js の基礎知識
  • TypeScript のコードを読む基本的な能力

Vite 8 アーキテクチャ:コードベースの全体像

Vite は React、Vue、Svelte、そして数多くのフレームワークにとってデファクトのフロントエンドビルドツールになりました。しかし、そのソースコードを初めて読もうとすると、地図なしに見知らぬ都市へ放り込まれたような感覚を覚えるかもしれません。このシリーズはその問題を解決するためのものです。CLI のブートストラップからプロダクションビルドまで、Vite 8 のすべての主要サブシステムを順を追って解説し、プロジェクトを読み解きコントリビュートするための知識を身につけていただきます。

この最初の記事では、全体の地図を描きます。読み終えるころには、各ディレクトリの役割、4つのランタイムコンテキストの関係、そして Vite 8 の Rolldown 移行の意味を理解できるようになるでしょう。

モノレポの構成と3つのパッケージ

Vite は pnpm ベースのモノレポです。トップレベルには3つの重要なパッケージが存在します。

パッケージ パス 役割
vite packages/vite コア — 開発サーバー、ビルドパイプライン、プラグインシステム、HMR
create-vite packages/create-vite スキャフォールディング CLI(npm create vite@latest
plugin-legacy packages/plugin-legacy SystemJS を使ったレガシーブラウザサポート

コードベースの大部分を占めるのは packages/vite ディレクトリです。その package.json を見ると基本的な構成がわかります。ESM ファーストのパッケージ(type: "module")として設計されており、エントリーポイントとして bin/vite.js を公開し、執筆時点のバージョンは 8.0.8 です。

exports マップも確認しておきましょう。

{
  ".": "./dist/node/index.js",
  "./client": { "types": "./client.d.ts" },
  "./module-runner": "./dist/node/module-runner.js",
  "./internal": "./dist/node/internal.js"
}

公開されているエントリーポイントは4つあります。メイン API、クライアント型定義、モジュールランナー、そして内部 API です。この設計は偶然ではありません。次に説明する4つのランタイムコンテキストをそのまま反映しています。

4つのランタイムコンテキスト

packages/vite/src の中には、互いに異なる4つの実行コンテキストに対応した4つのディレクトリがあります。

graph TD
    subgraph "packages/vite/src"
        N["node/"] -->|"Server-side core"| DESC1["Dev server, build pipeline,<br/>config, plugins"]
        C["client/"] -->|"Browser runtime"| DESC2["HMR client, error overlay,<br/>CSS injection"]
        M["module-runner/"] -->|"Environment-agnostic"| DESC3["SSR evaluation,<br/>module cache, transport"]
        S["shared/"] -->|"Cross-context"| DESC4["HMR protocol, utils,<br/>transport normalization"]
    end

src/node/ は Vite の心臓部です。Node.js 上で動作し、設定の解決、開発サーバー、ビルドパイプライン、プラグインシステム、オプティマイザー、ミドルウェアスタックといったすべての機能を含んでいます。vite devvite build を実行したとき、実際に動いているのはこのディレクトリのコードです。

src/client/ は開発中にブラウザへ注入されるコードです。メインファイルである client.ts が開発サーバーへの WebSocket 接続を確立し、HMR アップデートのペイロードを処理します。エラーオーバーレイの表示や CSS のホットインジェクションもここで管理されます。

src/module-runner/ は Vite の環境非依存なモジュール実行エンジンです。ModuleRunner クラスは AsyncFunction を使ってサーバーサイドのコードを評価し、独自のモジュールキャッシュ(EvaluatedModules)を維持しながら HMR をサポートします。SSR を動かす基盤であり、ワーカー、エッジランタイム、あらゆる JavaScript 環境で動作します。

src/shared/ はすべてのコンテキストで動作しなければならないコードが置かれています。たとえば HMRClient クラスは、ブラウザクライアントとモジュールランナーの両方から使われます。トランスポートの正規化もここに置かれており、WebSocket、ワーカーメッセージ、直接関数呼び出しなど、どの方法でも同じプロトコルが使えるようになっています。

ヒント: コードベースを読み進めるときは、自分が src/ のどのサブディレクトリにいるかを常に意識しましょう。shared/ のコードは node/ からインポートできず、client/ のコードはブラウザで動きます。こうした制約がすべての設計判断の根底にあります。

CLI のブートストラップシーケンス

ターミナルで vite と入力すると、実行は bin/vite.js から始まります。79行のこのファイルは見た目以上に多くのことを、慎重な順序でこなしています。

sequenceDiagram
    participant Shell
    participant bin/vite.js
    participant CLI (cli.ts)

    Shell->>bin/vite.js: Execute
    bin/vite.js->>bin/vite.js: Enable source maps (if not in node_modules)
    bin/vite.js->>bin/vite.js: Capture global.__vite_start_time
    bin/vite.js->>bin/vite.js: Parse --debug, --filter, --profile from argv
    bin/vite.js->>bin/vite.js: Set process.env.DEBUG before any imports
    bin/vite.js->>bin/vite.js: start(): enable compile cache, schedule flush
    alt --profile flag present
        bin/vite.js->>bin/vite.js: Start V8 Profiler via node:inspector
        bin/vite.js->>CLI (cli.ts): import('../dist/node/cli.js')
    else Normal start
        bin/vite.js->>CLI (cli.ts): import('../dist/node/cli.js')
    end

注目すべき設計上の選択が3つあります。

  1. インポートより先にデバッグフラグを処理する。 19〜46行では、他のモジュールを一切インポートする前に --debug フラグと --filter フラグを解析して process.env.DEBUG をセットしています。こうすることで、Vite 内部全体で使われている debug パッケージが、起動直後からフラグを認識できるようになります。

  2. コンパイルキャッシュとフラッシュタイマー。 48〜63行start() 関数は module.enableCompileCache()(Node 22.8+)を呼び出し、10秒後にフラッシュをスケジュールします。コメントがその理由を説明しています。長時間起動し続ける開発サーバーでは、Node はプロセス終了時にキャッシュを書き出そうとしますが、開発サーバーは通常 process.exit() で終了するため、フラッシュがスキップされてしまうのです。

  3. プロファイラーのサポート。 65〜78行--profile フラグは V8 CPU プロファイラーセッションを起動し、開発中に p キーで切り替えられるようになります。トランスフォームが遅い原因を調べたいときに役立ちます。

コマンドのルーティングと公開 API

ブートストラップが完了すると、処理は src/node/cli.ts に渡されます。ここでは cac ライブラリを使って4つのコマンドが定義されています。

flowchart LR
    CLI["vite CLI"] --> DEV["vite [root]<br/>(default command)"]
    CLI --> BUILD["vite build [root]"]
    CLI --> OPTIMIZE["vite optimize [root]<br/>(deprecated)"]
    CLI --> PREVIEW["vite preview [root]"]

    DEV -->|"imports"| CS["createServer()"]
    BUILD -->|"imports"| CB["createBuilder()"]
    PREVIEW -->|"imports"| PV["preview()"]

各コマンドはハンドラーを遅延インポートします。開発には createServer、ビルドには createBuilder、プレビューサーバーには preview が呼ばれます。必要なものだけを読み込む設計になっているため、起動を速く保てます。

dev コマンドのアクション(207〜303行)はサーバーを生成し、server.listen() を呼び出し、URL を表示して、CLI のショートカットキーをバインドします。build コマンド(343〜382行)は createBuilder を使い、builder.buildApp() を呼び出してマルチ環境ビルドを調整します。

build コマンドの --app フラグにも注目しましょう。このフラグを指定すると { builder: {} } が設定され、新しいマルチ環境ビルドシステムが有効になります。これにより、フレームワーク側でクライアントビルドと SSR ビルドの協調を制御できるようになります。

プログラマティック API のサーフェスは src/node/index.ts で定義されています。このファイルは createServerbuildcreateBuilderpreviewdefineConfig といったコア関数を再エクスポートするとともに、Rolldown のユーティリティも公開しています。

export { parse, parseSync, minify, minifySync, Visitor } from 'rolldown/utils'

これらの Rolldown 再エクスポートにより、Vite は Oxc ベースのパースとミニファイをエコシステム全体に提供しています。以前は esbuild.transform() を使っていたプラグインも、vite から直接 parse()minify() を呼び出せるようになりました。

Rolldown 移行:esbuild+Rollup から統合ツールチェーンへ

Vite 8 における最大のアーキテクチャ変更は、デュアルツールチェーン(開発時トランスフォームに esbuild、プロダクションバンドルに Rollup)から、Rollup のプラグイン API と互換性を持つ Rust 製バンドラー Rolldown への移行です。

graph TB
    subgraph "Vite ≤7 (Dual Toolchain)"
        DEV7["Dev Server"] --> ESB["esbuild<br/>(transpile, dep optimization)"]
        BUILD7["Production Build"] --> ROLLUP["Rollup<br/>(bundling, tree-shaking)"]
    end
    subgraph "Vite 8 (Unified Toolchain)"
        DEV8["Dev Server"] --> RD["Rolldown<br/>(transpile, dep optimization,<br/>bundling, tree-shaking)"]
        BUILD8["Production Build"] --> RD
        RD --> OXC["Oxc<br/>(parsing, transforms)"]
    end

この移行の痕跡はコードベースのあちこちに見られます。package.json では rolldown がバージョン 1.0.0-rc.15 の直接依存として追加されています。esbuild は後方互換性のため直接依存に残っていますが、peerDependencies にもオプションとして記載されており、主要ツールチェーンではなくなったことを示唆しています。constants.tsROLLUP_HOOKS 配列には、開発中に Vite のプラグインコンテナがエミュレートするすべての Rolldown フックが列挙されています。これが開発環境とビルド環境の挙動を一致させる仕組みです。

index.ts はまだ後方互換のために esbuildVersion 定数('0.25.0' にハードコード)を export しており、rollupVersionrolldownVersion とともに公開されています。移行が進行中であることを示すスナップショットです。

ヒント: Vite プラグインを書くとき、esbuild と Rolldown のどちらの API を使うべきか迷ったら、Rolldown を選んでください。vite から export されている parseparseSyncminifyVisitor が新しい標準です。esbuild は後方互換性のためにオプションのピア依存として引き続き利用できますが、新しいコードは Rolldown/Oxc をターゲットにするべきです。

次回の予告

3つのパッケージ、4つのランタイムコンテキスト、createServer/createBuilder/preview へルーティングする遅延 CLI、そして統合された Rolldown ツールチェーン——全体像が把握できたところで、いよいよ詳細へ踏み込む準備が整いました。

次回の記事では設定の解決を掘り下げます。350行超の resolveConfig パイプライン、環境の階層(PartialEnvironment → BaseEnvironment → DevEnvironment | BuildEnvironment)、Proxy ベースの設定マージの仕組みを解説します。さらに、移行期にエコシステムを動かし続けるための esbuild-to-Rolldown 互換レイヤーにも触れます。