Skip to content

feat: support React Router 8 compatibility#74

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

feat: support React Router 8 compatibility#74
ScriptedAlchemy wants to merge 142 commits into
mainfrom
codex/react-router-8-compat-v2

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds React Router 8 compatibility while preserving React Router 7 support and the current Rsbuild plugin architecture.

What changed

  • adds React Router 8 and RSC mode support, templates, virtual modules, examples, types, and package compatibility
  • adds classic-mode state-preserving HMR, route metadata patching, hot data revalidation, and Fast Refresh detection with full-reload fallback
  • adds RSC prerendering, server-first route transforms, CSS streaming, asset relocation, basename handling, and server-action manifest support
  • expands unit, integration, framework-corpus, package-interop, and example E2E coverage
  • migrates the checked-in framework corpus and Epic Stack fixtures from Vite/Vitest naming and configuration to Rsbuild/Rstest equivalents
  • replaces the mutating upstream corpus sync with a read-only audit plus a repository-local Codex skill for manual test selection and adaptation
  • hardens test fixture path containment and documents evidence-backed CodeQL dispositions
  • updates CI to build once, run the large non-HMR framework phase with four workers, use explicit read-only workflow permissions, and retain fail-fast ecosystem coverage
  • merges the latest main, including the sharded benchmark workflow

Validation

  • CI=true pnpm install --frozen-lockfile
  • pnpm test — 56 files, 659 tests passed
  • pnpm build
  • npx publint --errors-only — no errors
  • actionlint — no errors, ignoring the existing intentional SC2046 command-substitution warnings
  • targeted HMR regressions — basename, route-added, and RSC HMR/HDR passed
  • framework fail-fast suite — 1,005 passed, 46 skipped; four-worker non-HMR phase completed in 6.6 minutes
  • example/package E2E workspace was exercised twice; two different order-dependent dev-server timeouts each passed immediately when rerun alone

Review notes

  • the React Router framework corpus is repository-owned and manually adapted; UPSTREAM.json records only the last reviewed upstream revision
  • the Rstest logo is present and wired in all Epic Stack fixture variants
  • generated dynamic imports use JSON string literals; adversarial tests cover quotes, backslashes, newlines, and code-like module specifiers
  • test-only Express rate-limit findings and CodeQL-unrecognized containment guards were dismissed with scoped evidence after the fresh scan

@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@490c349

commit: 490c349

Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Dismissed
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 Dismissed
Comment thread src/rsc-route-transforms.ts Dismissed
Comment thread src/rsc-route-transforms.ts Dismissed
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Compared PR head 490c349 against base 7bb55da.

Reading benchmark confidence

Raw deltas are always shown. The signal label only indicates whether the observed median delta is larger than a robust run-to-run noise band; it does not erase or replace the measurement.

The noise band is the larger of 2% or two combined robust standard deviations estimated from each side's relative median absolute deviation (rMAD). Fewer than three finite samples is reported as insufficient data. An inconclusive result should be rerun or investigated from the uploaded raw samples before drawing a performance conclusion.

