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 <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Niklas Mischkulnig <[email protected]>
Getting Started
Used by some of the world's largest companies, Next.js enables you to create full-stack web applications by extending the latest React features, and integrating powerful Rust-based JavaScript tooling for the fastest builds.
- Visit our Learn Next.js course to get started with Next.js.
- Visit the Next.js Showcase to see more sites built with Next.js.
Documentation
Visit https://nextjs.org/docs to view the full documentation.
Community
The Next.js community can be found on GitHub Discussions where you can ask questions, voice ideas, and share your projects with other people.
To chat with other community members you can join the Next.js Discord server.
Do note that our Code of Conduct applies to all Next.js community channels. Users are highly encouraged to read and adhere to it to avoid repercussions.
Contributing
Contributions to Next.js are welcome and highly appreciated. However, before you jump right into it, we would like you to review our Contribution Guidelines to make sure you have a smooth experience contributing to Next.js.
Good First Issues:
We have a list of good first issues that contain bugs that have a relatively limited scope. This is a great place for newcomers and beginners alike to get started, gain experience, and get familiar with our contribution process.
Security
If you believe you have found a security vulnerability in Next.js, we encourage you to responsibly disclose this and NOT open a public issue.
To participate in our Open Source Software Bug Bounty program, please email [email protected]. We will add you to the program and provide further instructions for submitting your report.