Skip to content

fix: emit namespace declaration for empty modules in manual chunks#8993

Merged
hyf0 merged 2 commits intorolldown:mainfrom
privatenumber:fix/empty-module-manual-chunks-export
Apr 11, 2026
Merged

fix: emit namespace declaration for empty modules in manual chunks#8993
hyf0 merged 2 commits intorolldown:mainfrom
privatenumber:fix/empty-module-manual-chunks-export

Conversation

@privatenumber
Copy link
Copy Markdown
Contributor

@privatenumber privatenumber commented Apr 2, 2026

Summary

  • Fix undefined export binding when an empty module is dynamically imported and routed to a named chunk via manualChunks
  • Root cause: finalized_module_namespace_ref_usage gated on ExportsKind::Esm before checking SimulateFacadeChunk, so empty modules (ExportsKind::None) had their namespace removed despite the chunk optimizer marking it as needed

Problem

When all three conditions are met:

  1. Empty module (0 bytes, e.g. CSS extracted by Vite)
  2. manualChunks routing the module to a named chunk
  3. Non-top-level dynamic import (inside a function — top-level await import() gets inlined)

The output contained export { empty_exports as t } without ever defining empty_exports, causing a SyntaxError at runtime.

Fix

Check SimulateFacadeChunk before the ExportsKind gate in finalized_module_namespace_ref_usage, since the chunk optimizer's decision is authoritative regardless of exports kind.

Test plan

  • Added regression test packages/rolldown/tests/fixtures/issues/empty-module-manual-chunks/
  • 721 Node.js tests pass
  • 1662 Rust tests pass (1 pre-existing test262 submodule failure)

@privatenumber privatenumber force-pushed the fix/empty-module-manual-chunks-export branch 3 times, most recently from f45bad2 to af624a3 Compare April 2, 2026 20:56
@privatenumber privatenumber marked this pull request as ready for review April 2, 2026 20:57
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 2, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing privatenumber:fix/empty-module-manual-chunks-export (14b8217) with main (7266264)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@hyf0 hyf0 self-assigned this Apr 3, 2026
When an empty module was dynamically imported and routed to a named
chunk via manualChunks, the output referenced an undefined variable
(e.g. `export { empty_exports as t }` without defining `empty_exports`).

Root cause: `finalized_module_namespace_ref_usage` gated on
`ExportsKind::Esm` before checking `SimulateFacadeChunk`. Empty modules
have `ExportsKind::None` (dynamic imports with code splitting don't
upgrade None to Esm), so the check short-circuited and removed the
namespace ref from `used_symbol_refs` — even though the chunk optimizer
had explicitly marked it as needed via `SimulateFacadeChunk`.

Fix: check `SimulateFacadeChunk` before the `ExportsKind` gate, since
the chunk optimizer's decision is authoritative regardless of exports
kind.
@hyf0 hyf0 force-pushed the fix/empty-module-manual-chunks-export branch from af624a3 to c69f573 Compare April 9, 2026 07:07
@hyf0
Copy link
Copy Markdown
Member

hyf0 commented Apr 9, 2026

@IWANABETHATGUY Could you check this one? The title mention manual chunks but I saw the fix isn't quite related to manual chunks.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 9, 2026

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 14b8217
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69da4e722c88d10008c61c25
😎 Deploy Preview https://deploy-preview-8993--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hyf0
Copy link
Copy Markdown
Member

hyf0 commented Apr 11, 2026

Test failed without the fix. Change is small. LGTM.

@hyf0
Copy link
Copy Markdown
Member

hyf0 commented Apr 11, 2026

Thank you!

@hyf0 hyf0 merged commit 6a5f619 into rolldown:main Apr 11, 2026
32 checks passed
This was referenced Apr 15, 2026
shulaoda added a commit that referenced this pull request Apr 16, 2026
## [1.0.0-rc.16] - 2026-04-16

### 🚀 Features

