next.js
dab281a3 - Fix `export * as X from './self'` in scope-hoisted modules (#93192)

Commit
12 days ago
Fix `export * as X from './self'` in scope-hoisted modules (#93192) ### What? Scope-hoisted execution of a module that re-exports its own namespace (`export * as X from './self'`) returned the wrong namespace for named imports of the module. Given: ```js // data.js import * as Self from './data' export function foo() { return 'foo' } export function bar() { return 'bar' } export function fooViaSelf() { return Self.foo() } // Self.foo undefined export * as Data from './data' ``` Any binding imported from `./data` that relied on the module's own namespace (e.g. `Self.foo`, `Data.foo`, `Data.Data.foo`) was `undefined` at runtime. Without scope hoisting the same code worked correctly. This PR fixes the bug and adds execution tests covering self-namespace re-exports — with and without scope hoisting — including chained re-exports (`Data.Data.foo`, `Data.Data.Data.bar`). ### Why? For an access like `Self.Data` where `Self = import * as Self from './data'` and `Data` is exposed through `export * as Data from './data'`, the namespace-member-access optimization rewrites the reference to a named import resolving to a synthesized rename module (`./data <export * as Data>`). `ReferencedAsset::get_ident_inner` then recurses through the rename's `EsmExport::ImportedNamespace("Data")` and returns a `namespace_ident` derived from the inner module's chunk-item id — but `EsmAssetReference::code_generation` independently took `id = referenced_asset.chunk_item_id` and emitted ```js var <inner-data-ident> = __turbopack_context__.i("<rename-id>"); ``` so the variable named like `ns(data.js)` actually held `ns(rename) = { Data: ns(data.js) }`. The non-optimized `import * as Self` uses the same mangled name and sees the rename's namespace, so `Self.foo()` evaluates to `undefined`. ### How? Keep the variable name and the `.i(...)` argument consistent by moving the "what to import" decision onto the ident itself: - `ReferencedAssetIdent::Module` gains an `import_source: ImportSource` field that describes what to import to populate the namespace variable. - `ImportSource` is an enum: - `Module { asset }` — carries a reference to the final module in any re-export chain, from which the chunk-item id is lazily computed. - `External { request, ty }` — carries everything needed to emit `__turbopack_external_import` / `__turbopack_external_require`. - The `namespace_ident` is cached in `ReferencedAssetIdent::Module` at resolution time (computed via `ImportSource::get_namespace_ident()`) so downstream sync visitors can read it without re-entering the async layer. - `ReferencedAsset::get_ident` / `get_ident_inner` populate the field. For in-group re-exports the inner module propagates up; for external references the `External` variant is used. - `EsmAssetReference::code_generation` destructures `ReferencedAssetIdent::Module { namespace_ident, ctxt, import_source, .. }` and dispatches purely on `import_source`; it no longer reads `referenced_asset` after the `get_ident` call. The hoisted-statement dedup key still uses the directly-referenced asset's id, so two references that happen to resolve to the same inner module via different paths (e.g. direct vs. through a rename) still emit separate `var` declarations for AST merging to rename. - ESM-external gating (`__turbopack_external_import` vs. `__turbopack_external_require`) stays where it was — the emit site reads `self.import_externals` from the surrounding `EsmAssetReference`, so `ImportSource::External` does not carry it. No additional `MergeableModuleExposure` or `additional_ids` changes are needed. The rename module is never referenced at runtime; no snapshot files change. ### Tests - `turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/self-reexport-star/` — scope-hoisted execution test covering self-namespace re-exports, nested access (`Data.Data.foo`, `Data.Data.Data.bar`), chained re-exports through another module, and namespace key enumeration. - `turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/self-reexport-star-no-hoisting/` — same test cases, run with scope hoisting disabled, reusing the fixtures from the sibling directory. Verified by `cargo test --test execution` (213 passed) and `cargo test --test snapshot` (89 passed) in `turbopack-tests`. No snapshot files are modified. Closes NEXT- Fixes # <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Author
Parents
Loading