Benchmark Runs Base total Head total Delta Base rMAD Head rMAD Noise band Signal
large-355-ssr-esm (build) 3 8.40s 9.34s +11.2% 0.3% 0.9% ±2.8% 🔴 regression
synthetic-1024-ssr-esm (build) 3 3.92s 4.03s +2.8% 0.6% 2.9% ±8.7% ⚪ inconclusive
synthetic-1024-ssr-esm-split (build) 3 5.16s 5.76s +11.6% 0.3% 0.9% ±2.9% 🔴 regression
synthetic-256-sourcemaps (build) 5 2.17s 2.22s +2.5% 0.6% 0.7% ±2.7% ⚪ inconclusive
synthetic-256-ssr-esm (build) 5 2.03s 2.10s +3.4% 0.6% 0.2% ±2.0% 🔴 regression
synthetic-256-ssr-esm-split (build) 5 2.45s 2.62s +6.9% 1.0% 0.9% ±3.9% 🔴 regression
synthetic-48-ssr-esm (build) 5 1.33s 1.37s +2.5% 0.3% 0.3% ±2.0% 🔴 regression
large-355-ssr-esm (dev) 3 15.21s 17.54s +15.3% 0.3% 6.2% ±18.5% ⚪ inconclusive
synthetic-1024-ssr-esm (dev) 3 4.48s 4.71s +5.3% 1.2% 1.9% ±6.6% ⚪ inconclusive
synthetic-1024-ssr-esm-split (dev) 3 4.58s 4.75s +3.8% 0.8% 0.3% ±2.6% 🔴 regression
synthetic-256-sourcemaps (dev) 5 2.12s 2.21s +4.2% 3.5% 1.0% ±10.8% ⚪ inconclusive
synthetic-256-ssr-esm (dev) 5 1.79s 1.88s +5.2% 0.5% 0.7% ±2.6% 🔴 regression
synthetic-256-ssr-esm-split (dev) 5 1.83s 1.93s +5.1% 1.6% 2.4% ±8.4% ⚪ inconclusive
synthetic-48-ssr-esm (dev) 5 0.92s 0.92s -0.1% 0.7% 0.4% ±2.4% ⚪ inconclusive
complex app (cold) 3 115.69s 120.51s +4.2% 0.2% 0.3% ±2.0% 🔴 regression
complex app (dev) 3 101.82s 104.03s +2.2% 1.1% 0.7% ±4.0% ⚪ inconclusive
complex app (warm) 3 78.66s 87.87s +11.7% 0.5% 0.2% ±2.0% 🔴 regression

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 30.92s 33.94s +9.8% 18.86s 21.35s +13.2% 3.87s 3.65s -5.6% 2.89s 2.71s -6.3% 0.91x
Large app 1 15.21s 17.54s +15.3% 8.12s 9.45s +16.3% 1.93s 1.80s -7.0% 1.70s 1.72s +1.2% 0.87x
Standard fixtures 6 15.72s 16.40s +4.3% 10.74s 11.90s +10.8% 1.94s 1.86s -4.1% 1.19s 0.99s -17.0% 0.96x

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 3 8.40s 9.34s +11.2% 9.32s 9.42s 0.90x 1623 MB
synthetic-1024-ssr-esm 3 3.92s 4.03s +2.8% 4.03s 4.14s 0.97x 646 MB
synthetic-1024-ssr-esm-split 3 5.16s 5.76s +11.6% 5.76s 5.82s 0.90x 805 MB
synthetic-256-sourcemaps 5 2.17s 2.22s +2.5% 2.23s 2.26s 0.98x 447 MB
synthetic-256-ssr-esm 5 2.03s 2.10s +3.4% 2.10s 2.11s 0.97x 421 MB
synthetic-256-ssr-esm-split 5 2.45s 2.62s +6.9% 2.61s 2.66s 0.94x 462 MB
synthetic-48-ssr-esm 5 1.33s 1.37s +2.5% 1.36s 1.37s 0.98x 319 MB

ci-small+ci-large Dev Fixture Summary

Rendered 7 dev benchmark fixtures from the ci-small+ci-large 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 3 15.21s 17.54s +15.3% 8.12s 9.45s 1.93s 1.80s 1.70s 1.72s +1.2% 17.50s 18.63s 0.87x -
synthetic-1024-ssr-esm 3 4.48s 4.71s +5.3% 3.03s 3.45s 0.57s 0.52s 0.35s 0.30s -14.7% 4.73s 4.84s 0.95x -
synthetic-1024-ssr-esm-split 3 4.58s 4.75s +3.8% 3.11s 3.46s 0.53s 0.53s 0.40s 0.30s -25.0% 4.79s 4.90s 0.96x -
synthetic-256-sourcemaps 5 2.12s 2.21s +4.2% 1.47s 1.60s 0.25s 0.23s 0.13s 0.13s +0.2% 2.20s 2.23s 0.96x -
synthetic-256-ssr-esm 5 1.79s 1.88s +5.2% 1.23s 1.37s 0.23s 0.23s 0.13s 0.10s -19.6% 1.87s 1.89s 0.95x -
synthetic-256-ssr-esm-split 5 1.83s 1.93s +5.1% 1.26s 1.38s 0.24s 0.23s 0.13s 0.10s -19.8% 1.96s 2.06s 0.95x -
synthetic-48-ssr-esm 5 0.92s 0.92s -0.1% 0.64s 0.64s 0.12s 0.13s 0.05s 0.05s +1.2% 0.92s 0.94s 1.00x -

large-355-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 1072 1023.8ms 928.8ms -9.3% 928.8ms 11.9ms 7
node route:module 1071 494.6ms 470.0ms -5.0% 470.0ms 7.5ms 6
web route:client-entry 1072 271.5ms 271.8ms +0.1% 271.8ms 7.3ms 7
node manifest:transform 3 70.0ms 62.6ms -10.6% 62.6ms 24.2ms 3
web manifest:stage 10 15.5ms 12.8ms -17.4% 12.8ms 1.9ms 10
web manifest:transform 3 0.3ms 0.3ms 0.0% 0.3ms 0.1ms 3
node assets:relocate-ssr-only 7 - 109.4ms - 109.4ms 21.5ms 7

