[codex] support React Router 8 compatibility#74
Open
ScriptedAlchemy wants to merge 89 commits into
Open
Conversation
commit: |
Contributor
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: |
…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>
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>
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>
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.
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.Validation
CI=true pnpm install --lockfile-onlypnpm buildpnpm test:package-interoppnpm test:corepnpm --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.