- const enum cross-module inlining support (#8796) by @Dunqing
- implement module tagging system for code splitting (#9045) by @hyf0

### 🐛 Bug Fixes

- rolldown_plugin_vite_manifest: handle duplicate chunk names for CSS entries (#9059) by @sapphi-red
- improve error message for invalid return values in function options (#9125) by @shulaoda
- await async export-star init wrappers (#9101) by @thezzisu
- never panic during diagnostic emission (#9091) by @IWANABETHATGUY
- include array rest pattern in binding_identifiers (#9112) by @IWANABETHATGUY
- rolldown: set worker thread count with ROLLDOWN_WORKER_THREADS (#9086) by @fpotter
- rolldown_plugin_lazy_compilation: escape request ID in proxy modules (#9102) by @h-a-n-a
- treat namespace member access as side-effect-free (#9099) by @IWANABETHATGUY
- relax overly conservative side-effect leak check in chunk optimizer (#9085) by @IWANABETHATGUY
- runtime: release `cb` reference after `__commonJS` factory initialization (#9067) by @hyf0-agent
- `@__NO_SIDE_EFFECTS__` wrapper should not remove dynamic imports (#9075) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use POSIX path join/normalize for glob resolution (#9077) by @shulaoda
- emit REQUIRE_TLA error when require() loads a module with top-level await (#9071) by @jaehafe
- emit namespace declaration for empty modules in manual chunks (#8993) by @privatenumber
- rolldown_plugin_vite_import_glob: keep common base on path segment boundary (#9070) by @shulaoda
- prevent circular runtime helper imports during facade elimination (#8989) (#9057) by @IWANABETHATGUY
- correct circular dependency check in facade elimination (#9047) by @h-a-n-a
- docs: correct dead link in CodeSplittingGroup.tags JSDoc (#9051) by @hyf0
- emit DUPLICATE_SHEBANG warning when banner contains shebang (#9026) by @IWANABETHATGUY

### 🚜 Refactor

- use semantic reference flags for member write detection (#9060) by @Dunqing
- extract UsedSymbolRefs newtype wrapper (#9130) by @IWANABETHATGUY
- dedupe await wrapping in export-star init emit (#9119) by @IWANABETHATGUY
- calculate side-effect-free function symbols on demand (#9120) by @IWANABETHATGUY
- extract duplicated top-level await handling into shared helper (#9087) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use split_first for get_common_base (#9069) by @shulaoda
- simplify ESM init deduplication with idiomatic insert check (#9044) by @IWANABETHATGUY

### 📚 Documentation

- document runtime module placement strategy in code-splitting design (#9062) by @IWANABETHATGUY
- clarify `options` hook behavior difference with Rollup in watch mode (#9053) by @sapphi-red
- meta/design: introduce module tags (#9017) by @hyf0

### ⚡ Performance

- convert `generate_transitive_esm_init` to iterative (#9046) by @IWANABETHATGUY

### 🧪 Testing

- merge strict/non_strict test variants using configVariants (#9089) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- disable Renovate auto-updates for oxc packages (#9129) by @IWANABETHATGUY
- upgrade oxc@0.126.0 (#9127) by @Dunqing
- deps: update napi to v3.8.5 (#9126) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.6.2 (#9123) by @renovate[bot]
- move lazy-compilation design doc (#9117) by @h-a-n-a
- deps: update dependency vite-plus to v0.1.18 (#9118) by @renovate[bot]
- deps: update dependency vite-plus to v0.1.17 (#9113) by @renovate[bot]
- deps: update oxc to v0.125.0 (#9094) by @renovate[bot]
- deps: update dependency follow-redirects to v1.16.0 [security] (#9103) by @renovate[bot]
- deps: update test262 submodule for tests (#9097) by @sapphi-red
- deps: update crate-ci/typos action to v1.45.1 (#9096) by @renovate[bot]
- deps: update rust crates (#9081) by @renovate[bot]
- deps: update npm packages (#9080) by @renovate[bot]
- remove outdated TODO in determine_module_exports_kind (#9072) by @jaehafe
- rust/test: support `extendedTests: false` shorthand in test config (#9050) by @hyf0
- ci: extract shared infra-changes anchor in path filters (#9054) by @hyf0
- add docs build check to catch dead links in PRs (#9052) by @hyf0

### ❤️ New Contributors

* @thezzisu made their first contribution in [#9101](#9101)
* @fpotter made their first contribution in [#9086](#9086)
* @jaehafe made their first contribution in [#9071](#9071)
* @privatenumber made their first contribution in [#8993](#8993)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants