跪拜 Guibai
← All articles
Frontend · Backend · Programmer

Rolldown cuts plugin change round-trip by 26x with field-level diffing

By 前端之虎陈随易 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Rolldown is the bundler inside Vite 8, so large Vite production builds get this speedup without changing a line of plugin code: the `generateBundle` phase drops roughly 40%, and a single round-trip step falls from about a second to 39 ms. The 26x figure covers only the prepare-changes step, not the whole hook.

Summary

Rolldown's next release replaces the coarse plugin callback with field-level diffing. Previously any plugin mutation on a chunk, even a trivial filter on `imports`, reserialized the entire chunk including its sourcemap across the Rust/JS boundary, and invalidated the code cache so the next `chunk.code` read had to copy everything again. The fix transmits only the fields that changed; untouched chunks send nothing at all.

Benchmarks on an interleaved A/B build of 2182 chunks and 136 MB of hidden sourcemaps show the `generateBundle` phase falling from 1256 ms to 752 ms, a 40.1% reduction, with total build time down 20% to 25%. Tests on a large Vite production app bring the `collectChangedBundle` step from 1015 ms and 720 ms down to 39 ms and 28 ms across two CPU sampling runs, and reading `chunk.map` drops from 866 ms to 97 ms. Output hashes match across runs: all but 5 of 10,958 files are identical after normalization, and those 5 also differ between the two baseline builds.

The change merged on November 6 and ships with the next Rolldown release, which Vite 8 will use. Small projects won't feel it; the win is for large, chunk-heavy builds with hidden sourcemaps. The maintainers also added guard rails: writing a non-string into `chunk.code` or a non-array into `imports` now throws a `TypeError` naming the chunk and property, instead of silently accepting the bad value.

Takeaways
— The fix is field-level diffing: only chunk properties a plugin actually changed get serialized back to Rust, and unchanged chunks send zero bytes.
— Old behavior reserialized the whole chunk including its sourcemap when any field was touched, and invalidated the code cache, forcing the next `chunk.code` read to copy everything again.
— An interleaved A/B benchmark over 2182 chunks and 136 MB of hidden sourcemaps shows `generateBundle` falling from 1256 ms to 752 ms (40.1%) and total time from 2392 ms to 1862 ms.
— On a large Vite production build, the `collectChangedBundle` callback step dropped from 1015 ms and 720 ms to 39 ms and 28 ms across two CPU sampling runs; reading `chunk.map` fell from 866 ms to 97 ms.
— Output integrity held: 10,953 of 10,958 files matched after hash normalization, and the 5 outliers also differed between the two baseline builds.
— New type guards throw a `TypeError` naming the chunk and property when a plugin writes a non-string into `chunk.code` or a non-array into `imports`, instead of silently using the old value.
— Merged November 6 (PR #11488), ships with the next Rolldown release, which Vite 8 uses.
— The 26x figure covers only the prepare-changes step; the full output hook is roughly 40% faster.
— Small and medium projects see little benefit; the win applies to builds with thousands of chunks and enabled hidden sourcemaps.
Conclusions

Vite itself was paying this cost in practice: `vite:css-post` filters `chunk.imports` on every chunk with pure CSS, even though most filenames never change, so the optimization fixes real behavior inside the default toolchain rather than a hypothetical worst case.

The new type guards harden the plugin boundary: invalid writes that were silently accepted now fail with a specific `TypeError` at hook time, which pushes plugin authors to catch mistakes before they reach the final output.

The maintainers' honesty about the benchmark matters: 26x is only one step inside `generateBundle`, and quoting that multiplier as a global build speedup would misrepresent the actual 40% hook-level improvement.

Concepts & terms
generateBundle / writeBundle
Rolldown output hooks that run after code generation; plugins apply final mutations here, and those mutations must be transmitted back to the Rust core before files are written to disk.
collectChangedBundle
The internal step that gathers plugin mutations back to Rust. Before this PR it round-tripped full chunks; after the fix it sends only the fields that actually changed.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