Skip to content

[codex] support React Router 8 compatibility#74

Open
ScriptedAlchemy wants to merge 89 commits into
mainfrom
codex/react-router-8-compat-v2
Open

[codex] support React Router 8 compatibility#74
ScriptedAlchemy wants to merge 89 commits into
mainfrom
codex/react-router-8-compat-v2

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Collaborator

Summary

Restores the closed React Router 8 compatibility work from PR #67 onto current main, keeping the newer lazy compilation/dev background resource path from PR #54.

  • adds React Router 8 and RSC plugin support, exports, templates, examples, and framework compatibility coverage
  • keeps classic mode route transforms/lazy prewarm behavior on the current main implementation
  • makes dev background route resources tolerate RSC mode where there is no classic route transform executor
  • intentionally leaves the dynamic CSS parity branch closed per follow-up scope

Validation

  • CI=true pnpm install --lockfile-only
  • pnpm build
  • pnpm test:package-interop
  • pnpm test:core
  • pnpm --filter './examples/{react-router-8,rsc-mode}' typecheck\n- pnpm --filter './examples/{react-router-8,rsc-mode}' test:e2e\n\n## Notes\n\nThe workspace still reports existing engine warnings for several examples under Node 24 and peer warnings from mixed React Router fixture versions; the validation commands above completed successfully.

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/rsbuild-plugin-react-router@5d2cbc2

commit: 5d2cbc2

Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Fixed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Fixed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Fixed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Fixed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Fixed
Comment thread src/rsc-route-transforms.ts
Comment thread src/rsc-route-transforms.ts
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Compared PR head 5b65796 against base 9018ef6.

Dev Rollup

Group Fixtures Base total Head total Delta Base ready Head ready Ready delta Base routes Head routes Route delta Base update/HMR Head update/HMR Update delta Speedup
All dev fixtures 7 31.73s 43.86s +38.2% 21.13s 23.81s +12.7% 4.36s 3.83s -12.2% 3.62s 3.26s -10.0% 0.72x
Large app 1 14.88s 19.75s +32.7% 9.01s 10.61s +17.8% 2.21s 1.95s -11.7% 2.00s 1.80s -10.3% 0.75x
Standard fixtures 6 16.85s 24.12s +43.1% 12.12s 13.20s +8.9% 2.16s 1.88s -12.8% 1.62s 1.46s -9.7% 0.70x

Production Build Benchmarks

Rendered 7 production build benchmarks.

Benchmark Runs Base total Head total Delta Head mean Head p95 Speedup Head RSS p95
large-355-ssr-esm 5 9.17s 10.53s +14.9% 10.51s 10.59s 0.87x 1590 MB
synthetic-1024-ssr-esm 5 4.27s 4.40s +2.9% 4.43s 4.64s 0.97x 667 MB
synthetic-1024-ssr-esm-split 5 5.85s 6.34s +8.4% 6.38s 6.61s 0.92x 845 MB
synthetic-256-sourcemaps 10 2.33s 2.26s -2.8% 2.28s 2.47s 1.03x 452 MB
synthetic-256-ssr-esm 10 2.13s 2.13s +0.2% 2.14s 2.29s 1.00x 433 MB
synthetic-256-ssr-esm-split 10 2.54s 2.65s +4.5% 2.67s 2.83s 0.96x 480 MB
synthetic-48-ssr-esm 10 1.41s 1.37s -2.6% 1.40s 1.67s 1.03x 324 MB

full Dev Fixture Summary

Rendered 7 dev benchmark fixtures from the full profile.

