Skip to content

fix(ssr): register export * getters next to earlier same-source import to fix cyclic facade namespace#22493

Open
schiller-manuel wants to merge 1 commit into
vitejs:mainfrom
schiller-manuel:fix-ssr-cold
Open

fix(ssr): register export * getters next to earlier same-source import to fix cyclic facade namespace#22493
schiller-manuel wants to merge 1 commit into
vitejs:mainfrom
schiller-manuel:fix-ssr-cold

Conversation

@schiller-manuel

Copy link
Copy Markdown
Contributor

Fixes #22491.

Fix

Transform-level change in ssrTransform.ts. When export * from 'x' reuses a source already imported earlier in the same module:

  • Track earlier import ids by source (sourceToImportMap).
  • Reuse the existing import id for the export *.
  • Emit __vite_ssr_exportAll__(existingImportId) immediately after the earlier import (via s.appendLeft(existingImport.end, ...)).
  • Remove the later duplicate export * statement.
  • Skip the pass-2 appendLeft for those nodes via an earlyExportAllDeclarations Set to avoid double-emission.
  • Negative-guarded by !node.exported, so export * as ns from 'x' is left untouched.
    This restores link-time export * semantics for the common facade pattern: the wildcard getters are registered before any subsequent cycle-triggering import awaits.

Tests

  • runtime/__tests__/fixtures/cyclic-reexport-facade/* — fixtures mirroring the reduced repro in the issue.
  • server-runtime.spec.ts > resolves export star bindings through a cyclic facade — fails on main with TypeError: createThing is not a function, passes with this patch.
  • ssrTransform.spec.ts — three inline-snapshot tests

Scope (in this PR)

Only export * from 'x' where 'x' was already imported earlier in the same module (import … or export … from). The reported real-world case (TanStack Start) matches this shape.

Out of scope (can extend if maintainers want)

These are adjacent cyclic-export * deviations from spec link-time semantics that this PR does not address, to keep the diff minimal:

1. No prior same-source import

// facade.js
export { client } from './client.js'  // triggers cycle into facade
export * from './core.js'             // no earlier import of ./core.js
// client.js -> ... -> middleware.js
import { createThing } from './facade.js'  // sees partial namespace

Both the hoisted __vite_ssr_import__("./core.js") and its __vite_ssr_exportAll__ end up after the ./client.js import in the hoisted block, so the cycle observes the facade before core's wildcard is registered. A fix would hoist plain export * imports ahead of any cycle-capable preceding import, but that requires broader reordering.

2. Cycle during the anchor import's own await

// facade.js
import { a } from './x.js'   // anchor; ./x.js cycles back to ./facade.js
export * from './x.js'
// x.js
import { b } from './facade.js'  // cycle returns here, before exportAll(x) runs

The patched __vite_ssr_exportAll__(x) is appended after the anchor import resolves. While we're still awaiting ./x.js, the facade namespace does not yet have the wildcard getters. A fix would need to register exportAll before awaiting the anchor — but at that point the imported module reference does not exist yet, so it requires a different runtime shape (e.g. a deferred-getter proxy).

3. Multiple export * from the same anchored source

// facade.js
import { a } from './x.js'
export * from './x.js'
export * from './x.js'

Both deduped export * statements emit __vite_ssr_exportAll__(__vite_ssr_import_0__) at the same anchor — idempotent at runtime, but redundant. Trivially dedupable with a per-anchor Set<sourceValue> if desired.

4. export * as ns is left at its textual position

// facade.js
import { a } from './x.js'
export * as ns from './x.js'   // creates named export `ns`, kept in place

A more general fix could also hoist the ns named-export registration next to the anchor import, but export * as ns is a namespace snapshot with different semantics from export * and is not in the reported failure mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat: ssr p2-edge-case Bug, but has workaround or limited in scope (priority)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSR module runner exposes incomplete export * facade namespace in cold circular imports

2 participants