Vite 8 Rolldown Migration: Getting 10–30x Faster Builds While Keeping Existing Rollup Plugins Intact
I'll admit I was pretty nervous at first — my initial reaction was "is the ecosystem flipping upside down again?" When news broke that Vite 8 was adopting Rolldown as its default bundler, my first worry was whether I'd have to rewrite all my perfectly working Rollup plugins. But after going through the migration myself, my perspective changed quite a bit.
Reading this article will help you anticipate what might trip you up when upgrading an existing project to Vite 8, and give you patterns to bake performance in from the start when writing new plugins. For those unfamiliar with Rollup hooks: a hook is a function that runs at a specific point in the build process (file loading, transformation, chunk generation, etc.). Plugins extend bundler behavior by implementing these hooks. This article also focuses on production build-time performance — changes to dev server HMR performance are a separate topic.
Rolldown implements the existing Rollup plugin API as-is, while hook filters operating at the Rust layer deliver 10–30x build performance gains on large projects. And in most cases, both of these come without any code changes.
Two Ways Rolldown Guarantees Plugin Compatibility
First: It Implements the Rollup Plugin API as-is
Rolldown is a high-performance JavaScript/TypeScript bundler written in Rust, developed by VoidZero (founded by Evan You). With its official 1.0 release in early 2026, it follows SemVer and has stabilized its public API (option names, types, and plugin hook signatures).
The most important design principle behind Rolldown is simple: "Existing Rollup plugins must work without modification. If they don't, that's a bug in Rolldown."
In practice, it implements the same interfaces for Rollup's core hooks — resolveId, load, transform, renderChunk, generateBundle, and others. Based on the official compatibility tracking issue (GitHub #819), over 90% of existing Rollup plugins are compatible with Vite 8 without any code changes.
Second: Hook Filters — Rust Screens First
Honestly, this was the part I found most interesting. In the traditional Rollup approach, when a plugin hook is called, the hook itself checks conditions internally. The classic example is checking for .vue files inside a transform hook.
// Traditional Rollup approach: condition checked inside the hook
{
name: 'vue-transform',
transform(code, id) {
if (!id.endsWith('.vue')) return null; // JS → Rust context switch has already happened
// actual processing logic
}
}The problem is that this causes a Rust → JavaScript → Rust context switch every single time. In simple terms, there's a cost to having Rust-based bundler code call a JS function, and when this switch repeats across hundreds of files, the overhead accumulates.
Rolldown's hook filters evaluate this condition at the Rust layer first. If the condition isn't met, the JS context switch never happens at all.
// Rolldown approach: pre-evaluated at the Rust layer
{
name: 'vue-transform',
transform: {
filter: { id: /\.vue$/ }, // Evaluated in Rust — no JS entry
handler(code, id) {
// If we're here, it's guaranteed to be a .vue file
// actual processing logic
}
}
}Hook filters are supported in Rollup 4.38.0+, Vite 6.3.0+, and all versions of Rolldown. Standardization is progressing across the ecosystem, so when creating new plugins, writing them this way from the start is a good practice.
withFilter — Injecting Filters into Someone Else's Plugin
This is a situation I run into often in real projects: a third-party plugin doesn't support hook filters. The withFilter utility lets you inject filters from the outside without touching the plugin's source.
import { withFilter } from 'rolldown'
import somePlugin from 'some-rollup-plugin'
export default {
plugins: [
withFilter(somePlugin(), {
transform: { filter: { moduleType: 'ts' } }
})
]
}The fact that users can capture the performance benefit immediately, even if the plugin author hasn't updated their package, is quite a practical approach.
Real-World Application
Example 1: Upgrading an Existing Vite 7 Project to Vite 8
In most cases, you'll barely need to touch vite.config.ts. Vite 8 adopts Rolldown as its default bundler, so the switch happens without any additional configuration.
// vite.config.ts (Vite 8) — existing Rollup plugins work as-is
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue(), legacy()],
// No additional Rolldown configuration needed
})| Item | Notes |
|---|---|
@vitejs/plugin-vue |
Compatible without modification |
@vitejs/plugin-legacy |
Compatible without modification |
| CJS-based legacy packages | Requires legacy: { inconsistentCjsInterop: true } option |
Example 2: Writing a Native Rolldown Plugin from Scratch
When creating a new plugin, you can write it using hook filters from the start. Here's a simple plugin example that processes JSON files.
import { readFile } from 'node:fs/promises'
import type { Plugin } from 'rolldown'
const myJsonPlugin: Plugin = {
name: 'my-json-plugin',
load: {
filter: { id: /\.json$/ },
async handler(id) {
const data = await readFile(id, 'utf-8')
return {
code: `export default ${data}`,
moduleType: 'js' // Must be specified when converting non-JS formats
}
}
}
}
moduleType: 'js'— In Rolldown, this must be explicitly specified when returning a result after processing non-JS modules like JSON or CSS. Omitting it means Rolldown can't determine the module type, which results in unclear parsing errors likeCannot parse module, or the module is silently treated as empty. The error messages are vague enough that it can take a while to track down the cause.
Example 3: Gradual Migration — Validating with rolldown-vite First
For complex projects, a two-phase approach is recommended: apply rolldown-vite in a Vite 7 environment first to isolate compatibility issues.
# Phase 1: Vite 7 + Rolldown experimental integration
pnpm add -D rolldown-vite// vite.config.ts (Phase 1)
import { defineConfig } from 'rolldown-vite' // import from rolldown-vite instead of vite
export default defineConfig({
plugins: [/* existing plugins as-is */],
})Run a build in this state and check the terminal for unsupported hook warnings or plugin-related errors. If any plugins fail at this stage, you can address them before moving to Vite 8.
# Phase 2: Official Vite 8 upgrade after confirming compatibility
pnpm add -D vite@latest// vite.config.ts (Phase 2)
import { defineConfig } from 'vite' // restore back to vite| Phase | Package | Purpose |
|---|---|---|
| Phase 1 | rolldown-vite |
Pre-check compatibility issues in existing environment |
| Phase 2 | vite@8 |
Switch to official Rolldown build environment |
Example 4: Temporary Workaround for CJS Legacy Packages
When an older package using CommonJS modules causes issues, you can apply a temporary workaround with the following option.
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
legacy: {
inconsistentCjsInterop: true // Temporary compatibility for mixed CJS/ESM packages
}
})This option is a temporary fix. Long-term, the natural direction is either for the package to be updated to support ESM, or to replace it with an ESM alternative.
Pros and Cons
Advantages
| Item | Details |
|---|---|
| Build speed | Rust-based, 10–30x faster than Rollup (based on official benchmarks) |
| High compatibility | Implements Rollup plugin API as-is, allowing reuse of existing ecosystem assets |
| Hook filters | Rust-layer pre-evaluation maximizes performance gains in large projects |
| Vite 8 integration | Benefits available with just a Vite 8 upgrade, no extra configuration |
| Stable API | SemVer guarantee post-1.0 ensures plugin ecosystem stability |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Partially supported hooks | Some complex hooks like renderChunk, augmentChunkHash incomplete |
Check official compatibility tracking issue #819 before deciding |
| TS/JSX handling | Rolldown transforms internally, so transform hooks must be able to receive TS/JSX input | Review whether plugin logic can handle TS/JSX input |
| esbuild-dependent plugins | Plugins using transformWithEsbuild are not supported |
Migrate to transformWithOxc API |
| Multiple output behavior difference | Rollup uses a single process; Rolldown processes each output separately | Review logic that shares state across outputs |
| Composable Filters | Advanced filter composition from @rolldown/pluginutils not supported in Vite/unplugin |
Use simple filters only for now; await future updates |
Oxc — A Rust-based JavaScript/TypeScript parser and transpiler used internally by Rolldown. It replaces the role that esbuild played in Vite previously, and in Vite 8, the
transformWithOxcAPI is recommended.
The Most Common Mistakes in Practice
-
Omitting
moduleType: 'js': I wasted 30 minutes on this while writing a JSON plugin. ACannot parse moduleerror appeared, and it took a while to figure out whether the problem was in the plugin or the file. It's worth building the habit of always specifyingmoduleType: 'js'in the return value ofload/transformhooks that process JSON or CSS. -
Using esbuild-dependent plugins as-is: Plugins that internally use
transformWithEsbuildwon't work in Vite 8. It's recommended to check the plugin README or source code foresbuilddependencies beforehand. In most cases, an alternative plugin based ontransformWithOxcalready exists. -
Sharing global state across outputs in multi-output plugins: If you assume all outputs are processed in a single process (as in Rollup) and share global state in plugins, it can conflict with Rolldown's per-output isolation. Patterns that maintain a global map in hooks like
renderChunkrequire particular caution — I went through some troubleshooting on this myself.
Closing Thoughts
I was initially worried the ecosystem was going through another major upheaval, but it turned out to be much smoother than expected. Rolldown is designed not to replace the existing Rollup ecosystem, but to layer Rust's performance on top of it — which means the migration burden is genuinely low. It's common to see 30-second builds drop to under 5 seconds.
Three steps you can take right now:
- Check compatibility on your current project with
rolldown-vitefirst. Afterpnpm add -D rolldown-vite, just change the import path invite.config.tsto build with Rolldown in your existing Vite 7 environment. This phase lets you identify problematic plugins early. - After confirming compatibility, upgrade to Vite 8 with
pnpm add -D vite@latest. Just revert the import path back tovite. You'll immediately feel the difference of a 30-second build dropping to under 5 seconds. - Apply the hook filter pattern from the start in any new plugins you write. Simply changing the
if (!id.endsWith('.vue')) return nullpattern tofilter: { id: /\.vue$/ }automatically applies performance optimization across Rolldown, Rollup 4.38+, and Vite 6.3+.
References
If you need the full plugin API list, start with the first link; for hook filter detailed specs, the second; and to check compatibility status before migrating, the twelfth link (GitHub issue #819) is the most practical.
- Plugin API | Rolldown
- Plugin Hook Filters | Rolldown
- withFilter function | Rolldown
- Why Plugin Hook Filters? | Rolldown
- Announcing Rolldown 1.0 | VoidZero
- Announcing Rolldown 1.0 RC | VoidZero
- Vite 8.0 is out! | Vite
- Vite 8 Beta: The Rolldown-powered Vite | Vite
- Rolldown Integration | Vite 7
- Migration from v7 | Vite
- Vite 8 Rolldown Migration Guide | byteiota
- Rollup Plugin Compat Status (Tracking Issue) | GitHub
- VoidZero's Rolldown Library | InfoQ
- unplugin - Unified plugin system | GitHub
- tsdown - The elegant bundler for libraries | GitHub
- Rolldown GitHub Repository