Benchmark Runs Base total Head total Delta Base ready Head ready Base routes Head routes Base update/HMR Head update/HMR Update delta Head mean Head p95 Speedup Head RSS p95
large-355-ssr-esm 5 14.88s 19.75s +32.7% 9.01s 10.61s 2.21s 1.95s 2.00s 1.80s -10.3% 19.74s 20.00s 0.75x -
synthetic-1024-ssr-esm 5 4.91s 7.59s +54.7% 3.51s 3.94s 0.59s 0.54s 0.56s 0.53s -4.8% 7.54s 7.61s 0.65x -
synthetic-1024-ssr-esm-split 5 5.00s 7.59s +51.6% 3.58s 3.95s 0.64s 0.53s 0.53s 0.50s -5.3% 7.57s 7.61s 0.66x -
synthetic-256-sourcemaps 10 2.17s 3.07s +41.8% 1.60s 1.70s 0.27s 0.23s 0.15s 0.13s -16.9% 3.04s 3.12s 0.71x -
synthetic-256-ssr-esm 10 1.89s 2.42s +28.4% 1.36s 1.45s 0.26s 0.23s 0.15s 0.13s -17.4% 2.39s 2.44s 0.78x -
synthetic-256-ssr-esm-split 10 1.89s 2.39s +26.2% 1.36s 1.48s 0.26s 0.22s 0.15s 0.13s -16.0% 2.38s 2.46s 0.79x -
synthetic-48-ssr-esm 10 0.99s 1.05s +6.5% 0.70s 0.68s 0.14s 0.13s 0.08s 0.05s -32.8% 1.05s 1.07s 0.94x -

large-355-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 1785 1852.9ms 1597.0ms -13.8% 1597.0ms 13.5ms 10
node route:module 1785 925.0ms 777.7ms -15.9% 777.7ms 11.1ms 10
web route:client-entry 1785 407.5ms 439.4ms +7.8% 439.4ms 10.0ms 10
node manifest:transform 5 98.4ms 107.1ms +8.8% 107.1ms 24.5ms 5
web manifest:stage 15 15.3ms 20.0ms +30.7% 20.0ms 1.9ms 15
web manifest:transform 5 0.5ms 0.5ms 0.0% 0.5ms 0.1ms 5
node assets:relocate-ssr-only 10 - 139.8ms - 139.8ms 15.3ms 10

synthetic-1024-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 5130 2201.9ms 1797.5ms -18.4% 1797.5ms 6.8ms 10
node route:module 5130 962.8ms 946.3ms -1.7% 946.3ms 5.8ms 10
web route:client-entry 5130 650.3ms 608.0ms -6.5% 608.0ms 7.6ms 10
node manifest:transform 5 228.1ms 209.9ms -8.0% 209.9ms 49.1ms 5
node module:client-only-stub 5 74.3ms 233.0ms +213.6% 233.0ms 73.4ms 5
web manifest:stage 15 70.4ms 58.0ms -17.6% 58.0ms 6.6ms 15
web manifest:transform 5 0.5ms 0.5ms 0.0% 0.5ms 0.1ms 5
node assets:relocate-ssr-only 10 - 2.2ms - 2.2ms 0.4ms 10

synthetic-1024-ssr-esm-split Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 5130 2130.9ms 1809.2ms -15.1% 1809.2ms 7.7ms 10
node route:module 5130 962.2ms 921.7ms -4.2% 921.7ms 10.9ms 10
web route:client-entry 5130 641.5ms 652.5ms +1.7% 652.5ms 7.5ms 10
node manifest:transform 5 223.2ms 187.7ms -15.9% 187.7ms 41.9ms 5
node module:client-only-stub 5 64.6ms 261.9ms +305.4% 261.9ms 71.9ms 5
web manifest:stage 15 53.3ms 57.5ms +7.9% 57.5ms 6.5ms 15
web manifest:transform 5 0.5ms 0.5ms 0.0% 0.5ms 0.1ms 5
node assets:relocate-ssr-only 10 - 2.3ms - 2.3ms 0.4ms 10

synthetic-256-sourcemaps Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 2581 1482.8ms 1328.9ms -10.4% 1328.9ms 17.3ms 21
node route:module 2580 609.1ms 628.3ms +3.2% 628.3ms 6.7ms 20
web route:client-entry 2581 426.9ms 408.7ms -4.3% 408.7ms 5.4ms 21
node manifest:transform 10 165.1ms 156.2ms -5.4% 156.2ms 21.1ms 10
node module:client-only-stub 10 65.4ms 25.6ms -60.9% 25.6ms 4.0ms 10
web manifest:stage 33 26.9ms 29.9ms +11.2% 29.9ms 3.4ms 33
web manifest:transform 10 1.0ms 1.0ms 0.0% 1.0ms 0.1ms 10
node assets:relocate-ssr-only 23 - 6.2ms - 6.2ms 0.5ms 23

