Skip to content

fix(rsc): preserve exported client reference subpaths#1257

Open
NathanDrake2406 wants to merge 1 commit into
cloudflare:mainfrom
NathanDrake2406:nathan/client-reference-dedup-subpaths
Open

fix(rsc): preserve exported client reference subpaths#1257
NathanDrake2406 wants to merge 1 commit into
cloudflare:mainfrom
NathanDrake2406:nathan/client-reference-dedup-subpaths

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Overview

Field Details
Goal Preserve package subpath identity in the experimental client reference dedupe plugin.
Core change Resolve absolute package files back to the best bare package specifier using package metadata instead of always using the package root.
Boundary Dev-only RSC client references imported through the Vite RSC client-in-server package proxy.
Primary files packages/vinext/src/plugins/client-reference-dedup.ts, tests/client-reference-dedup.test.ts
Expected impact Packages with exported client subpaths keep the right bindings while private internals keep the existing root-package dedupe behavior.

Why

Client reference identity has to preserve the module boundary that the framework means to load. Collapsing every package file to the package root only works when the root barrel re-exports the same binding. For exported subpaths, that changes the import contract and can make valid package subpath references fail or bind to the wrong module.

Area Principle / invariant What this PR changes
Next.js parity Client references are keyed by resolved resource identity, not by package root alone. vinext now preserves exported package subpaths where package metadata proves the subpath is public.
Vite RSC dev proxy The proxy re-exports the resolved package file in dev. vinext still redirects through a bare specifier for optimization, but no longer always chooses the root package.
Existing dedupe behavior Private package internals should keep sharing the root package module when deep imports are blocked. Package exports that do not expose the internal file still fall back to the package root.

What changed

Scenario Before After
Exported exact subpath /node_modules/widget-lib/client/button.js became widget-lib. It becomes widget-lib/client/button.
Exported pattern subpath /node_modules/widget-lib/dist/client/button.js became widget-lib. It becomes widget-lib/client/button when exports maps ./client/* to ./dist/client/*.js.
Private internal with exports /node_modules/fake-context-lib/internal/context.js became fake-context-lib. It still becomes fake-context-lib.
Legacy package without exports Deep files fell back to the package root. Deep files use packageName/relativePath when package.json does not restrict subpaths.
Maintainer review path
  1. packages/vinext/src/plugins/client-reference-dedup.ts

    • Review extractPackageImportSpecifier first. It is the new package-boundary decision point.
    • Then review resolveId to confirm optimizeDeps.exclude is still honored for root packages and exact subpaths.
    • Finally review load to confirm generated virtual modules still re-export through the selected bare specifier.
  2. tests/client-reference-dedup.test.ts

    • Review exported exact subpath, scoped package, export pattern, blocked private internal, and legacy deep import cases.
    • Review the plugin-level resolveId assertions that lock the virtual module ID produced for public subpaths and private internals.
Validation
  • vp test run tests/client-reference-dedup.test.ts
  • vp test run tests/app-router.test.ts -t "renders context provider/consumer from package with internal 'use client' submodule"
  • vp check
  • Commit hook also ran formatting, lint/type checks, and knip before creating the commit.
Risk / compatibility
  • Public API: no stable public API change. The plugin remains experimental and opt-in.
  • Runtime: dev server only. Production still uses the SSR manifest path.
  • Compatibility: packages with explicit exports now get subpath-preserving behavior for public files. Packages with private internals under an exports map keep root fallback to avoid reintroducing duplicate React context modules.
  • Residual risk: this is a reverse mapping from resolved files to package export keys. It supports exact exports, conditional exports, arrays, and single-wildcard patterns, but it is intentionally not a full Node resolver.
Non-goals
  • This does not replace the Vite RSC dev proxy.
  • This does not change production client reference manifest behavior.
  • This does not attempt to infer non-exported package internals back to arbitrary original import specifiers.

References

Reference Why it matters
Next.js flight manifest uses the resolved resource as the client module key Shows Next.js keeps client reference identity at resource granularity instead of blindly normalizing to package root.
Next.js SSR/RSC mappings retain the same client module entry Confirms the resource-keyed client entry is what server mappings reuse.
Next.js rsc-basic manifest key test Existing upstream coverage checks client reference manifest key shape and reinforces that reference identity is a framework contract.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 16, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1257

commit: 717e266

@NathanDrake2406 NathanDrake2406 force-pushed the nathan/client-reference-dedup-subpaths branch from 7298896 to e8146be Compare May 16, 2026 07:26
Client reference dedupe currently rewrites every package file imported through the RSC package proxy back to the package root. That loses subpath identity when a package exposes client modules through an exported subpath instead of its root barrel.

The plugin now derives a bare import specifier from package metadata, preserving exact and pattern exports while keeping private internals on the package root so existing context dedupe behavior stays intact.

Regression coverage exercises exported subpaths, scoped packages, export patterns, private internals, and legacy deep imports.
@NathanDrake2406 NathanDrake2406 force-pushed the nathan/client-reference-dedup-subpaths branch from e8146be to 717e266 Compare May 16, 2026 07:28
@NathanDrake2406 NathanDrake2406 marked this pull request as ready for review May 16, 2026 07:29
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 16, 2026

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

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.

2 participants