synthetic-1024-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 3078 1219.8ms 1111.8ms -8.9% 1111.8ms 10.3ms 6
node route:module 3078 577.3ms 531.7ms -7.9% 531.7ms 18.7ms 6
web route:client-entry 3078 399.7ms 372.6ms -6.8% 372.6ms 8.0ms 6
node module:client-only-stub 3 168.9ms 102.2ms -39.5% 102.2ms 53.6ms 3
node manifest:transform 3 114.9ms 114.7ms -0.2% 114.7ms 38.5ms 3
web manifest:stage 9 43.5ms 37.2ms -14.5% 37.2ms 7.1ms 9
web manifest:transform 3 0.3ms 0.3ms 0.0% 0.3ms 0.1ms 3
node assets:relocate-ssr-only 6 - 1.3ms - 1.3ms 0.4ms 6

synthetic-1024-ssr-esm-split Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 3078 1241.9ms 1080.2ms -13.0% 1080.2ms 6.9ms 6
node route:module 3078 568.0ms 548.0ms -3.5% 548.0ms 19.3ms 6
web route:client-entry 3078 343.3ms 323.7ms -5.7% 323.7ms 8.2ms 6
node module:client-only-stub 3 317.8ms 88.9ms -72.0% 88.9ms 51.6ms 3
node manifest:transform 3 129.5ms 124.0ms -4.2% 124.0ms 45.4ms 3
web manifest:stage 9 46.6ms 36.3ms -22.1% 36.3ms 7.1ms 9
web manifest:transform 3 0.3ms 0.3ms 0.0% 0.3ms 0.1ms 3
node assets:relocate-ssr-only 6 - 1.4ms - 1.4ms 0.4ms 6

synthetic-256-sourcemaps Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 1293 708.4ms 657.6ms -7.2% 657.6ms 16.6ms 13
node route:module 1290 281.3ms 291.6ms +3.7% 291.6ms 3.4ms 10
web route:client-entry 1293 196.6ms 199.3ms +1.4% 199.3ms 5.3ms 13
node manifest:transform 5 64.9ms 72.5ms +11.7% 72.5ms 16.5ms 5
node module:client-only-stub 5 42.5ms 9.9ms -76.7% 9.9ms 2.3ms 5
web manifest:stage 18 19.8ms 15.2ms -23.2% 15.2ms 1.3ms 18
web manifest:transform 5 0.5ms 0.5ms 0.0% 0.5ms 0.1ms 5
node assets:relocate-ssr-only 13 - 3.5ms - 3.5ms 0.4ms 13

synthetic-256-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 1290 683.0ms 650.7ms -4.7% 650.7ms 17.7ms 10
node route:module 1290 275.7ms 257.9ms -6.5% 257.9ms 4.9ms 10
web route:client-entry 1290 202.1ms 184.9ms -8.5% 184.9ms 5.1ms 10
node module:client-only-stub 5 87.8ms 14.1ms -83.9% 14.1ms 4.4ms 5
node manifest:transform 5 85.2ms 72.7ms -14.7% 72.7ms 19.2ms 5
web manifest:stage 15 13.8ms 13.0ms -5.8% 13.0ms 1.3ms 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-256-ssr-esm-split Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 1292 696.4ms 603.7ms -13.3% 603.7ms 16.5ms 12
node route:module 1290 283.7ms 260.4ms -8.2% 260.4ms 4.7ms 10
web route:client-entry 1292 206.3ms 195.6ms -5.2% 195.6ms 5.2ms 12
node manifest:transform 5 88.4ms 64.4ms -27.1% 64.4ms 18.8ms 5
node module:client-only-stub 5 55.1ms 13.6ms -75.3% 13.6ms 5.4ms 5
web manifest:stage 17 14.0ms 14.7ms +5.0% 14.7ms 1.6ms 17
web manifest:transform 5 0.5ms 0.5ms 0.0% 0.5ms 0.1ms 5
node assets:relocate-ssr-only 12 - 2.2ms - 2.2ms 0.3ms 12

synthetic-48-ssr-esm Plugin Operations