synthetic-256-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 2582 1413.0ms 1253.1ms -11.3% 1253.1ms 13.4ms 22
node route:module 2580 589.9ms 605.8ms +2.7% 605.8ms 8.4ms 20
web route:client-entry 2582 416.4ms 382.3ms -8.2% 382.3ms 5.8ms 22
node manifest:transform 10 164.9ms 171.1ms +3.8% 171.1ms 23.8ms 10
node module:client-only-stub 10 131.8ms 21.0ms -84.1% 21.0ms 2.5ms 10
web manifest:stage 32 23.7ms 27.9ms +17.7% 27.9ms 1.3ms 32
web manifest:transform 10 1.0ms 1.0ms 0.0% 1.0ms 0.1ms 10
node assets:relocate-ssr-only 22 - 4.5ms - 4.5ms 0.4ms 22

synthetic-256-ssr-esm-split Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 2584 1443.8ms 1231.0ms -14.7% 1231.0ms 11.0ms 24
node route:module 2580 608.8ms 585.1ms -3.9% 585.1ms 5.0ms 20
node module:client-only-stub 10 426.3ms 21.9ms -94.9% 21.9ms 3.2ms 10
web route:client-entry 2584 409.7ms 421.0ms +2.8% 421.0ms 8.2ms 24
node manifest:transform 10 182.4ms 178.0ms -2.4% 178.0ms 25.0ms 10
web manifest:stage 34 22.5ms 28.9ms +28.4% 28.9ms 1.4ms 34
web manifest:transform 10 1.0ms 1.0ms 0.0% 1.0ms 0.1ms 10
node assets:relocate-ssr-only 24 - 4.8ms - 4.8ms 0.4ms 24

synthetic-48-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 500 520.5ms 371.8ms -28.6% 371.8ms 8.9ms 20
node route:module 500 176.4ms 138.5ms -21.5% 138.5ms 0.8ms 20
web route:client-entry 500 114.0ms 89.9ms -21.1% 89.9ms 1.8ms 20
node module:client-only-stub 10 102.9ms 81.3ms -21.0% 81.3ms 13.2ms 10
node manifest:transform 10 54.6ms 52.0ms -4.8% 52.0ms 6.2ms 10
web manifest:stage 30 5.9ms 7.5ms +27.1% 7.5ms 0.4ms 30
web manifest:transform 10 1.0ms 1.0ms 0.0% 1.0ms 0.1ms 10
node assets:relocate-ssr-only 20 - 4.7ms - 4.7ms 0.4ms 20

Synthetic Rsbuild App

Rendered 2 production build benchmarks.

Benchmark Runs Base total Head total Delta Head mean Head p95 Speedup Head RSS p95
complex app 2 121.57s 121.63s +0.0% 121.63s - 1.00x -
complex app 2 85.52s 88.62s +3.6% 88.62s - 0.96x -

Rendered 1 dev benchmark fixture from the embedded complex app.

Benchmark Runs Base total Head total Delta Base ready Head ready Base routes Head routes Base update/HMR Head update/HMR Update delta Head mean Head p95 Speedup Head RSS p95
complex app 2 105.15s 105.14s -0.0% 95.75s 92.57s 3.02s 3.02s 3.69s 3.31s -10.2% 105.14s - 1.00x -

Profile: full; mode: dev; iterations: 10; warmup: 0.
The uploaded benchmark artifact includes diagnostics/summary.md and diagnostics/summary.json with runner metadata, per-run timing samples, CPU/RSS samples, and plugin timing hot spots.
Workflow run

ScriptedAlchemy and others added 10 commits July 3, 2026 20:28
…typing

- one resolveAppPackagePath/getPackageVersion pair in plugin-utils
  (react-router version now resolved from the app, not the plugin install)
- extract createRouteManifestItem from the dev-manifest effect and
  collapse the per-chunk module lookups
