Repository navigation
test(router): guard Fast Refresh for co-located non-"use client" file (vite-plugin-react#1248) - #574
Merged
Merged
Conversation
Pins that editing a non-"use client" file present in both the rsc and client module graphs (a server export plus a client-rendered component, consumed by a "use client" island) keeps client Fast Refresh: the marker text updates while seeded island state survives, with no full reload. This is the Rango-safe analog of vitejs/vite-plugin-react#1248. The island's "use client" boundary makes isInsideClientBoundary true, so plugin-rsc's client hotUpdate guard never fires for the shared file. Adds a dedicated /colocated-fr fixture route, a dev-only HMR test in client-component-hmr.test.ts, and dev plus production render coverage.
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
@vitejs/plugin-rsc(which Rango builds on) has a dev-onlyhotUpdateguard that, for a file present in the rsc module graph but not behind a"use client"boundary, returns[]and drops client Fast Refresh. Upstream PR vitejs/vite-plugin-react#1248 ("keep client HMR for client modules co-located with rsc-graph code", currently open/unmerged) narrows that guard so a genuinely client-rendered component co-located with server-graph code keeps Fast Refresh.Does Rango need that fix? No — Rango's client module graph is only ever reached through
"use client"client references (Flight hydration; the browser entry imports only framework internals), soisInsideClientBoundaryis always true for those modules and the guard never fires. But that invariant was untested: no e2e exercised a non-"use client"file living in both graphs. This PR closes the gap so a futureplugin-rscbump can't silently regress Fast Refresh for the co-located pattern.Consumer-observable framing: a consumer who co-locates a server export and a client-rendered component in one non-
"use client"file, consumed by a"use client"island, edits that file in dev and expects Fast Refresh — state preserved, no full reload. This PR pins that.The pattern this pins (consumer seat)
Editing
marker-baseline->marker-editedin dev: the marker updates and the island counter stays at its seeded value (Fast Refresh, not a remount/reload).Semantics
"use client"?"use client"island (client reference)isInsideClientBoundarytrue"use client"componentclient-component-hmrplugin cedes rsc/ssr)Rango only ever produces row 2 (boundary present), never row 3 (the CSR-island shape #1248 fixes), because the browser entry hydrates Flight and never statically imports route/component modules.
Changes
Test-only + test-app fixtures; no router src, no public API, no bundle impact.
e2e/test-app/src/components/ColocatedFrShared.tsx(new) — dual-graph, non-"use client", all-component exports.e2e/test-app/src/components/ColocatedFrIsland.tsx(new) —"use client"island + counter.e2e/test-app/src/urls/colocated-fast-refresh.tsx(new) — server route at/colocated-fr.e2e/test-app/src/urls.tsx,e2e/test-app/src/router.named-routes.gen.ts— wire + regenerate the route.e2e/colocated-fast-refresh.test.ts(new) — render/hydration in dev + production.e2e/client-component-hmr.test.ts— newcolocated-fast-refresh-hmrdescribe (dev-only HMR).Test plan
pnpm run typecheck— 12/12 packagespnpm run test:unit:all(unit + RSC, all packages) — greenpnpm run lint(oxlint) — 0 warnings, 0 errorspnpm run format(oxfmt) — cleanpnpm check:e2e-bucketing— OKcolocated-fast-refresh (dev)+(production)render — passcolocated-fast-refresh-hmr(hmr-client) — pass (marker updates, island counter survives, no reload)Contract pinned: the HMR test pins that editing a non-
"use client"dual-graph file keeps client Fast Refresh (seeded state preserved); the render twin pins that the same fixture builds + hydrates in a production build.Notes
colocated-fast-refresh.test.ts (production)). CI runs the HMR test under thehmr-clientmatrix job (notest.skip(CI)) — it is the same component-text-edit category as the existingclient-component-hmrtest, not the watcher-flaky local-only bucket (route-types/basename/prerender).plugin-rsc ^0.5.26(without it). This PR adopts nothing — it guards Rango's existing, correct behavior. If refactor(router): wire protocol module for partial-request parameters (S16) #1248 later lands in aplugin-rscbump it is orthogonal to Rango (different environment branch) and carries no regression risk for this path.