Environment Operation Count Base total Head total Delta Head wall Head max Reports
web route:module 250 250.8ms 190.9ms -23.9% 190.9ms 7.7ms 10
node route:module 250 81.6ms 65.8ms -19.4% 65.8ms 0.5ms 10
web route:client-entry 250 57.8ms 44.2ms -23.5% 44.2ms 2.0ms 10
node module:client-only-stub 5 44.8ms 30.7ms -31.5% 30.7ms 8.6ms 5
node manifest:transform 5 27.0ms 22.9ms -15.2% 22.9ms 6.3ms 5
web manifest:stage 15 3.6ms 3.8ms +5.6% 3.8ms 0.4ms 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 Rsbuild App

Rendered 2 production build benchmarks.

Benchmark Runs Base total Head total Delta Head mean Head p95 Speedup Head RSS p95
complex app 3 115.69s 120.51s +4.2% 120.67s - 0.96x -
complex app 3 78.66s 87.87s +11.7% 87.85s - 0.90x -

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 3 101.82s 104.03s +2.2% 90.86s 92.91s 2.98s 3.04s 3.36s 3.39s +1.0% 104.56s - 0.98x -

Profile: ci-small+ci-large; mode: dev; iterations: 5; warmup: 1.
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 ScriptedAlchemy force-pushed the codex/react-router-8-compat-v2 branch from e8fe50f to 0e7fa0c Compare July 9, 2026 22:20
ScriptedAlchemy and others added 7 commits July 9, 2026 22:51
Its only plugin import is type-only and the typegen integration resolves it
from the app directory with graceful fallback, so adding the peer must not
break installs that lack it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Preserve the React Router 8 warning format while incorporating repository hygiene and prerender release metadata from main.
Trim branch-specific commentary and replace Module Federation option assertions with structural narrowing.
Assigning ogEnv back when NODE_ENV was originally unset coerced it to the
string "undefined", poisoning every later fixture build in the serial CI
worker: rsbuild saw an unrecognized mode, the built web bundle kept a
literal process.env.NODE_ENV, and the browser threw 'process is not
defined' — failing preview-test.ts ~600 tests downstream and blocking the
suite via max-failures. Delete the variable when it was unset instead.
Reproduced and verified: error-sanitization + preview serial sequence now
40/40.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rsbuild only emits the NODE_ENV define for its recognized modes; an
unrecognized value (such as the string 'undefined' leaking from a
misconfigured shell) resolves mode 'none', leaves the bare reference in
the browser bundle, and React throws 'process is not defined' at runtime.
Define it unconditionally for the web environment — valid explicit values
pass through, otherwise build resolves to production and dev to
development, and a user-supplied define wins. Mirrors the Vite plugin.
Verified: preview build serves correctly under NODE_ENV=undefined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The step adding a newly-imported client component pays a cold module
compile before the hot update applies; the 15s budget that comfortably
covers the ~6s local run intermittently expires on slow CI runners (same
sha passed and failed across runs). Use 45s under CI, matching the
hydration budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Web-only commits reuse the node compiler's stale modifiedFiles snapshot,
and each HDR bump itself triggers a web rebuild, so the bump fed back into
an infinite rebuild loop after the first server-file edit. Track the last
node compilation identity that signaled and fire once per real node build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Comment thread tests/react-router-framework/integration/helpers/rsc-framework/start.js Dismissed
Comment thread tests/react-router-framework/integration/helpers/rsc-framework/start.js Dismissed
ScriptedAlchemy and others added 6 commits July 10, 2026 12:21
…compat-v2

# Conflicts:
#	.github/workflows/e2e-tests.yml
#	pnpm-lock.yaml
#	src/build-output-transforms.ts
#	src/dev-hmr.ts
#	src/dev-runtime-controller.ts
#	src/index.ts
#	src/route-artifacts.ts
#	src/route-transform-tasks.ts
#	tests/build-output-transforms.test.ts
#	tests/dev-runtime-controller.test.ts
#	tests/route-artifacts.test.ts
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Dismissed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Dismissed
Comment thread tests/react-router-framework/integration/helpers/create-fixture.ts Dismissed
Comment thread tests/react-router-framework/integration/helpers/rsc-framework/start.js Dismissed
Comment thread tests/react-router-framework/integration/helpers/rsc-framework/start.js Dismissed
…compat-v2

# Conflicts:
#	tests/benchmark-fixture.test.ts
@ScriptedAlchemy ScriptedAlchemy changed the title [codex] support React Router 8 compatibility feat: support React Router 8 compatibility Jul 11, 2026
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