feat: state-preserving HMR and hot data revalidation in classic dev#93
Conversation
Ports the classic-mode (non-RSC) dev HMR feature from the RR8-compat branch onto main: route module updates in development now apply React Refresh registration and in-place route patching instead of forcing a full reload, and server code changes bump an HDR revision file that signals hot data revalidation to the client. Degrades to the previous full-reload behavior when @rsbuild/plugin-react is not present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b9a4a476a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| previous.web.manifestsByEntryName, | ||
| manifestsByEntryName | ||
| manifestsByEntryName, | ||
| !clientPatchesRouteMetadata |
There was a problem hiding this comment.
Don't suppress reloads for unloaded routes
When a lazily loaded/unvisited route changes only HMR-patchable flags such as hasLoader or hasClientLoader, its route client entry is not in the browser module graph, so the new HMR runtime never runs scheduleReactRouterRouteUpdate for that route. Passing false here suppresses the previous full reload globally, leaving window.__reactRouterManifest with stale metadata until a manual reload; a later navigation can then skip a newly added loader or fetch one that was removed. Keep the full reload for metadata changes that cannot be patched by an already-loaded route module.
Useful? React with 👍 / 👎.
| }) | ||
| : undefined; | ||
| devHdrSignal?.ensure(); | ||
| const devHmrEnabled = devHmrRefreshRuntimePath !== undefined; |
There was a problem hiding this comment.
Gate route HMR on enabled Fast Refresh
This enables the new self-accepting route HMR whenever the package can resolve @rsbuild/plugin-react, even for configs that install the plugin but disable Fast Refresh (for example the federation examples use pluginReact({ fastRefresh: false, swcReactOptions: { refresh: false } })). In that case the React Refresh plugin/transform is not installed, but route updates are still self-accepted and applyRouteModuleUpdate preserves the old component identity, so component-only edits can stop updating instead of falling back to a full reload. Please base this on the actual enabled React Refresh configuration rather than package resolution alone.
Useful? React with 👍 / 👎.
|
Warning Review limit reached
Next review available in: 11 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds development-only React Router HMR with React Refresh registration, route metadata updates, in-place route patching, and loader-data revalidation. Route transforms now support HMR flags, route IDs, and registration backfill for pre-lowered modules. A generated browser runtime uses HDR revision files to receive rebuild signals. Dev runtime orchestration signals qualifying node rebuilds while excluding CSS and HDR-only changes. Tests and changesets cover the new behavior. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
Benchmark ResultsCompared PR head Dev Rollup
Production Build BenchmarksRendered 7 production build benchmarks.
full Dev Fixture SummaryRendered 7 dev benchmark fixtures from the
large-355-ssr-esm Plugin Operations
synthetic-1024-ssr-esm Plugin Operations
synthetic-1024-ssr-esm-split Plugin Operations
synthetic-256-sourcemaps Plugin Operations
synthetic-256-ssr-esm Plugin Operations
synthetic-256-ssr-esm-split Plugin Operations
synthetic-48-ssr-esm Plugin Operations
Synthetic Rsbuild AppRendered 2 production build benchmarks.
Rendered 1 dev benchmark fixture from the embedded complex app.
Profile: |
85eb78a to
16460db
Compare
16460db to
59d44f8
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/dev-hmr.ts (1)
383-392: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueRevalidation navigate fallback is bypassed when
router.revalidateis absent.When
shouldRefreshRouteStateis true and revalidation is required,refreshRouteState(router)runs andshouldRevalidateis set tofalse. ButrefreshRouteStateis a no-op unlessrouter.revalidateis a function, whereasrevalidateRouteradditionally falls back to anavigate(...)replace. So on a router withoutrevalidate, loader data that should be revalidated never is, because the fallback path is skipped. This is only reachable if the data router lacksrevalidate, so please confirm whether that shape can occur in the supported React Router versions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/dev-hmr.ts` around lines 383 - 392, Ensure revalidation is not marked complete when router.revalidate is unavailable: inspect refreshRouteState and revalidateRouter, and only clear shouldRevalidate after a successful refresh that supports router.revalidate; otherwise allow revalidateRouter’s navigate replace fallback to run. Confirm compatibility with supported React Router router shapes and preserve the fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/dev-hmr.ts`:
- Around line 383-392: Ensure revalidation is not marked complete when
router.revalidate is unavailable: inspect refreshRouteState and
revalidateRouter, and only clear shouldRevalidate after a successful refresh
that supports router.revalidate; otherwise allow revalidateRouter’s navigate
replace fallback to run. Confirm compatibility with supported React Router
router shapes and preserve the fallback behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c1c66d8d-17ea-4a8b-ab1a-dbe7bb33fda5
📒 Files selected for processing (9)
src/dev-hmr.tssrc/dev-runtime-controller.tssrc/index.tssrc/prerender-build.tssrc/route-artifacts.tssrc/route-transform-tasks.tstests/dev-hmr-fast-refresh-detection.test.tstests/route-artifacts.test.tstests/setup.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- tests/route-artifacts.test.ts
- src/route-transform-tasks.ts
- src/dev-runtime-controller.ts
- src/index.ts
- src/route-artifacts.ts
Summary
Ports the classic-mode (non-RSC) dev HMR feature from the React Router 8 branch (#74) so RR7 users get it without waiting for RR8:
src/dev-hmr.tsruntime delivered as a virtual module imported by each route's client entry) instead of forcing a full reload. Component state survives edits..react-router/hdr-revision.mjsfile watched by the web compiler; the client runtime revalidates the router so loader data refreshes without a reload.$RefreshReg$registrations following react-refresh's own component-detection rules.@rsbuild/plugin-reactisn't present.Bug found & fixed during verification (also affects #74)
Live-testing surfaced an infinite rebuild loop: web-only commits reuse the node compiler's stale
modifiedFiles, and each HDR bump itself triggers a web rebuild — so after the first server-file edit the bump signal self-sustained (revision file climbed unboundedly, constant CPU burn). Reproduced identically on the source branch. Fixed by signaling once per node compilation identity; the same fix is pushed to #74. Regression test included (red-green verified).Testing
tsc --noEmitclean; fullrstestsuite 462/462.examples/default-template(RR 7.14): page 200, HMR runtime present in route bundles only, HDR revision bumps exactly once per server edit and stays stable (0 → 1, steady for 75s).createRoutesForHMR,_internalSetRoutes, etc.) exist in react-router 7.13.0 (the dependency floor) viaHydratedRouter, which the plugin's ownentry.clientuses.Includes the feature changeset (minor) and the refresh-backfill changeset (patch).
🤖 Generated with Claude Code