perf: avoid unnecessary intermediate sourcemaps#9599
Conversation
How to use the Graphite Merge QueueAdd the label graphite: merge-when-ready to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
✅ Deploy Preview for rolldown-rs canceled.
|
4267424 to
f6653cb
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR optimizes sourcemap handling during plugin transforms to avoid generating expensive intermediate (hires) sourcemaps when plugins either omit map or explicitly return map: null, addressing the memory/time overhead described in #5062.
Changes:
- Introduces explicit sourcemap-chain markers for transform results that are
map-omitted vsmap: null, and updates sourcemap collapsing/materialization to account for them. - Updates the native MagicString sourcemap generation channel to carry
module_idso generated maps get the correctsource. - Adds/adjusts a fixture test to cover
map: null+ changed code preserving originalsourcesContent(preventing injected code from leaking).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rolldown/tests/fixtures/plugin/transform/without-sourcemap/_config.ts | Extends fixture to return map: null and asserts correct sourcesContent / unmapped injected code. |
| packages/rolldown/src/plugin/bindingify-build-hooks.ts | Ensures native MagicString path signals map: null to avoid Rust treating it as an omitted/broken map. |
| crates/rolldown/src/utils/render_ecma_module.rs | Updates sourcemap chain collapsing to handle Omitted/Null elements without generating intermediate maps. |
| crates/rolldown/src/stages/scan_stage.rs | Enhances MagicString worker to set source using carried module_id; updates chain sorting to include new element kinds. |
| crates/rolldown_sourcemap/src/lib.rs | Adds empty_sourcemap() helper used as a lightweight placeholder map. |
| crates/rolldown_plugin/src/plugin_driver/build_hooks.rs | Stops generating hires maps on missing map; records Omitted/Null markers instead. |
| crates/rolldown_plugin/src/plugin_context/transform_plugin_context.rs | Updates get_combined_sourcemap to incorporate Omitted/Null semantics and MagicString message payload. |
| crates/rolldown_common/src/types/sourcemap_chain_element.rs | Adds Omitted and Null variants to the sourcemap chain element enum. |
| crates/rolldown_common/src/source_map_gen_msg.rs | Extends MagicString message payload to include module_id. |
|
It would be better if we could add some tests to verify the optimization and avoid regressions in the future. |
The optimization itself is transparent so we cannot have a test (there's no observable change). Were you thinking of a benchmark? |
I see. I don't think this is worth adding a benchmark for. I don't have any other questions. |
f6653cb to
1a75854
Compare
1a75854 to
704125e
Compare
Merge activity
|
Claude gave me the following result with #9621: <details> ### Empirical before/after (threejs, ~600 modules, sourcemap = File, real-FS `Bundler`, default allocator, 8 iters/run) | Mode (transform behavior) | Metric | Before (`f6653cb7b^`) | After (`f6653cb7b`) | Δ | | ------------------------------------------------ | ------------- | --------------------- | ------------------- | --------------------- | | **omitted** (changed code, no map) | peak RSS | ~97–104 MiB | **~73 MiB** | **−25–30 MiB (~28%)** | | | time (median) | ~60–62 ms | ~52–58 ms | ~8–12% faster | | **null** (changed code, `map: null`) | peak RSS | ~97 MiB | **~80 MiB** | **−16 MiB (~17%)** | | | time (median) | ~62 ms | ~57 ms | ~8% faster | | **none** (no map-omitting transform) — *control* | peak RSS | ~80 MiB | ~78–80 MiB | within noise | | | time | ~56 ms | ~56 ms | within noise | Numbers were stable across 3 repeat runs each. **Key signal:** in the *before* build, the map-omitting transform inflates peak RSS from the ~80 MiB baseline (the `none` control) up to ~97–104 MiB — that ~17–24 MiB *is* the intermediate sourcemaps. In the *after* build, `omitted` drops back to ~73 MiB (at/below baseline): the overhead is eliminated. The `none` control is unchanged, confirming the optimization is correctly scoped and doesn't regress ordinary builds. </details> close #5062
704125e to
31ad110
Compare
Adds some tests for `render_ecma_module` which was modified in #9599.
> [!IMPORTANT] > **This is a minor release.** Two changes alter default behavior compared to `1.0.3`. Please read this section before upgrading. Everything else is additive (new features, fixes, deps). ##⚠️ Notable behavior changes ### 1. `experimental.lazyBarrel` is now enabled by default (#9632) **What changed.** `experimental.lazyBarrel` now defaults to `true`. When a barrel module is recognized as side-effect-free, Rolldown skips compiling the re-exported modules that are never actually used. **Impact.** For codebases with large barrel files (component libraries such as Ant Design, `@mui/icons-material`, etc.) this is a meaningful build-time speedup, and for the vast majority of projects the emitted output is unchanged. In rare cases where a barrel is *incorrectly* treated as side-effect-free, the optimization could drop a module that was being relied on for its side effects. **How to opt out (backward compatible).** ```js // rolldown.config.js export default { experimental: { lazyBarrel: false }, } ``` > Note: this opt-out flag is planned to be removed in a future release. If you have a case where you must turn it off, please open an issue so we can fix the underlying detection instead. --- ### 2. `tsconfig` project-reference resolution now aligns with TypeScript Upgrading `oxc_resolver` (`11.19.1` → `11.20.0` in #9549, then `→ 11.21.0` in #9634) changes how a *solution-style* `tsconfig.json` (one that only lists `references` and delegates the real settings to `tsconfig.app.json` / `tsconfig.node.json`, as Vite scaffolds) is resolved, bringing it **in line with how TypeScript (`tsc`) itself behaves**: - **Reference match priority** (oxc-resolver [#1151](oxc-project/oxc-resolver#1151)): when the root has `references`, a referenced project that includes the file now **takes precedence over the root**, instead of the root matching it first (this is what TypeScript already does). So that project's `compilerOptions.paths` now apply. - **`allowJs`** (oxc-resolver [#1198](oxc-project/oxc-resolver#1198)): whether a `.js`/`.jsx`/`.mjs`/`.cjs` file is included is now decided by **each referenced project's own** `allowJs`, not the root's (again matching TypeScript). So `tsconfig.app.json` with `allowJs: true` + `paths` now resolves aliases for `.js` files even when the root doesn't set `allowJs`. For most projects this is a fix (the standard Vite `paths` aliases now resolve, closes #8468), but it **is** a behavior change if you relied on the previous behavior, where the root's `paths` / `allowJs` took precedence. **If you relied on the old "root wins" behavior.** There is no exact toggle back, because the old behavior was the bug being fixed. The recommended path is to align your config with TypeScript: declare the `paths` / `allowJs` on the referenced project that actually owns the files. If you must keep the old precedence while still using `references`: a referenced project's match wins, and **the first matching `references` entry takes priority** (the root is only a fallback when no reference claims the file). So extract the old root settings into their own config and list it **first**: ```jsonc // tsconfig.json (solution root) { "files": [], "references": [ { "path": "./tsconfig.base.json" }, // old root paths/allowJs — listed first, so it wins { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] } ``` `tsconfig.base.json` should carry the `paths` you previously declared on the root, plus `allowJs: true` if it needs to claim `.js` files (the extension is checked against each config's own `allowJs`). With no `include`, it defaults to `**/*` under its directory and claims every file first. Alternatively, bypass reference resolution entirely by pointing the top-level `tsconfig` option at a single config: `export default { tsconfig: './tsconfig.app.json' }`. --- ## [1.1.0] - 2026-06-03 ### 🚀 Features - enable `experimental.lazyBarrel` by default (#9632) by @shulaoda - `import.meta.glob` support `caseSensitive` option (#9594) by @btea - add `SOURCEMAP_BROKEN` warning for renderChunk hook (#9601) by @sapphi-red - add `SOURCEMAP_BROKEN` warning for transform hook (#9600) by @sapphi-red - add `@__NO_SIDE_EFFECTS__` hint for invalid `@__PURE__` before function declarations (#9505) by @Copilot - code-splitting: support group-local `includeDependenciesRecursively` (#9587) by @hyf0 ### 🐛 Bug Fixes - report TSCONFIG_ERROR instead of UNHANDLEABLE_ERROR for a missing tsconfig file (#9633) by @shulaoda - browser: add missing exports and ensure consistency with `rolldown` package (#9629) by @sapphi-red - should build test-dev-server when test-node (#9610) by @situ2001 - chunk-optimizer: refuse asymmetric merge for cyclic dynamic entries (#9320) (#9322) by @aminpaks - dev: handle the remaining errors in dev (#9570) by @h-a-n-a - handle slash-normalized ids with preserveModulesRoot (#9595) by @IWANABETHATGUY - json: preserve .default access on JSON default imports (#9568) by @IWANABETHATGUY - testing: remove unintended trigger_full_build from test harness (#9573) by @hyf0 ### 🚜 Refactor - js-regex: use regress native replace/replace_all (#9607) by @IWANABETHATGUY - remove never-constructed `ImportStatus` variants (#9606) by @Boshen ### 📚 Documentation - clarify that `RolldownBuild::close` method should be called in most cases (#9619) by @sapphi-red ### ⚡ Performance - avoid unnecessary intermediate sourcemaps (#9599) by @sapphi-red ### 🧪 Testing - add unit test for collapsing module sourcemap (#9626) by @sapphi-red - cover vite-alias regex capture-group expansion (#9602) (#9608) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - deps: update oxc_resolver to 11.21.0 (#9634) by @shulaoda - update invalid option diagnostic link to point to Rolldown docs (#9631) by @sapphi-red - deps: update vite+ to v0.1.24 (#9628) by @renovate[bot] - deps: update oxc resolver to v11.20.0 (#9549) by @renovate[bot] - deps: update dependency vite-plus to v0.1.24 (#9470) by @renovate[bot] - deps: update npm packages (#9614) by @renovate[bot] - deps: upgrade oxc to 0.134.0 (#9625) by @shulaoda - deps: update crate-ci/typos action to v1.47.0 (#9620) by @renovate[bot] - deps: update rollup submodule for tests to v4.61.0 (#9623) by @rolldown-guard[bot] - deps: update github actions (#9613) by @renovate[bot] - deps: update pnpm to v11.4.0 (#9616) by @renovate[bot] - deps: update rust crates (#9615) by @renovate[bot] - deps: update test262 submodule for tests (#9624) by @rolldown-guard[bot] - deps: update dependency @napi-rs/cli to v3.7.0 (#9588) by @renovate[bot] - deps: update dependency rust to v1.96.0 (#9596) by @renovate[bot] - re-enable WASI testing with proper infrastructure (#9397) by @Boshen ### ❤️ New Contributors * @aminpaks made their first contribution in [#9322](#9322) Co-authored-by: shulaoda <[email protected]>

Claude gave me the following result with #9621:
Details
Empirical before/after (threejs, ~600 modules, sourcemap = File, real-FS
Bundler, default allocator, 8 iters/run)f6653cb7b^)f6653cb7b)map: null)Numbers were stable across 3 repeat runs each.
Key signal: in the before build, the map-omitting transform inflates peak RSS from the ~80 MiB baseline (the
nonecontrol) up to ~97–104 MiB — that ~17–24 MiB is the intermediate sourcemaps. In the after build,omitteddrops back to ~73 MiB (at/below baseline): the overhead is eliminated. Thenonecontrol is unchanged, confirming the optimization is correctly scoped and doesn't regress ordinary builds.close #5062