2.3 · difficulty 3/4 · 15 min read
Rolldown Architecture Deep Dive
How does Rolldown work internally? What do the three stages — OXC parsing, module graph construction, and chunk generation — each do? Where do the architectural differences with Rollup lie?
Comparing the Two Architectures
Before diving into Rolldown's internals, take a moment to feel the architectural difference between Vite 7 and Vite 8 at a glance:
Two engines had different designs, so plugin behavior often needed separate adaptation.
One Rust core keeps dev/build behavior aligned, while the compatibility layer preserves Rollup APIs.
On the left is Vite 7's dual engine — esbuild handles dev transforms, Rollup handles build bundling, and the two are completely independent. On the right is Vite 8's unified Rolldown architecture.
Rolldown's Three Core Stages
Rolldown's execution can be broken down into three main stages:
Stage 1: Parsing and Module Graph Construction
Input: entry file path
What Rolldown does:
- Calls OXC Parser to parse the entry file, producing an AST
- Extracts all
importdeclarations from the AST - Runs the resolve flow for each
importpath (triggering theresolveIdhook) - For each successfully resolved module, runs load (triggering the
loadhook) + parse - Recursively processes dependencies until all modules have been parsed
This process builds a Module Graph — a directed graph of all modules in the project and their dependency relationships.
Entry: src/main.tsx
│
├── resolve: "react" → node_modules/react/index.js
├── resolve: "./App" → src/App.tsx
│ ├── resolve: "./Button" → src/Button.tsx
│ └── resolve: "./styles.css" → src/styles.css
└── resolve: "virtual:env" → \0virtual:env (handled by plugin)Stage 2: Transform
Once the module graph is built, Rolldown runs the transform pipeline on each module:
- Triggers all plugins'
transform(code, id)hooks (executed sequentially) - OXC Transformer handles TypeScript type-stripping
- For JSX, calls the corresponding transform (e.g.,
@vitejs/plugin-react) - Handles
import.meta.*injections (e.g.,import.meta.env,import.meta.hot)
In dev mode, Rolldown executes transforms on demand — only modules actually requested by the browser are transformed; unrequested modules are left alone. This is the fundamental reason cold starts are so fast.
In build mode, Rolldown fully transforms all modules, preparing them for tree-shaking and chunk generation.
Stage 3: Chunk Generation and Optimization (build only)
This stage only runs during vite build:
- Tree-shaking — based on the module graph and
export/importanalysis, marks and removes unused code - Chunk splitting — based on dynamic
import()boundaries androlldownOptions.output.manualChunksconfiguration, decides how to pack modules into different chunk files - Minification — OXC Minifier compresses code (removes whitespace, shortens variable names, etc.)
- Asset processing — CSS extraction, image hash fingerprinting, static asset copying
Hook execution order:
buildStart → resolveId/load/transform (per module) → buildEnd
→ renderStart → renderChunk (per chunk) → generateBundle → writeBundle → closeBundleOXC's Place in the Architecture
OXC is not an external dependency of Rolldown — it is Rolldown's internal core:
Rolldown process
│
├── OXC Parser — handles all JS/TS AST parsing
├── OXC Transformer — handles TS type-stripping, basic JSX support
├── OXC Minifier — handles code minification in the build stage
└── Rolldown Core — module graph, resolve logic, chunk algorithm, plugin hooksOXC is implemented in Rust and runs in the same process as Rolldown, with no need for cross-process communication. This is fundamentally different from Vite 7, where esbuild ran as a separate process (via child_process or WASM).
Architectural Differences from Rollup
Rolldown deliberately mirrors Rollup's plugin API, but the underlying architecture is fundamentally different:
| Dimension | Rollup | Rolldown |
|---|---|---|
| Implementation | JavaScript | Rust |
| Parser | Acorn (JS) | OXC Parser (Rust) |
| Concurrency | Single-threaded + Promise | Multi-threaded (Rayon) |
| Tree-shaking | Based on Acorn AST | Based on OXC AST |
| Plugin API | Rollup Plugin native | Compatibility layer (translates to Rust calls) |
Rolldown uses a compatibility layer to translate JavaScript plugin hook calls into internal Rust operations. This means:
- The plugin API is transparent to users (the syntax is identical)
- Performance overhead is concentrated at the JS ↔ Rust boundary, not in parsing and analysis itself
- As Rolldown iterates, certain hook behavior details may have minor differences from Rollup
Self-check
- Across Rolldown's three stages, what is the main difference between dev mode and build mode, and in which stage does it occur? Why?
- OXC Parser is integrated inside the Rolldown process. What practical advantages does this have over Vite 7's external esbuild process architecture?
- Is Rolldown's module graph construction depth-first or breadth-first? How does this affect the call order of the
resolveIdhook? - Why is Rolldown's plugin API described as a "compatibility layer"? What potential risks does this layer introduce?
// Analyze the following code. In the build stage, which hooks will Rolldown trigger and in what order?
// (Assume resolveId and load use default behavior; transform has one user plugin)
// vite.config.ts
export default defineConfig({
build: {
rolldownOptions: {
input: "src/main.ts",
output: {
manualChunks: {
vendor: ["react", "react-dom"],
},
},
},
},
plugins: [myTransformPlugin()],
})
// src/main.ts
import React from "react"
import { App } from "./App"
// Questions:
// 1. How many times will renderChunk be called? (Hint: there is a manualChunks config)
// 2. Does generateBundle come before or after renderChunk?
// 3. At which stage is myTransformPlugin's transform hook called?