- replace Awaited<ReturnType<...>>/Parameters<...>[5] gymnastics with
  the exported ReactRouterManifestForDev/RouteChunkManifestOptions types
- type modePlan webOutput/webOptimization with Rspack config types and
  drop the as-const noise
- dedupe classic server-build options, single rsc-option normalization,
  inline single-use query helpers, remove plugin factory indirection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transformed routes import UNSAFE_withComponentProps and friends from
react-router, which only exist from 7.7.0; 7.13 is the lowest v7 line
this plugin is tested against (main's previous floor). Widening the
peer range to ^7.0.0 let 7.0-7.12 installs pass peer resolution and
then fail every classic-mode build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Register the RSC request listener via the server.setup post-callback so
it runs after compiled-asset and public-dir middlewares, making it the
fallback for anything static serving declines - the same routing
semantics as production. This removes the GET/POST and file-extension
bypass heuristics that 404'd extension-ful resource routes (e.g.
/sitemap.xml) and non-GET/POST resource routes in dev; only /__rsbuild_
internals bypass the handler now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire the prerender config into the RSC mode plan and add an
onAfterBuild step that replays upstream's RSC prerender pipeline
against the built server bundle's fetch handler: emits
<path>/index.html plus <path>.rsc flight payloads (_.rsc for the
root), meta-refresh documents for redirects, raw bytes for resource
routes, and __spa-fallback.html/.rsc for ssr:false builds. The RSC
preview fixture now serves prerendered documents and the SPA fallback
like upstream's configurePreviewServer. Classic-mode prerender is
untouched.

Vendored parity tests rsc/rsc-prerender-test.ts now pass 3/3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createReactRouterModePlan is now a thin dispatcher over
createClassicModePlan / createRscModePlan with a narrow shared
ModePlanContext, so each mode's planner owns only the inputs it uses
instead of one 30-field options blob feeding both branches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sync script now carries a pinned upstream ref
(4cf6e62d0cdf9d7f6e09b0ea10077d7fb0e1b438), refuses to sync from a
dirty or mismatched source checkout (--update-pin adopts a new sha),
and writes tests/react-router-framework/UPSTREAM.json recording the
repository, sha, source dirs, file count, and adapter-owned overlay
list. The README documents the pin-update flow and that everything
outside the adapter-owned files is generated and must not be
hand-edited.

corpusVerified is false: 13 checked-in files drift from the pinned
sha (a locally added rsc-framework-test.ts plus Rsbuild adaptations
and small edits) - recorded in the manifest as an undeclared overlay
to classify in a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The corpus is repo-owned and rsbuild-adapted; UPSTREAM.json records
provenance only. Delete the five checked-in template vite.config.ts
files (the adapter generates rsbuild.config.ts and never reads them)
and strip vite/@vitejs/@vanilla-extract/vite-plugin/@cloudflare/
vite-plugin/vite-tsconfig-paths entries from corpus package.jsons
(never installed - the corpus is not a workspace package and the
adapter rewrites fixture deps wholesale). No test file imports 'vite'
at top level; remaining matches are fixture template strings the
adapter converts. vite-env-only stays: fixture app code imports its
macros.

Verified: playwright smoke 7/7, test:core 490/490.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RSC mode silently ignored buildEnd, presets, serverBundles, and
subResourceIntegrity from react-router.config - a user's buildEnd hook
never fired and serverBundles/SRI produced nothing with no signal.
Upstream's RSC vite plugin rejects these with a validation error;
assertReactRouterRscConfigSupport now does the same.

allowedActionOrigins was also dropped in RSC mode even though
react-router's matchRSCServerRequest supports it (upstream's default
RSC entry drops it too). Expose it as a
virtual/react-router/unstable_rsc/allowed-action-origins module and
pass it through the entry template so action-origin restrictions apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The RSC mode plan pinned routeChunkConfig.splitRouteModules to false,
so a configured 'enforce' never triggered chunk validation. Upstream's
RSC vite plugin gates validation on config.splitRouteModules ===
'enforce' while chunk splitting itself stays content-detected - pass
the resolved config value through to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ScriptedAlchemy and others added 10 commits July 3, 2026 23:42
The config factory in integration/helpers/vite.ts now emits
rsbuild.config.ts content (pluginReactRouter/pluginReactRouterRSC +
pluginReact, pluginMdx when needed) with real option mappings:
base->output.assetPrefix, port->server.port+strictPort,
assetsInlineLimit->output.dataUriLimit, assetsDir->output.distPath,
vanilla-extract via @vanilla-extract/webpack-plugin (deterministic
identifiers, realContentHash off, sideEffects workaround). ~35 inline
vite.config fixture strings converted across 20+ test files; Vite-only
suites (plugin-order validation, @cloudflare/vite-plugin, fs.allow)
carry visible describe/test.skip with reasons. The vite-7/vite-8
template pair is collapsed to one (identical under rsbuild), halving
template-parameterized suite runs. The adapter's vite.config
interception is now a warn-loudly dead path (0 hits across all runs).

Verified against captured baselines: no test that passed before fails
after; vite-manifests (3), server-bundles dev/build-manifest,
spa-mode manifest, and vanilla-extract no-JS suites (3) newly pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Route edits in classic dev now apply through react-refresh instead of
full reloads, and loader/action changes trigger client revalidation:

- src/dev-hmr.ts: resolves @rsbuild/plugin-react's react-refresh
  runtime, generates a dev-only HMR runtime virtual module, and
  maintains an HDR revision module that the dev runtime bumps when a
  node rebuild commits so the browser revalidates loader data.
- route transforms gain a devHmr mode wrapping client route modules
  with refresh registration; dev-runtime-controller patches route
  metadata on the client and signals rebuild commits instead of
  forcing full reloads.
- When the react-refresh runtime is absent (or in RSC/build mode),
  everything falls back to the previous full-reload behavior.

Vendored parity tests vite-hmr-hdr-test.ts now pass 3/3 (previously
0/6 pre-template-collapse). vite-dev-test.ts guard: 12 passed, 4
pre-existing failures verified identical without this change.

This work was implemented by a subagent whose worktree was lost to a
session limit; the change set was recovered by replaying its recorded
54 edit operations and validated byte-for-byte against its partial
snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The helper spawnSyncs 'rsbuild dev' expecting Vite's validate-and-exit
startup behavior; rsbuild's dev server never self-terminates, which
blocked a suite run indefinitely when a test assumed an error exit.
Triage confirmed no plugin or rsbuild bug (config function-form was
coincidental): a 30s timeout keeps any such assumption from hanging
the harness again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two manifest asset-URL bugs:

- The dev manifest derived its asset prefix solely from
  output.assetPrefix, ignoring dev.assetPrefix (Rsbuild's effective
  prefix in dev, defaulted from server.base). Asset URLs 404'd for any
  app configuring a dev prefix. resolveEffectiveAssetPrefix now mirrors
  Rsbuild's per-mode precedence off the normalized config, resolved in
  onBeforeCreateCompiler.

- The build-mode browser manifest was generated at processAssets stage
  'additions' (-100), before rspack's realContentHash rename at
  OPTIMIZE_HASH (2500), so manifest CSS/JS URLs could reference
  pre-rename hashed names absent from the output. Build-mode manifest
  generation and emission now run at 'report' (5000), unifying the
  previously separate SRI deferral; dev keeps 'additions' (no
  realContentHash pass in dev).

test:core 491 -> 499. Classic-mode basename dev/build corpus cases all
pass; vanilla-extract build cases pass with the fixtures'
realContentHash:false workaround removed (workaround removal lands
with the corpus rename commit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Content-level renames completing the file renames that landed with
fb25f5a: viteConfig->rsbuildConfig (77 refs), vitePreview->
rsbuildPreview, viteDevCmd->rsbuildDevCmd, viteMajorTemplates->
bundlerTemplates, Rsbuild-accurate describe/test titles ('Vite CSS'->
'CSS', 'vite dev'->'rsbuild dev', ...), rsc fixture package scripts
now say rsbuild, and UPSTREAM.json gains a complete upstream->corpus
renames map (31 entries) so manual upstream comparison survives the
renames. The two describe.skip'ed Vite-only suites keep their accurate
Vite titles, as do assertions on strings React Router itself emits.

Also applies the deferred manifest-fix corpus hunk to the renamed
config factory: 'base' now maps to dev.assetPrefix as well (the plugin
honors it since fb25f5a) and the vanilla-extract realContentHash:false
workaround is removed (manifest reads post-hash names now).

Verified: test:core 499; smoke 7/7; hmr-hdr 3/3; css+basename exactly
match pre-existing baselines (24 passed / 14 failed / 3 skipped / 1
DNR - zero new failures); corpus greps for vite-flavored helper names
come back empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Assets referenced only by server code (asset?url imports, .css?url
files) are returned to the client as URLs but were never emitted to
build/client, 404ing at runtime. Mirroring upstream's ssrEmitAssets +
writeBundle move/remove: a node-target processAssets 'report' hook
(post-realContentHash, so names are final) relocates emitted static
assets into the client output when absent and strips them from the
server build; server code-split chunks get a proper async chunkFilename
and server-only .css?url output is redirected to the flat
static/assets layout while preserving postcss processing.

Verified against rsbuild source: dev serves from real disk when
writeToDisk is on (setupOutputFileSystem), cleanAll runs before
compilations so relocated files survive, and the uniform assetPrefix
keeps loader-returned URLs aligned. No rsbuild/rspack built-in covers
cross-environment asset sharing (tracedecay-searched), so the
relocation module stays.

Corpus: build-test + extra-server-environment now 9/9 (combined with
the post-realContentHash manifest fix, even code-split CSS passes);
smoke 7/7; test:core 506.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tests/react-router-framework/.tmp/ (7,856 materialized fixture files)
was accidentally committed by a broad git add during the corpus work,
inflating the PR diff massively. Untrack it and ignore fixture scratch,
test-results, and playwright reports going forward.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four-angle review (reuse/simplification/efficiency/altitude) over the
RR8 branch diff:

- assetPrefix now has a single writer (onBeforeCreateCompiler); the two
  config-phase assignments are gone and the virtual-module publicPath
  reads output.assetPrefix locally
- RscModePlan drops its routeTransformExecutor/routeChunkOptions
  sentinels; the one unguarded consumer gates on plan kind
- shared parseVersionMajorMinor replaces two divergent react-router
  version parsers; getPackageVersion memoizes default-resolver lookups
- rsc-route-transforms imports CLIENT_NON_COMPONENT_EXPORTS from
  constants instead of redefining it; SPA_FALLBACK_HTML_FILE is a
  shared constant across both prerender pipelines; RSC basename/
  bootstrap prefixes reuse normalizeAssetPrefix (was 4 copies)
- route modules are no longer parsed for analysis in the node
  transform (client-entry covers every route; disk fallback intact),
  removing 1-2 redundant SWC parses per route per build
- ssr-asset-relocation caches relocated destinations across node
  compilations (serverBundles, dev rebuilds) skipping steady-state
  stat/write syscalls; dev-hmr skips redundant mkdir after first bump
- react-refresh runtime resolution no longer falls back to a bare
  root resolve that could bind a different physical instance than the
  browser graph (silent no-op refresh); unavailable now means the
  documented full-reload path
- devHmr options pass through the mode plan without re-projection;
  RSC virtual modules share a defaultExport helper; classic
  createResolveConfig drops its empty-alias conditional

Skipped deliberately: the node .css?url filename mapping (documented,
empirically-validated tradeoff), hooks-per-mode/discriminated-union
mode-plan reshape (deferred, larger), PrerenderConfig type unification
(low value vs inference churn), shared classic/RSC alias builder
(resolution semantics deliberately differ). manifestChunkNames on
RscModePlan was confirmed live (web clientStats), not dead state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The interop script's hand-rolled plugin API stub predates the
onBeforeCreateCompiler registration added for effective-asset-prefix
resolution, so CI's build-test failed with 'api.onBeforeCreateCompiler
is not a function'. The hook exists on the real Rsbuild plugin API
(@rsbuild/core 2.x); only the stub was stale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ScriptedAlchemy and others added 30 commits July 8, 2026 23:51
Delete dead code (getRscPrerenderRequestPaths, federation threading through
mode-plan/classic-mode/generateServerBuild), reuse canonical constants and
types (SERVER_ONLY_ROUTE_EXPORTS, ReactRouterPerformanceProfiler, rspack
NormalModuleFactory/ResolveData, shared PrerenderConfigObject), trust
resolveRouteDiscoveryConfig output in RSC virtual modules, type
import.meta.webpackHot once, and dedupe test boilerplate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Delete createDataOutputPath (behavioral duplicate of createDataRequestPath),
share the HMR-patchable route flag list between route-artifacts and the dev
manifest differ, collapse the copy-pasted HMR export-identity ternaries,
flatten the ssr-asset-relocation skip checks, reorder the RSC route transform
test predicate so the regex runs before path resolution, and document the
federation asyncStartup workaround and the web route-module transform
ownership invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Match react-refresh/babel's component detection: bail on multi-declarator
lists and curried arrows, reject require/import interop callees, recurse
call-expression arguments so HOC results register only when their argument
is a component, and relax the emitted registration guard so memo/forwardRef
exotic objects register like react-refresh's own runtime allows. Adds a
regression suite pinning the ruleset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server-first routes import CSS in the RSC layer where it never reaches the
client <Links>. Mark modules exporting server components with the
'use server-entry' directive so rspack's RSC runtime records entryCssFiles,
and stream those as precedence-tagged stylesheet links from the server route
entry wrapper — the rsbuild-native equivalent of upstream's
import.meta.viteRsc.loadCss(). Detection uses exact export names via the
cached export analyzer, not substring matching. Updates the repo-owned
transform oracle for this intentional divergence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The template shipped no rsbuild.config.ts, so the fixture adapter synthesized
a framework-mode config whose route resolution fails (app/routes.ts) — every
rsc-test.ts describe died in beforeAll. Ship the template's own data-mode
config wiring rsbuild-plugin-rsc: rsc/ssr layers, dist/rsc + dist/client
split matching server.js, html:false so the RSC document isn't shadowed,
client-reference exports guarded from tree-shaking/mangling, and a dev
catch-all serving the compiled RSC handler via loadBundle. Bootstrap scripts
and the server action manifest are threaded through the entries so hydration
and form actions work. Full RSC suite: 41/41 on Node 22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
E2E for the entryCssFiles mechanism: builds an rsc-framework app with a
server-first /css route, asserts the raw server document contains the
stylesheet link, and verifies computed styles with JavaScript disabled —
first-paint proof that rspack's RscServerPlugin populates entryCssFiles at
runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the includes("rsc") template check that conflated rsc-framework
(framework mode, synthesized config) with rsc-preview (data mode, ships its
own rsbuild.config.ts): templates shipping a config now fail loudly if it is
missing instead of silently receiving a wrong-mode config, the start script
is correct per template, the rsbuild-plugin-rsc pin locations are documented
at both bump sites, and the vendoring README records the intentional
'use server-entry' CSS divergence so corpus syncs don't revert it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clone only the mutated route entries per HMR flush instead of deep-cloning
the whole manifest; type the RSC dev-server setup with rsbuild's own
ServerSetupContext discriminated union (casts deleted, preview action
excluded soundly); drop the dev-generation re-exports nothing imports
through it, keeping the three that tests consume.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regression-cover applyConfigDefines' stop-on-match traversal (longest
member path wins, no overlapping replacement, computed string access
normalizes to the dotted key) and assert RSC-path splitRouteModules
enforce rejects unsplittable clientMiddleware with the canonical error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The RSC bootstrap-scripts virtual module hardcoded static/js/index.js;
custom output.distPath.js configs produced dead bootstrap URLs. Read the
js dist path from the raw config at the same modifyRsbuildConfig site as
the asset prefix and thread it to the virtual module (the entry filename
stays deterministic because the plugin forces web filename.js to
'[name].js'). Classic mode ignores the value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dev server now streams source-mapped error documents on handler throws;
the remaining failure is that useLocation().search is empty during the RSC
server render, so the fixture's crash condition never fires. Update the
fixme to the verified root cause.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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