[codex] Allow parallel dev compilers#54
Conversation
commit: |
Benchmark ResultsCompared PR head Total median wall time: 9.44s -> 9.94s (+5.2%, 0.95x speedup)
Profile: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1095e45d4
ℹ️ 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".
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (125)
💤 Files with no reviewable changes (10)
✅ Files skipped from review due to trivial changes (16)
🚧 Files skipped from review as they are similar to previous changes (83)
📝 WalkthroughWalkthroughThe pull request updates the React Router plugin internals around route analysis, route chunking, manifest generation, browser manifest emission, prerendering, and dev-server runtime coordination. It also adds benchmark scripts and fixture coverage, revises documentation and release notes, and updates example applications and package manifests. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.23.0)examples/custom-node-server/tsconfig.json┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m [00.22][ERROR]: unable to find a config; path examples/default-template/app/routes.ts┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m [00.17][ERROR]: unable to find a config; path examples/default-template/package.json┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m [00.19][ERROR]: unable to find a config; path
🔧 markdownlint-cli2 (0.22.1).changeset/bright-routes-run.mdmarkdownlint-cli2 wrapper config was not available before execution .changeset/coherent-dev-generations.mdmarkdownlint-cli2 wrapper config was not available before execution .changeset/lazy-entry-hydration.mdmarkdownlint-cli2 wrapper config was not available before execution
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 |
There was a problem hiding this comment.
Actionable comments posted: 11
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (10)
examples/custom-node-server/scripts/smoke-production.mjs-64-70 (1)
64-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winAvoid the fixed 5s teardown delay after early exits.
If the child has already exited before this block runs,
once(child, 'exit')subscribes too late and the script always waits the full timeout. Capture the exit promise before signaling, and skip the wait whenexitCodeis already set.Proposed fix
} finally { - child.kill('SIGTERM'); - await Promise.race([ - once(child, 'exit'), - new Promise(resolve => setTimeout(resolve, 5_000)), - ]); - if (child.exitCode === null) { - child.kill('SIGKILL'); - } + if (child.exitCode === null) { + const exited = once(child, 'exit'); + child.kill('SIGTERM'); + await Promise.race([ + exited, + new Promise(resolve => setTimeout(resolve, 5_000)), + ]); + if (child.exitCode === null) { + child.kill('SIGKILL'); + } + } }🤖 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 `@examples/custom-node-server/scripts/smoke-production.mjs` around lines 64 - 70, The teardown in smoke-production.mjs waits on once(child, 'exit') only after sending SIGTERM, which can miss an exit that already happened and force the full timeout. Update the child process handling around the existing child.kill and Promise.race flow by creating the exit promise before signaling, and guard the wait with child.exitCode so you skip the timeout path when the process has already exited.README.md-107-112 (1)
107-112: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDocument one
parallelTransformsdefault.Line 110 says
@default true, but the defaults section on Lines 309-314 says the option defaults toundefinedwith worker execution enabled implicitly. Pick one representation so the public API contract is not self-contradictory.🤖 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 `@README.md` around lines 107 - 112, The README documentation for parallelTransforms is contradictory about its default value. Update the public API docs so the JSDoc on parallelTransforms and the defaults section describe the same behavior, using one consistent default representation in both places. Refer to parallelTransforms in the options docs and the defaults section, and make sure the wording matches whether worker execution is enabled implicitly or via an explicit true default.README.md-527-527 (1)
527-527: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winExit on unrecoverable startup failure.
Line 527 only logs boot errors, but the section above explicitly requires a clean process restart after startup failure. Exit non-zero after logging so the example does not stay alive in a broken state.
Proposed fix
-startServer().catch(console.error); +startServer().catch(error => { + console.error(error); + process.exit(1); +});🤖 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 `@README.md` at line 527, The startup example in startServer() currently only logs failures with console.error and leaves the process running, which conflicts with the documented requirement for unrecoverable startup failures. Update the startup handling so the catch path for startServer() both logs the error and terminates the process with a non-zero exit code, keeping the example from continuing in a broken state.scripts/benchmark/fixture.mjs-648-658 (1)
648-658: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFail fast on invalid
largeConfigcounts.The new
largeConfigoverride path is unvalidated, but several generators later use counts likeworkers,localeFiles,cssModules,svgAssets,utilitiesPerRoute, andpayloadEntriesPerComponentin%/division and import-path generation. Passing0currently emits broken fixture code instead of rejecting the config up front.🛠️ Proposed fix
+const assertPositiveInteger = (name, value) => { + if (!Number.isInteger(value) || value < 1) { + throw new Error( + `${name} must be a positive integer for the large benchmark fixture.` + ); + } +}; + const generateLargeFixture = async ({ root, routeCount, variant, @@ }) => { const config = normalizeLargeConfig(routeCount, largeConfig); + for (const [name, value] of Object.entries({ + routes: config.routes, + componentsPerRoute: config.componentsPerRoute, + utilitiesPerRoute: config.utilitiesPerRoute, + lazyModulesPerRoute: config.lazyModulesPerRoute, + workers: config.workers, + svgAssets: config.svgAssets, + cssModules: config.cssModules, + localeFiles: config.localeFiles, + payloadEntriesPerComponent: config.payloadEntriesPerComponent, + })) { + assertPositiveInteger(name, value); + } const isSpa = variant === 'spa';Also applies to: 814-834
🤖 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 `@scripts/benchmark/fixture.mjs` around lines 648 - 658, The largeConfig override path in generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which later break generators that use workers, localeFiles, cssModules, svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or import-path logic. Add upfront validation for these count fields in normalizeLargeConfig (or immediately after calling it in generateLargeFixture) and reject non-positive values with a clear error before any fixture generation starts.rslib.config.ts-23-28 (1)
23-28: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winEmit the route transform worker from the CJS build too.
createWorkerUrl()always resolves./parallel-route-transform-worker.js, but the CJSsource.entryblock doesn’t include that file. CJS consumers won’t get the worker asset, so parallel transforms will fall back to the serial path.Proposed fix
source: { entry: { index: './src/index.ts', + 'parallel-route-transform-worker': + './src/parallel-route-transform-worker.ts', 'templates/entry.server': './src/templates/entry.server.tsx', 'templates/entry.client': './src/templates/entry.client.tsx', },🤖 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 `@rslib.config.ts` around lines 23 - 28, The CJS source.entry map is missing the route transform worker entry, so CJS builds won’t emit the asset that createWorkerUrl() resolves. Update the source.entry block in rslib.config.ts to include the parallel route transform worker alongside the existing index, templates/entry.server, and templates/entry.client entries, so CJS consumers also receive parallel-route-transform-worker.js.src/route-export-pruning.ts-544-549 (1)
544-549: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDon’t track sourced re-export names as local bindings.
For
export { loader as clientLoader } from "./x",specifier.local.nameis the imported name from the source module, not a local binding. Adding it toremovedExportLocalNamescan make the later assignment cleanup remove unrelated localloader.* = ...statements.Proposed fix
const exportedName = getExportedName(specifier); if (exportedName && exportsToRemoveSet.has(exportedName)) { exportsChanged = true; - if (specifier.local?.name) { + if (!statement.source && specifier.local?.name) { removedExportLocalNames.add(specifier.local.name); removedExportReferencedNames.add(specifier.local.name); } return false; }🤖 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/route-export-pruning.ts` around lines 544 - 549, The re-export cleanup in the export pruning flow is incorrectly treating sourced re-export names as local bindings. In the logic around the export specifier handling in route-export-pruning and the sets removedExportLocalNames / removedExportReferencedNames, only add names that are actually declared locally in the file; for `export { loader as clientLoader } from "./x"`, avoid recording `specifier.local.name` as a local binding so later assignment cleanup does not delete unrelated `loader.*` statements. Use the existing export-specifier and assignment-pruning checks to distinguish sourced re-exports from true local exports before adding names to those tracking sets.tests/index.test.ts-106-139 (1)
106-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRestore the previous
existsSyncmock implementation.Line 111 overrides a shared mock, but Line 138 leaves it hardcoded to
true. Capture and restore the previous implementation so this test cannot leak filesystem behavior into later cases.🤖 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 `@tests/index.test.ts` around lines 106 - 139, Restore the shared fs.existsSync mock in this test by capturing the previous mock implementation before overriding it and reinstating that exact implementation in the finally block. Update the test around createStubRsbuild and pluginReactRouter so the temporary behavior used for the watchFiles assertion does not leak into later tests.src/index.ts-231-239 (1)
231-239: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTreat
serverOutput: undefinedas omitted.
hasOwnPropertymakes{ serverOutput: undefined }explicit, soresolvedServerOutputcan becomeundefinedand flow into the node Rspack config.Proposed fix
- const hasExplicitServerOutput = Object.prototype.hasOwnProperty.call( - options, - 'serverOutput' - ); - let resolvedServerOutput = pluginOptions.serverOutput; - if (!hasExplicitServerOutput) { - resolvedServerOutput = - serverModuleFormat === 'cjs' ? 'commonjs' : 'module'; - } + const hasExplicitServerOutput = options.serverOutput !== undefined; + const resolvedServerOutput = hasExplicitServerOutput + ? options.serverOutput + : serverModuleFormat === 'cjs' + ? 'commonjs' + : 'module';🤖 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/index.ts` around lines 231 - 239, Treat serverOutput: undefined the same as an omitted option in the server output resolution logic. Update the hasExplicitServerOutput check in the src/index.ts flow around pluginOptions.serverOutput so that undefined does not count as explicitly set, and keep the fallback based on serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the Node Rspack config. Reference the server output resolution branch and the resolvedServerOutput assignment when making the fix.src/dev-generation.ts-551-553 (1)
551-553: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep
load()asynchronous in the ready path.
selectBuild()can throw here, which escapes synchronously instead of rejecting the promised API. That makesloadReactRouterServerBuild()behave differently betweenstartingandreadystates for the same missing-build failure.Proposed fix
if (state.kind === 'ready') { - return Promise.resolve(selectBuild(state.committed, entryName)); + return Promise.resolve().then(() => + selectBuild(state.committed, entryName) + ); }🤖 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-generation.ts` around lines 551 - 553, The `load()` implementation in `src/dev-generation.ts` should stay asynchronous in the `state.kind === 'ready'` branch, because calling `selectBuild()` directly can throw synchronously instead of returning a rejected promise. Update the ready-path logic around `selectBuild()` and `loadReactRouterServerBuild()` so missing-build failures are handled through promise rejection consistently with the `starting` state.src/typegen.ts-12-19 (1)
12-19: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winUse the local
react-routerCLI and keep watch failures visible.
npx --yescan fall back to a registry install when the binary is missing; run the app’s installed CLI directly so typegen stays on the pinned version.- The watch process currently swallows every rejection. Only ignore shutdown, or restart/log unexpected exits so typegen doesn’t die silently.
🤖 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/typegen.ts` around lines 12 - 19, The typegen launcher in typegen.ts should use the project’s locally installed react-router CLI instead of npx --yes, so update the execa invocation to call the pinned binary directly from the app dependencies. In the same typegen/watch flow, avoid swallowing every rejected process exit: adjust the process handling around the execa call so only intentional shutdown is ignored, and unexpected failures from the watch process are surfaced or restarted/logged. Use the existing typegen process setup and its process variable as the place to make these changes.
🤖 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.
Inline comments:
In `@src/build-output-transforms.ts`:
- Around line 123-136: The fallback in build-output-transforms should stay
bundle-scoped even when latestServerManifestsByBundleId is not yet populated. In
the manifest selection logic around getLatestServerManifest,
getLatestServerManifestByBundleId, and getReactRouterManifestForDev, ensure the
cold-path dev manifest returned for
virtual/react-router/server-manifest-${bundleId} is filtered to the current
bundleId before it is used. Keep the per-bundle behavior consistent with the
virtual module wiring from src/index.ts so the fallback never embeds routes from
other bundles.
In `@src/dev-runtime-artifacts.ts`:
- Around line 145-174: The assertBuildMatchesManifest check only verifies routes
present in ReactRouterDevManifest, so stale ServerBuild entries for deleted
routes can slip through. Update assertBuildMatchesManifest to also compare
build.routes against manifest.routes and throw when the evaluated server build
contains any extra route IDs not in the manifest, alongside the existing
missing-route and loader/action export checks. Use the existing entryName,
build, manifest, and routeId logic to keep the validation in one place.
In `@src/dev-runtime-session.ts`:
- Around line 62-69: The completeClose flow in dev-runtime-session skips
closeBinding() when the session is already in the closing state, so successful
server.close() calls never fully clean up the runtime. Update completeClose to
invoke closeBinding(binding) for the successful closing path as well as active,
while still guarding with isCurrentBinding(binding) and preserving the
transition to idle; apply the same fix in the other matching close path around
the later close handler.
In `@src/prerender-build.ts`:
- Around line 175-177: The prerender write path logic in the output path
construction must prevent traversal outside clientBuildDir. Update the path
handling in prerender-build.ts around the outputPath/mkdir/writeFile flow to
validate normalizedPath segments before joining, reject any .. or
backslash-based traversal, and assert the final resolved path stays under
clientBuildDir. Apply the same safeguard to the other prerender output write
sites mentioned by the build flow helpers so all generated files remain confined
to the client build directory.
- Around line 496-498: The cleanup in enqueue path handling creates an
unconsumed promise from task.finally, which can surface prerender failures as
unhandled rejections; update the logic around enqueue, pending, and task so the
cleanup promise is handled explicitly. Use the task promise’s then with both
fulfillment and rejection handlers, or attach a catch to the promise returned by
finally, while still removing the task from pending.
- Around line 549-607: The prerender/build-request flow in prerender-build.ts
leaves process.env.IS_RR_BUILD_REQUEST mutated after the work completes. In the
block around resolveServerBuildModule, createRequestHandler, runPrerenderPaths,
and handleSpaMode, save the previous IS_RR_BUILD_REQUEST value before setting it
and restore it in a finally block after all prerender/Spa handling finishes.
This should be done around the existing build request logic so buildEnd and
subsequent runs/tests see the original process-global state.
- Around line 211-225: Escape the redirect target before interpolating it into
the fallback HTML in the redirect handling block so untrusted `Location` values
cannot inject markup or attributes. Update the logic around
`redirectStatusCodes`, `response.headers.get('Location')`, and the HTML template
in `src/prerender-build.ts` to use a safely escaped/sanitized version for the
`<title>`, `meta refresh`, and `<a href>`/link text. Keep `normalizedPath`
as-is, but ensure every use of the redirect destination is HTML- and
attribute-safe.
In `@src/route-chunks.ts`:
- Around line 211-216: The shared side-effect collection in route chunking is
incorrectly treating directive prologues like "use client" and "use strict" as
blocking statements. Update the filtering logic in the shared side-effect scan
and the related `topLevelNonModuleStatements` handling so `ExpressionStatement`s
that are directive prologues are excluded, while real side-effectful expressions
remain included. Use the existing `sharedTopLevelSideEffects` and export
analysis paths to ensure `splitRouteModules: "enforce"` only rejects genuinely
shared side effects.
In `@src/route-export-resolution.ts`:
- Around line 160-182: Update resolvePackageExports and related callers so
subpath imports are handled correctly: when packageJson.exports is absent, do
not always fall back to module/main for every packageSubpath, and make sure
pkg/subpath is not resolved as the package entry. In the exports branch, enhance
the lookup logic in resolvePackageExports to support non-exact export shapes
such as pattern mappings like "./*" instead of only direct packageSubpath keys.
Then adjust resolveExportAllModule so a null result from resolvePackageExports
is treated as “not matched yet” and it can still fall back to
createRequire(...).resolve() before rejecting the import.
In `@src/typegen.ts`:
- Around line 22-30: The `typegen --watch` promise handling in `typegenProcess`
is swallowing every rejection, which hides unexpected watcher exits. Update the
`.catch` path so it only ignores the intentional shutdown case from server
teardown, and for any other rejection in `src/typegen.ts` surface the failure by
logging or rethrowing it. Use the existing `typegenProcess` cleanup in
`.finally()` to keep the process reset behavior intact while making crashes or
invalid config failures visible.
In `@src/virtual-modules.ts`:
- Around line 11-19: The virtual module ID validation in the module path builder
does not block backslash separators, so a Windows-style traversal pattern can
slip through. Update the validation in the relativeId/segments check to reject
any segment containing backslashes before returning the node_modules path,
keeping the existing dot-segment and empty-segment checks in place.
---
Minor comments:
In `@examples/custom-node-server/scripts/smoke-production.mjs`:
- Around line 64-70: The teardown in smoke-production.mjs waits on once(child,
'exit') only after sending SIGTERM, which can miss an exit that already happened
and force the full timeout. Update the child process handling around the
existing child.kill and Promise.race flow by creating the exit promise before
signaling, and guard the wait with child.exitCode so you skip the timeout path
when the process has already exited.
In `@README.md`:
- Around line 107-112: The README documentation for parallelTransforms is
contradictory about its default value. Update the public API docs so the JSDoc
on parallelTransforms and the defaults section describe the same behavior, using
one consistent default representation in both places. Refer to
parallelTransforms in the options docs and the defaults section, and make sure
the wording matches whether worker execution is enabled implicitly or via an
explicit true default.
- Line 527: The startup example in startServer() currently only logs failures
with console.error and leaves the process running, which conflicts with the
documented requirement for unrecoverable startup failures. Update the startup
handling so the catch path for startServer() both logs the error and terminates
the process with a non-zero exit code, keeping the example from continuing in a
broken state.
In `@rslib.config.ts`:
- Around line 23-28: The CJS source.entry map is missing the route transform
worker entry, so CJS builds won’t emit the asset that createWorkerUrl()
resolves. Update the source.entry block in rslib.config.ts to include the
parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.
In `@scripts/benchmark/fixture.mjs`:
- Around line 648-658: The largeConfig override path in
generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which
later break generators that use workers, localeFiles, cssModules, svgAssets,
utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or
import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.
In `@src/dev-generation.ts`:
- Around line 551-553: The `load()` implementation in `src/dev-generation.ts`
should stay asynchronous in the `state.kind === 'ready'` branch, because calling
`selectBuild()` directly can throw synchronously instead of returning a rejected
promise. Update the ready-path logic around `selectBuild()` and
`loadReactRouterServerBuild()` so missing-build failures are handled through
promise rejection consistently with the `starting` state.
In `@src/index.ts`:
- Around line 231-239: Treat serverOutput: undefined the same as an omitted
option in the server output resolution logic. Update the hasExplicitServerOutput
check in the src/index.ts flow around pluginOptions.serverOutput so that
undefined does not count as explicitly set, and keep the fallback based on
serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the
Node Rspack config. Reference the server output resolution branch and the
resolvedServerOutput assignment when making the fix.
In `@src/route-export-pruning.ts`:
- Around line 544-549: The re-export cleanup in the export pruning flow is
incorrectly treating sourced re-export names as local bindings. In the logic
around the export specifier handling in route-export-pruning and the sets
removedExportLocalNames / removedExportReferencedNames, only add names that are
actually declared locally in the file; for `export { loader as clientLoader }
from "./x"`, avoid recording `specifier.local.name` as a local binding so later
assignment cleanup does not delete unrelated `loader.*` statements. Use the
existing export-specifier and assignment-pruning checks to distinguish sourced
re-exports from true local exports before adding names to those tracking sets.
In `@src/typegen.ts`:
- Around line 12-19: The typegen launcher in typegen.ts should use the project’s
locally installed react-router CLI instead of npx --yes, so update the execa
invocation to call the pinned binary directly from the app dependencies. In the
same typegen/watch flow, avoid swallowing every rejected process exit: adjust
the process handling around the execa call so only intentional shutdown is
ignored, and unexpected failures from the watch process are surfaced or
restarted/logged. Use the existing typegen process setup and its process
variable as the place to make these changes.
In `@tests/index.test.ts`:
- Around line 106-139: Restore the shared fs.existsSync mock in this test by
capturing the previous mock implementation before overriding it and reinstating
that exact implementation in the finally block. Update the test around
createStubRsbuild and pluginReactRouter so the temporary behavior used for the
watchFiles assertion does not leak into later tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5108dcda-f5f7-41c8-9159-04e0bd83d220
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (124)
.changeset/bright-routes-run.md.changeset/coherent-dev-generations.md.changeset/lazy-entry-hydration.md.changeset/quiet-topology-order.md.changeset/sharp-routes-heal.md.changeset/warm-watchers-rest.mdREADME.mdbenchmarks/README.mdconfig/package.jsonconfig/rslib.config.tsdocs/coherent-dev-generations.mdexamples/client-only/package.jsonexamples/cloudflare/package.jsonexamples/custom-node-server/app/load-context.d.tsexamples/custom-node-server/app/routes/projects/edit.tsxexamples/custom-node-server/app/routes/projects/index.tsxexamples/custom-node-server/app/routes/projects/layout.tsxexamples/custom-node-server/app/routes/projects/project.tsxexamples/custom-node-server/app/routes/projects/settings.tsxexamples/custom-node-server/package.jsonexamples/custom-node-server/playwright.config.tsexamples/custom-node-server/rsbuild.config.tsexamples/custom-node-server/scripts/smoke-production.mjsexamples/custom-node-server/server.jsexamples/custom-node-server/server/index.tsexamples/custom-node-server/tsconfig.jsonexamples/default-template/app/dev-routes.tsexamples/default-template/app/routes.tsexamples/default-template/package.jsonexamples/default-template/playwright.config.tsexamples/default-template/rsbuild.config.tsexamples/default-template/tests/e2e/dev-route-watch.test.tsexamples/default-template/tests/e2e/lazy-compilation.test.tsexamples/epic-stack/package.jsonexamples/federation/epic-stack-remote/package.jsonexamples/federation/epic-stack/package.jsonexamples/prerender/package.jsonexamples/spa-mode/package.jsonpackage.jsonrslib.config.tsscripts/bench-builds.mjsscripts/benchmark/fixture.mjsscripts/compare-benchmarks.mjsscripts/test-package-interop.mjssrc/babel.tssrc/bounded-cache.tssrc/build-manifest.tssrc/build-output-transforms.tssrc/concurrency.tssrc/constants.tssrc/dev-generation.tssrc/dev-runtime-artifacts.tssrc/dev-runtime-compilation.tssrc/dev-runtime-controller.tssrc/dev-runtime-session.tssrc/dev-server.tssrc/export-utils.tssrc/index.tssrc/lazy-compilation.tssrc/manifest.tssrc/modify-browser-manifest.tssrc/parallel-route-transform-protocol.tssrc/parallel-route-transform-worker.tssrc/parallel-route-transforms.tssrc/performance.tssrc/plugin-utils.tssrc/prerender-build.tssrc/prerender.tssrc/react-router-config.tssrc/route-artifacts.tssrc/route-ast.tssrc/route-chunks.tssrc/route-component-transform.tssrc/route-export-pruning.tssrc/route-export-resolution.tssrc/route-transform-tasks.tssrc/route-watch.tssrc/server-build-plan.tssrc/server-utils.tssrc/templates/entry.server.tsxsrc/typegen.tssrc/types.tssrc/virtual-modules.tssrc/warnings/warn-on-client-source-maps.tssrc/yuku.tstests/benchmark-fixture.test.tstests/bounded-cache.test.tstests/build-manifest.test.tstests/build-output-transforms.test.tstests/client-modules.test.tstests/dev-generation-multi-entry.test.tstests/dev-generation.test.tstests/dev-runtime-controller.test.tstests/dev-runtime.integration.test.tstests/dev-server.test.tstests/export-utils.test.tstests/features.test.tstests/fixtures/dev-runtime/app/entry.client.tsxtests/fixtures/dev-runtime/app/entry.server.tsxtests/fixtures/dev-runtime/app/root.tsxtests/fixtures/dev-runtime/app/routes.tstests/fixtures/dev-runtime/app/routes/index.tsxtests/fixtures/dev-runtime/app/routes/other.tsxtests/fixtures/dev-runtime/react-router.config.tstests/fixtures/dev-runtime/server/index.tstests/index.test.tstests/lazy-compilation.test.tstests/manifest-split-route-modules.test.tstests/manifest.test.tstests/modify-browser-manifest.test.tstests/parallel-route-transforms.test.tstests/performance.test.tstests/plugin-utils.test.tstests/prerender.test.tstests/react-router-config.test.tstests/remove-exports.test.tstests/route-artifacts.test.tstests/route-chunks-cache.test.tstests/route-chunks.test.tstests/route-watch.test.tstests/server-build-plan.test.tstests/server-utils.test.tstests/setup.tstests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (2)
- examples/custom-node-server/server/index.ts
- src/babel.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 11
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (10)
examples/custom-node-server/scripts/smoke-production.mjs-64-70 (1)
64-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winAvoid the fixed 5s teardown delay after early exits.
If the child has already exited before this block runs,
once(child, 'exit')subscribes too late and the script always waits the full timeout. Capture the exit promise before signaling, and skip the wait whenexitCodeis already set.Proposed fix
} finally { - child.kill('SIGTERM'); - await Promise.race([ - once(child, 'exit'), - new Promise(resolve => setTimeout(resolve, 5_000)), - ]); - if (child.exitCode === null) { - child.kill('SIGKILL'); - } + if (child.exitCode === null) { + const exited = once(child, 'exit'); + child.kill('SIGTERM'); + await Promise.race([ + exited, + new Promise(resolve => setTimeout(resolve, 5_000)), + ]); + if (child.exitCode === null) { + child.kill('SIGKILL'); + } + } }🤖 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 `@examples/custom-node-server/scripts/smoke-production.mjs` around lines 64 - 70, The teardown in smoke-production.mjs waits on once(child, 'exit') only after sending SIGTERM, which can miss an exit that already happened and force the full timeout. Update the child process handling around the existing child.kill and Promise.race flow by creating the exit promise before signaling, and guard the wait with child.exitCode so you skip the timeout path when the process has already exited.README.md-107-112 (1)
107-112: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDocument one
parallelTransformsdefault.Line 110 says
@default true, but the defaults section on Lines 309-314 says the option defaults toundefinedwith worker execution enabled implicitly. Pick one representation so the public API contract is not self-contradictory.🤖 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 `@README.md` around lines 107 - 112, The README documentation for parallelTransforms is contradictory about its default value. Update the public API docs so the JSDoc on parallelTransforms and the defaults section describe the same behavior, using one consistent default representation in both places. Refer to parallelTransforms in the options docs and the defaults section, and make sure the wording matches whether worker execution is enabled implicitly or via an explicit true default.README.md-527-527 (1)
527-527: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winExit on unrecoverable startup failure.
Line 527 only logs boot errors, but the section above explicitly requires a clean process restart after startup failure. Exit non-zero after logging so the example does not stay alive in a broken state.
Proposed fix
-startServer().catch(console.error); +startServer().catch(error => { + console.error(error); + process.exit(1); +});🤖 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 `@README.md` at line 527, The startup example in startServer() currently only logs failures with console.error and leaves the process running, which conflicts with the documented requirement for unrecoverable startup failures. Update the startup handling so the catch path for startServer() both logs the error and terminates the process with a non-zero exit code, keeping the example from continuing in a broken state.scripts/benchmark/fixture.mjs-648-658 (1)
648-658: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFail fast on invalid
largeConfigcounts.The new
largeConfigoverride path is unvalidated, but several generators later use counts likeworkers,localeFiles,cssModules,svgAssets,utilitiesPerRoute, andpayloadEntriesPerComponentin%/division and import-path generation. Passing0currently emits broken fixture code instead of rejecting the config up front.🛠️ Proposed fix
+const assertPositiveInteger = (name, value) => { + if (!Number.isInteger(value) || value < 1) { + throw new Error( + `${name} must be a positive integer for the large benchmark fixture.` + ); + } +}; + const generateLargeFixture = async ({ root, routeCount, variant, @@ }) => { const config = normalizeLargeConfig(routeCount, largeConfig); + for (const [name, value] of Object.entries({ + routes: config.routes, + componentsPerRoute: config.componentsPerRoute, + utilitiesPerRoute: config.utilitiesPerRoute, + lazyModulesPerRoute: config.lazyModulesPerRoute, + workers: config.workers, + svgAssets: config.svgAssets, + cssModules: config.cssModules, + localeFiles: config.localeFiles, + payloadEntriesPerComponent: config.payloadEntriesPerComponent, + })) { + assertPositiveInteger(name, value); + } const isSpa = variant === 'spa';Also applies to: 814-834
🤖 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 `@scripts/benchmark/fixture.mjs` around lines 648 - 658, The largeConfig override path in generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which later break generators that use workers, localeFiles, cssModules, svgAssets, utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or import-path logic. Add upfront validation for these count fields in normalizeLargeConfig (or immediately after calling it in generateLargeFixture) and reject non-positive values with a clear error before any fixture generation starts.rslib.config.ts-23-28 (1)
23-28: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winEmit the route transform worker from the CJS build too.
createWorkerUrl()always resolves./parallel-route-transform-worker.js, but the CJSsource.entryblock doesn’t include that file. CJS consumers won’t get the worker asset, so parallel transforms will fall back to the serial path.Proposed fix
source: { entry: { index: './src/index.ts', + 'parallel-route-transform-worker': + './src/parallel-route-transform-worker.ts', 'templates/entry.server': './src/templates/entry.server.tsx', 'templates/entry.client': './src/templates/entry.client.tsx', },🤖 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 `@rslib.config.ts` around lines 23 - 28, The CJS source.entry map is missing the route transform worker entry, so CJS builds won’t emit the asset that createWorkerUrl() resolves. Update the source.entry block in rslib.config.ts to include the parallel route transform worker alongside the existing index, templates/entry.server, and templates/entry.client entries, so CJS consumers also receive parallel-route-transform-worker.js.src/route-export-pruning.ts-544-549 (1)
544-549: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDon’t track sourced re-export names as local bindings.
For
export { loader as clientLoader } from "./x",specifier.local.nameis the imported name from the source module, not a local binding. Adding it toremovedExportLocalNamescan make the later assignment cleanup remove unrelated localloader.* = ...statements.Proposed fix
const exportedName = getExportedName(specifier); if (exportedName && exportsToRemoveSet.has(exportedName)) { exportsChanged = true; - if (specifier.local?.name) { + if (!statement.source && specifier.local?.name) { removedExportLocalNames.add(specifier.local.name); removedExportReferencedNames.add(specifier.local.name); } return false; }🤖 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/route-export-pruning.ts` around lines 544 - 549, The re-export cleanup in the export pruning flow is incorrectly treating sourced re-export names as local bindings. In the logic around the export specifier handling in route-export-pruning and the sets removedExportLocalNames / removedExportReferencedNames, only add names that are actually declared locally in the file; for `export { loader as clientLoader } from "./x"`, avoid recording `specifier.local.name` as a local binding so later assignment cleanup does not delete unrelated `loader.*` statements. Use the existing export-specifier and assignment-pruning checks to distinguish sourced re-exports from true local exports before adding names to those tracking sets.tests/index.test.ts-106-139 (1)
106-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRestore the previous
existsSyncmock implementation.Line 111 overrides a shared mock, but Line 138 leaves it hardcoded to
true. Capture and restore the previous implementation so this test cannot leak filesystem behavior into later cases.🤖 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 `@tests/index.test.ts` around lines 106 - 139, Restore the shared fs.existsSync mock in this test by capturing the previous mock implementation before overriding it and reinstating that exact implementation in the finally block. Update the test around createStubRsbuild and pluginReactRouter so the temporary behavior used for the watchFiles assertion does not leak into later tests.src/index.ts-231-239 (1)
231-239: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTreat
serverOutput: undefinedas omitted.
hasOwnPropertymakes{ serverOutput: undefined }explicit, soresolvedServerOutputcan becomeundefinedand flow into the node Rspack config.Proposed fix
- const hasExplicitServerOutput = Object.prototype.hasOwnProperty.call( - options, - 'serverOutput' - ); - let resolvedServerOutput = pluginOptions.serverOutput; - if (!hasExplicitServerOutput) { - resolvedServerOutput = - serverModuleFormat === 'cjs' ? 'commonjs' : 'module'; - } + const hasExplicitServerOutput = options.serverOutput !== undefined; + const resolvedServerOutput = hasExplicitServerOutput + ? options.serverOutput + : serverModuleFormat === 'cjs' + ? 'commonjs' + : 'module';🤖 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/index.ts` around lines 231 - 239, Treat serverOutput: undefined the same as an omitted option in the server output resolution logic. Update the hasExplicitServerOutput check in the src/index.ts flow around pluginOptions.serverOutput so that undefined does not count as explicitly set, and keep the fallback based on serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the Node Rspack config. Reference the server output resolution branch and the resolvedServerOutput assignment when making the fix.src/dev-generation.ts-551-553 (1)
551-553: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep
load()asynchronous in the ready path.
selectBuild()can throw here, which escapes synchronously instead of rejecting the promised API. That makesloadReactRouterServerBuild()behave differently betweenstartingandreadystates for the same missing-build failure.Proposed fix
if (state.kind === 'ready') { - return Promise.resolve(selectBuild(state.committed, entryName)); + return Promise.resolve().then(() => + selectBuild(state.committed, entryName) + ); }🤖 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-generation.ts` around lines 551 - 553, The `load()` implementation in `src/dev-generation.ts` should stay asynchronous in the `state.kind === 'ready'` branch, because calling `selectBuild()` directly can throw synchronously instead of returning a rejected promise. Update the ready-path logic around `selectBuild()` and `loadReactRouterServerBuild()` so missing-build failures are handled through promise rejection consistently with the `starting` state.src/typegen.ts-12-19 (1)
12-19: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winUse the local
react-routerCLI and keep watch failures visible.
npx --yescan fall back to a registry install when the binary is missing; run the app’s installed CLI directly so typegen stays on the pinned version.- The watch process currently swallows every rejection. Only ignore shutdown, or restart/log unexpected exits so typegen doesn’t die silently.
🤖 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/typegen.ts` around lines 12 - 19, The typegen launcher in typegen.ts should use the project’s locally installed react-router CLI instead of npx --yes, so update the execa invocation to call the pinned binary directly from the app dependencies. In the same typegen/watch flow, avoid swallowing every rejected process exit: adjust the process handling around the execa call so only intentional shutdown is ignored, and unexpected failures from the watch process are surfaced or restarted/logged. Use the existing typegen process setup and its process variable as the place to make these changes.
🤖 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.
Inline comments:
In `@src/build-output-transforms.ts`:
- Around line 123-136: The fallback in build-output-transforms should stay
bundle-scoped even when latestServerManifestsByBundleId is not yet populated. In
the manifest selection logic around getLatestServerManifest,
getLatestServerManifestByBundleId, and getReactRouterManifestForDev, ensure the
cold-path dev manifest returned for
virtual/react-router/server-manifest-${bundleId} is filtered to the current
bundleId before it is used. Keep the per-bundle behavior consistent with the
virtual module wiring from src/index.ts so the fallback never embeds routes from
other bundles.
In `@src/dev-runtime-artifacts.ts`:
- Around line 145-174: The assertBuildMatchesManifest check only verifies routes
present in ReactRouterDevManifest, so stale ServerBuild entries for deleted
routes can slip through. Update assertBuildMatchesManifest to also compare
build.routes against manifest.routes and throw when the evaluated server build
contains any extra route IDs not in the manifest, alongside the existing
missing-route and loader/action export checks. Use the existing entryName,
build, manifest, and routeId logic to keep the validation in one place.
In `@src/dev-runtime-session.ts`:
- Around line 62-69: The completeClose flow in dev-runtime-session skips
closeBinding() when the session is already in the closing state, so successful
server.close() calls never fully clean up the runtime. Update completeClose to
invoke closeBinding(binding) for the successful closing path as well as active,
while still guarding with isCurrentBinding(binding) and preserving the
transition to idle; apply the same fix in the other matching close path around
the later close handler.
In `@src/prerender-build.ts`:
- Around line 175-177: The prerender write path logic in the output path
construction must prevent traversal outside clientBuildDir. Update the path
handling in prerender-build.ts around the outputPath/mkdir/writeFile flow to
validate normalizedPath segments before joining, reject any .. or
backslash-based traversal, and assert the final resolved path stays under
clientBuildDir. Apply the same safeguard to the other prerender output write
sites mentioned by the build flow helpers so all generated files remain confined
to the client build directory.
- Around line 496-498: The cleanup in enqueue path handling creates an
unconsumed promise from task.finally, which can surface prerender failures as
unhandled rejections; update the logic around enqueue, pending, and task so the
cleanup promise is handled explicitly. Use the task promise’s then with both
fulfillment and rejection handlers, or attach a catch to the promise returned by
finally, while still removing the task from pending.
- Around line 549-607: The prerender/build-request flow in prerender-build.ts
leaves process.env.IS_RR_BUILD_REQUEST mutated after the work completes. In the
block around resolveServerBuildModule, createRequestHandler, runPrerenderPaths,
and handleSpaMode, save the previous IS_RR_BUILD_REQUEST value before setting it
and restore it in a finally block after all prerender/Spa handling finishes.
This should be done around the existing build request logic so buildEnd and
subsequent runs/tests see the original process-global state.
- Around line 211-225: Escape the redirect target before interpolating it into
the fallback HTML in the redirect handling block so untrusted `Location` values
cannot inject markup or attributes. Update the logic around
`redirectStatusCodes`, `response.headers.get('Location')`, and the HTML template
in `src/prerender-build.ts` to use a safely escaped/sanitized version for the
`<title>`, `meta refresh`, and `<a href>`/link text. Keep `normalizedPath`
as-is, but ensure every use of the redirect destination is HTML- and
attribute-safe.
In `@src/route-chunks.ts`:
- Around line 211-216: The shared side-effect collection in route chunking is
incorrectly treating directive prologues like "use client" and "use strict" as
blocking statements. Update the filtering logic in the shared side-effect scan
and the related `topLevelNonModuleStatements` handling so `ExpressionStatement`s
that are directive prologues are excluded, while real side-effectful expressions
remain included. Use the existing `sharedTopLevelSideEffects` and export
analysis paths to ensure `splitRouteModules: "enforce"` only rejects genuinely
shared side effects.
In `@src/route-export-resolution.ts`:
- Around line 160-182: Update resolvePackageExports and related callers so
subpath imports are handled correctly: when packageJson.exports is absent, do
not always fall back to module/main for every packageSubpath, and make sure
pkg/subpath is not resolved as the package entry. In the exports branch, enhance
the lookup logic in resolvePackageExports to support non-exact export shapes
such as pattern mappings like "./*" instead of only direct packageSubpath keys.
Then adjust resolveExportAllModule so a null result from resolvePackageExports
is treated as “not matched yet” and it can still fall back to
createRequire(...).resolve() before rejecting the import.
In `@src/typegen.ts`:
- Around line 22-30: The `typegen --watch` promise handling in `typegenProcess`
is swallowing every rejection, which hides unexpected watcher exits. Update the
`.catch` path so it only ignores the intentional shutdown case from server
teardown, and for any other rejection in `src/typegen.ts` surface the failure by
logging or rethrowing it. Use the existing `typegenProcess` cleanup in
`.finally()` to keep the process reset behavior intact while making crashes or
invalid config failures visible.
In `@src/virtual-modules.ts`:
- Around line 11-19: The virtual module ID validation in the module path builder
does not block backslash separators, so a Windows-style traversal pattern can
slip through. Update the validation in the relativeId/segments check to reject
any segment containing backslashes before returning the node_modules path,
keeping the existing dot-segment and empty-segment checks in place.
---
Minor comments:
In `@examples/custom-node-server/scripts/smoke-production.mjs`:
- Around line 64-70: The teardown in smoke-production.mjs waits on once(child,
'exit') only after sending SIGTERM, which can miss an exit that already happened
and force the full timeout. Update the child process handling around the
existing child.kill and Promise.race flow by creating the exit promise before
signaling, and guard the wait with child.exitCode so you skip the timeout path
when the process has already exited.
In `@README.md`:
- Around line 107-112: The README documentation for parallelTransforms is
contradictory about its default value. Update the public API docs so the JSDoc
on parallelTransforms and the defaults section describe the same behavior, using
one consistent default representation in both places. Refer to
parallelTransforms in the options docs and the defaults section, and make sure
the wording matches whether worker execution is enabled implicitly or via an
explicit true default.
- Line 527: The startup example in startServer() currently only logs failures
with console.error and leaves the process running, which conflicts with the
documented requirement for unrecoverable startup failures. Update the startup
handling so the catch path for startServer() both logs the error and terminates
the process with a non-zero exit code, keeping the example from continuing in a
broken state.
In `@rslib.config.ts`:
- Around line 23-28: The CJS source.entry map is missing the route transform
worker entry, so CJS builds won’t emit the asset that createWorkerUrl()
resolves. Update the source.entry block in rslib.config.ts to include the
parallel route transform worker alongside the existing index,
templates/entry.server, and templates/entry.client entries, so CJS consumers
also receive parallel-route-transform-worker.js.
In `@scripts/benchmark/fixture.mjs`:
- Around line 648-658: The largeConfig override path in
generateLargeFixture/normalizeLargeConfig accepts invalid zero values, which
later break generators that use workers, localeFiles, cssModules, svgAssets,
utilitiesPerRoute, and payloadEntriesPerComponent in division, modulo, or
import-path logic. Add upfront validation for these count fields in
normalizeLargeConfig (or immediately after calling it in generateLargeFixture)
and reject non-positive values with a clear error before any fixture generation
starts.
In `@src/dev-generation.ts`:
- Around line 551-553: The `load()` implementation in `src/dev-generation.ts`
should stay asynchronous in the `state.kind === 'ready'` branch, because calling
`selectBuild()` directly can throw synchronously instead of returning a rejected
promise. Update the ready-path logic around `selectBuild()` and
`loadReactRouterServerBuild()` so missing-build failures are handled through
promise rejection consistently with the `starting` state.
In `@src/index.ts`:
- Around line 231-239: Treat serverOutput: undefined the same as an omitted
option in the server output resolution logic. Update the hasExplicitServerOutput
check in the src/index.ts flow around pluginOptions.serverOutput so that
undefined does not count as explicitly set, and keep the fallback based on
serverModuleFormat ('cjs' => commonjs, otherwise module) from flowing into the
Node Rspack config. Reference the server output resolution branch and the
resolvedServerOutput assignment when making the fix.
In `@src/route-export-pruning.ts`:
- Around line 544-549: The re-export cleanup in the export pruning flow is
incorrectly treating sourced re-export names as local bindings. In the logic
around the export specifier handling in route-export-pruning and the sets
removedExportLocalNames / removedExportReferencedNames, only add names that are
actually declared locally in the file; for `export { loader as clientLoader }
from "./x"`, avoid recording `specifier.local.name` as a local binding so later
assignment cleanup does not delete unrelated `loader.*` statements. Use the
existing export-specifier and assignment-pruning checks to distinguish sourced
re-exports from true local exports before adding names to those tracking sets.
In `@src/typegen.ts`:
- Around line 12-19: The typegen launcher in typegen.ts should use the project’s
locally installed react-router CLI instead of npx --yes, so update the execa
invocation to call the pinned binary directly from the app dependencies. In the
same typegen/watch flow, avoid swallowing every rejected process exit: adjust
the process handling around the execa call so only intentional shutdown is
ignored, and unexpected failures from the watch process are surfaced or
restarted/logged. Use the existing typegen process setup and its process
variable as the place to make these changes.
In `@tests/index.test.ts`:
- Around line 106-139: Restore the shared fs.existsSync mock in this test by
capturing the previous mock implementation before overriding it and reinstating
that exact implementation in the finally block. Update the test around
createStubRsbuild and pluginReactRouter so the temporary behavior used for the
watchFiles assertion does not leak into later tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5108dcda-f5f7-41c8-9159-04e0bd83d220
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (124)
.changeset/bright-routes-run.md.changeset/coherent-dev-generations.md.changeset/lazy-entry-hydration.md.changeset/quiet-topology-order.md.changeset/sharp-routes-heal.md.changeset/warm-watchers-rest.mdREADME.mdbenchmarks/README.mdconfig/package.jsonconfig/rslib.config.tsdocs/coherent-dev-generations.mdexamples/client-only/package.jsonexamples/cloudflare/package.jsonexamples/custom-node-server/app/load-context.d.tsexamples/custom-node-server/app/routes/projects/edit.tsxexamples/custom-node-server/app/routes/projects/index.tsxexamples/custom-node-server/app/routes/projects/layout.tsxexamples/custom-node-server/app/routes/projects/project.tsxexamples/custom-node-server/app/routes/projects/settings.tsxexamples/custom-node-server/package.jsonexamples/custom-node-server/playwright.config.tsexamples/custom-node-server/rsbuild.config.tsexamples/custom-node-server/scripts/smoke-production.mjsexamples/custom-node-server/server.jsexamples/custom-node-server/server/index.tsexamples/custom-node-server/tsconfig.jsonexamples/default-template/app/dev-routes.tsexamples/default-template/app/routes.tsexamples/default-template/package.jsonexamples/default-template/playwright.config.tsexamples/default-template/rsbuild.config.tsexamples/default-template/tests/e2e/dev-route-watch.test.tsexamples/default-template/tests/e2e/lazy-compilation.test.tsexamples/epic-stack/package.jsonexamples/federation/epic-stack-remote/package.jsonexamples/federation/epic-stack/package.jsonexamples/prerender/package.jsonexamples/spa-mode/package.jsonpackage.jsonrslib.config.tsscripts/bench-builds.mjsscripts/benchmark/fixture.mjsscripts/compare-benchmarks.mjsscripts/test-package-interop.mjssrc/babel.tssrc/bounded-cache.tssrc/build-manifest.tssrc/build-output-transforms.tssrc/concurrency.tssrc/constants.tssrc/dev-generation.tssrc/dev-runtime-artifacts.tssrc/dev-runtime-compilation.tssrc/dev-runtime-controller.tssrc/dev-runtime-session.tssrc/dev-server.tssrc/export-utils.tssrc/index.tssrc/lazy-compilation.tssrc/manifest.tssrc/modify-browser-manifest.tssrc/parallel-route-transform-protocol.tssrc/parallel-route-transform-worker.tssrc/parallel-route-transforms.tssrc/performance.tssrc/plugin-utils.tssrc/prerender-build.tssrc/prerender.tssrc/react-router-config.tssrc/route-artifacts.tssrc/route-ast.tssrc/route-chunks.tssrc/route-component-transform.tssrc/route-export-pruning.tssrc/route-export-resolution.tssrc/route-transform-tasks.tssrc/route-watch.tssrc/server-build-plan.tssrc/server-utils.tssrc/templates/entry.server.tsxsrc/typegen.tssrc/types.tssrc/virtual-modules.tssrc/warnings/warn-on-client-source-maps.tssrc/yuku.tstests/benchmark-fixture.test.tstests/bounded-cache.test.tstests/build-manifest.test.tstests/build-output-transforms.test.tstests/client-modules.test.tstests/dev-generation-multi-entry.test.tstests/dev-generation.test.tstests/dev-runtime-controller.test.tstests/dev-runtime.integration.test.tstests/dev-server.test.tstests/export-utils.test.tstests/features.test.tstests/fixtures/dev-runtime/app/entry.client.tsxtests/fixtures/dev-runtime/app/entry.server.tsxtests/fixtures/dev-runtime/app/root.tsxtests/fixtures/dev-runtime/app/routes.tstests/fixtures/dev-runtime/app/routes/index.tsxtests/fixtures/dev-runtime/app/routes/other.tsxtests/fixtures/dev-runtime/react-router.config.tstests/fixtures/dev-runtime/server/index.tstests/index.test.tstests/lazy-compilation.test.tstests/manifest-split-route-modules.test.tstests/manifest.test.tstests/modify-browser-manifest.test.tstests/parallel-route-transforms.test.tstests/performance.test.tstests/plugin-utils.test.tstests/prerender.test.tstests/react-router-config.test.tstests/remove-exports.test.tstests/route-artifacts.test.tstests/route-chunks-cache.test.tstests/route-chunks.test.tstests/route-watch.test.tstests/server-build-plan.test.tstests/server-utils.test.tstests/setup.tstests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (2)
- examples/custom-node-server/server/index.ts
- src/babel.ts
🛑 Comments failed to post (11)
src/build-output-transforms.ts (1)
123-136: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Keep bundle-specific server-manifest fallbacks bundle-scoped.
When this transform handles
virtual/react-router/server-manifest-${bundleId}beforestageLatestManifests()has populatedlatestServerManifestsByBundleId, Lines 123-136 fall back togetReactRouterManifestForDev(routes, ...), which regenerates the full manifest. Insrc/index.ts, those virtual modules are wired into per-bundle server builds and later filtered by bundle, so a node-first dev compile can embed routes outside the current bundle until a rebuild corrects it. Please filter the cold-path fallback bybundleIdtoo, not just the cached path.Possible direction
- const manifest = + const baseManifest = (latestServerManifest ? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ?? latestServerManifest) : null) ?? (await getReactRouterManifestForDev( routes, pluginOptions, getClientStats(), appDirectory, getAssetPrefix(), routeChunkOptions )); + const manifest = + bundleId && routesByServerBundleId[bundleId] + ? { + ...baseManifest, + routes: Object.fromEntries( + Object.entries(baseManifest.routes).filter(([routeId]) => + routeId in routesByServerBundleId[bundleId] + ) + ), + } + : baseManifest;🤖 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/build-output-transforms.ts` around lines 123 - 136, The fallback in build-output-transforms should stay bundle-scoped even when latestServerManifestsByBundleId is not yet populated. In the manifest selection logic around getLatestServerManifest, getLatestServerManifestByBundleId, and getReactRouterManifestForDev, ensure the cold-path dev manifest returned for virtual/react-router/server-manifest-${bundleId} is filtered to the current bundleId before it is used. Keep the per-bundle behavior consistent with the virtual module wiring from src/index.ts so the fallback never embeds routes from other bundles.src/dev-runtime-artifacts.ts (1)
145-174: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Also reject server builds with extra route IDs.
This check only walks
manifest.routes. If the node build is stale and still contains a deleted route, validation passes and the mixed pair gets pinned anyway. Route removals are one of the mismatch cases this flow needs to catch.Suggested fix
const assertBuildMatchesManifest = ( entryName: string, build: ServerBuild, manifest: ReactRouterDevManifest ): void => { + const manifestRouteIds = new Set(Object.keys(manifest.routes)); + for (const routeId of Object.keys(build.routes)) { + if (!manifestRouteIds.has(routeId)) { + throw new Error( + `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} does not exist in the matching web manifest.` + ); + } + } + for (const [routeId, manifestRoute] of Object.entries(manifest.routes)) { const routeModule = build.routes[routeId]?.module;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const assertBuildMatchesManifest = ( entryName: string, build: ServerBuild, manifest: ReactRouterDevManifest ): void => { const manifestRouteIds = new Set(Object.keys(manifest.routes)); for (const routeId of Object.keys(build.routes)) { if (!manifestRouteIds.has(routeId)) { throw new Error( `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} does not exist in the matching web manifest.` ); } } for (const [routeId, manifestRoute] of Object.entries(manifest.routes)) { const routeModule = build.routes[routeId]?.module; if (!routeModule) { throw new Error( `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} is missing from the evaluated server build.` ); } if ( Boolean(manifestRoute.hasLoader) !== (typeof routeModule.loader === 'function') ) { throw new Error( `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} loader export does not match its web manifest.` ); } if ( Boolean(manifestRoute.hasAction) !== (typeof routeModule.action === 'function') ) { throw new Error( `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} action export does not match its web manifest.` ); } } };🤖 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-runtime-artifacts.ts` around lines 145 - 174, The assertBuildMatchesManifest check only verifies routes present in ReactRouterDevManifest, so stale ServerBuild entries for deleted routes can slip through. Update assertBuildMatchesManifest to also compare build.routes against manifest.routes and throw when the evaluated server build contains any extra route IDs not in the manifest, alongside the existing missing-route and loader/action export checks. Use the existing entryName, build, manifest, and routeId logic to keep the validation in one place.src/dev-runtime-session.ts (1)
62-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Run
closeBinding()after successful closes from theclosingstate.Once
markClosing()switches the session toclosing, Line 66 stops invokingcloseBinding(). A normalserver.close()then moves toidlewithoutruntime.close(), unregistering the dev runtime, or clearing compiler identity state.Suggested fix
const completeClose = (binding: RuntimeBinding): void => { if (!isCurrentBinding(binding)) { return; } - if (state.status === 'active') { + if (state.status === 'active' || state.status === 'closing') { closeBinding(binding); } state = { status: 'idle' }; };Also applies to: 170-173
🤖 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-runtime-session.ts` around lines 62 - 69, The completeClose flow in dev-runtime-session skips closeBinding() when the session is already in the closing state, so successful server.close() calls never fully clean up the runtime. Update completeClose to invoke closeBinding(binding) for the successful closing path as well as active, while still guarding with isCurrentBinding(binding) and preserving the transition to idle; apply the same fix in the other matching close path around the later close handler.src/prerender-build.ts (4)
175-177: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Keep prerender output paths inside
clientBuildDir.
normalizedPath.split('/')can include..or backslash segments from configured/generated prerender paths, letting dynamic/splat route paths write outside the client build directory. Validate segments and assert the resolved path remains underclientBuildDir.🛡️ Proposed fix
const createDataRequestPath = ( prerenderPath: string, trailingSlashAwareDataRequests: boolean ): string => { @@ : `${prerenderPath.replace(/\/$/, '')}.data`; }; + +const getClientBuildOutputPath = ( + clientBuildDir: string, + normalizedPath: string, + ...extraSegments: string[] +): string => { + const segments = normalizedPath.split('/').filter(Boolean); + if ( + segments.some( + segment => + segment === '.' || segment === '..' || segment.includes('\\') + ) + ) { + throw new Error(`Invalid prerender output path: ${normalizedPath}`); + } + + const outputPath = resolve(clientBuildDir, ...segments, ...extraSegments); + const relativeOutputPath = relative(clientBuildDir, outputPath); + if (relativeOutputPath === '..' || relativeOutputPath.startsWith('../')) { + throw new Error(`Invalid prerender output path: ${normalizedPath}`); + } + return outputPath; +}; @@ - const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); + const outputPath = getClientBuildOutputPath(clientBuildDir, normalizedPath); @@ - const outputPath = resolve( - clientBuildDir, - ...normalizedPath.split('/'), - 'index.html' - ); + const outputPath = getClientBuildOutputPath( + clientBuildDir, + normalizedPath, + 'index.html' + ); @@ - const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); + const outputPath = getClientBuildOutputPath(clientBuildDir, normalizedPath);Also applies to: 234-240, 284-286
🤖 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/prerender-build.ts` around lines 175 - 177, The prerender write path logic in the output path construction must prevent traversal outside clientBuildDir. Update the path handling in prerender-build.ts around the outputPath/mkdir/writeFile flow to validate normalizedPath segments before joining, reject any .. or backslash-based traversal, and assert the final resolved path stays under clientBuildDir. Apply the same safeguard to the other prerender output write sites mentioned by the build flow helpers so all generated files remain confined to the client build directory.
211-225: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Escape redirect
Locationbefore writing fallback HTML.The redirect page writes
Locationdirectly into text and attributes. A route redirect with<,", or similar characters can generate static HTML with script/markup injection.🛡️ Proposed fix
+const escapeHtml = (value: string): string => + value.replace(/[&<>"']/g, char => { + switch (char) { + case '&': + return '&'; + case '<': + return '<'; + case '>': + return '>'; + case '"': + return '"'; + default: + return '&`#39`;'; + } + }); + @@ if (redirectStatusCodes.has(response.status)) { const location = response.headers.get('Location'); + if (!location) { + throw new Error( + `Prerender (html): Redirect response for \`${normalizedPath}\` is missing a Location header.` + ); + } + const escapedLocation = escapeHtml(location); const delay = response.status === 302 ? 2 : 0; html = `<!doctype html> <head> -<title>Redirecting to: ${location}</title> -<meta http-equiv="refresh" content="${delay};url=${location}"> +<title>Redirecting to: ${escapedLocation}</title> +<meta http-equiv="refresh" content="${delay};url=${escapedLocation}"> <meta name="robots" content="noindex"> </head> <body> -\t<a href="${location}"> - Redirecting from <code>${normalizedPath}</code> to <code>${location}</code> +\t<a href="${escapedLocation}"> + Redirecting from <code>${escapeHtml(normalizedPath)}</code> to <code>${escapedLocation}</code> </a> </body> </html>`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const escapeHtml = (value: string): string => value.replace(/[&<>"']/g, char => { switch (char) { case '&': return '&'; case '<': return '<'; case '>': return '>'; case '"': return '"'; default: return '&`#39`;'; } }); if (redirectStatusCodes.has(response.status)) { const location = response.headers.get('Location'); if (!location) { throw new Error( `Prerender (html): Redirect response for \`${normalizedPath}\` is missing a Location header.` ); } const escapedLocation = escapeHtml(location); const delay = response.status === 302 ? 2 : 0; html = `<!doctype html> <head> <title>Redirecting to: ${escapedLocation}</title> <meta http-equiv="refresh" content="${delay};url=${escapedLocation}"> <meta name="robots" content="noindex"> </head> <body> \t<a href="${escapedLocation}"> Redirecting from <code>${escapeHtml(normalizedPath)}</code> to <code>${escapedLocation}</code> </a> </body> </html>`;🤖 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/prerender-build.ts` around lines 211 - 225, Escape the redirect target before interpolating it into the fallback HTML in the redirect handling block so untrusted `Location` values cannot inject markup or attributes. Update the logic around `redirectStatusCodes`, `response.headers.get('Location')`, and the HTML template in `src/prerender-build.ts` to use a safely escaped/sanitized version for the `<title>`, `meta refresh`, and `<a href>`/link text. Keep `normalizedPath` as-is, but ensure every use of the redirect destination is HTML- and attribute-safe.
496-498: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Consume the promise returned by the cleanup handler.
task.finally(...)creates a second promise that rejects whentaskrejects; because it is ignored, a prerender failure can also surface as an unhandled rejection. Usethenwith both handlers or catch the returned promise.🛠️ Proposed fix
const task = enqueue(path); pending.add(task); - task.finally(() => pending.delete(task)); + void task.then( + () => pending.delete(task), + () => pending.delete(task) + );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const task = enqueue(path); pending.add(task); void task.then( () => pending.delete(task), () => pending.delete(task) );🤖 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/prerender-build.ts` around lines 496 - 498, The cleanup in enqueue path handling creates an unconsumed promise from task.finally, which can surface prerender failures as unhandled rejections; update the logic around enqueue, pending, and task so the cleanup promise is handled explicitly. Use the task promise’s then with both fulfillment and rejection handlers, or attach a catch to the promise returned by finally, while still removing the task from pending.
549-607: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Restore
IS_RR_BUILD_REQUESTafter prerendering.This mutates process-global state and leaves it set for
buildEnd, later builds, or tests in the same process. Preserve and restore the previous value in afinallyblock around the build-request work.🛠️ Proposed fix
if (!ssr || isPrerenderEnabled) { + const previousBuildRequest = process.env.IS_RR_BUILD_REQUEST; process.env.IS_RR_BUILD_REQUEST = 'yes'; - const buildModule = await import(pathToFileURL(serverBuildPath).toString()); - const build = (await resolveServerBuildModule( - buildModule, - `Server build ${JSON.stringify(serverBuildPath)}` - )) as PrerenderServerBuild; - const requestHandler = createRequestHandler(build, 'production'); + try { + const buildModule = await import(pathToFileURL(serverBuildPath).toString()); + const build = (await resolveServerBuildModule( + buildModule, + `Server build ${JSON.stringify(serverBuildPath)}` + )) as PrerenderServerBuild; + const requestHandler = createRequestHandler(build, 'production'); - if (isPrerenderEnabled) { + if (isPrerenderEnabled) { // existing prerender logic - } + } - if (!ssr) { + if (!ssr) { await handleSpaMode({ handler: requestHandler, build, clientBuildDir, basename, api, }); + } + } finally { + if (previousBuildRequest === undefined) { + delete process.env.IS_RR_BUILD_REQUEST; + } else { + process.env.IS_RR_BUILD_REQUEST = previousBuildRequest; } } }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.if (!ssr || isPrerenderEnabled) { const previousBuildRequest = process.env.IS_RR_BUILD_REQUEST; process.env.IS_RR_BUILD_REQUEST = 'yes'; try { const buildModule = await import(pathToFileURL(serverBuildPath).toString()); const build = (await resolveServerBuildModule( buildModule, `Server build ${JSON.stringify(serverBuildPath)}` )) as PrerenderServerBuild; const requestHandler = createRequestHandler(build, 'production'); if (isPrerenderEnabled) { if (!ssr) { const generated = latestBrowserManifest ? { manifest: latestBrowserManifest, moduleExportsByRouteId: latestBrowserManifestModuleExports, } : await generateReactRouterManifestForDev( routes, pluginOptions, clientStats, appDirectory, assetPrefix, routeChunkOptions ); assertValidSsrFalsePrerenderExports({ routes, manifestRoutes: generated.manifest.routes, routeExports: generated.moduleExportsByRouteId, prerenderPaths, api, }); } validatePrerenderPathMatches(routes, prerenderPaths); if (prerenderPaths.length > 0) { api.logger.info( `Prerender (html): ${prerenderPaths.length} path(s)...` ); } await runPrerenderPaths({ build, requestHandler, clientBuildDir, options, }); } if (!ssr) { await handleSpaMode({ handler: requestHandler, build, clientBuildDir, basename, api, }); } } finally { if (previousBuildRequest === undefined) { delete process.env.IS_RR_BUILD_REQUEST; } else { process.env.IS_RR_BUILD_REQUEST = previousBuildRequest; } } }🤖 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/prerender-build.ts` around lines 549 - 607, The prerender/build-request flow in prerender-build.ts leaves process.env.IS_RR_BUILD_REQUEST mutated after the work completes. In the block around resolveServerBuildModule, createRequestHandler, runPrerenderPaths, and handleSpaMode, save the previous IS_RR_BUILD_REQUEST value before setting it and restore it in a finally block after all prerender/Spa handling finishes. This should be done around the existing build request logic so buildEnd and subsequent runs/tests see the original process-global state.src/route-chunks.ts (1)
211-216: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Exclude directive prologues from shared side-effect blocking.
"use client"/"use strict"directives areExpressionStatements, so they are added to every export’stopLevelNonModuleStatements. That makes the intersection check reject otherwise split-safe exports, and can failsplitRouteModules: "enforce"for routes that only share a directive.Proposed fix
- const sharedTopLevelSideEffects = (module.ast as AnyNode).body.filter( + const sharedTopLevelSideEffects = (module.ast as AnyNode).body.filter( (statement: AnyNode) => (statement.type === 'ImportDeclaration' && statement.specifiers.length === 0) || statement.type === 'ExpressionStatement' );for (const statement of sharedTopLevelSideEffects) { dependencies.topLevelStatements.add(statement); - dependencies.topLevelNonModuleStatements.add(statement); + if (statement.type !== 'ExpressionStatement' || !statement.directive) { + dependencies.topLevelNonModuleStatements.add(statement); + } }Also applies to: 329-332
🤖 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/route-chunks.ts` around lines 211 - 216, The shared side-effect collection in route chunking is incorrectly treating directive prologues like "use client" and "use strict" as blocking statements. Update the filtering logic in the shared side-effect scan and the related `topLevelNonModuleStatements` handling so `ExpressionStatement`s that are directive prologues are excluded, while real side-effectful expressions remain included. Use the existing `sharedTopLevelSideEffects` and export analysis paths to ensure `splitRouteModules: "enforce"` only rejects genuinely shared side effects.src/route-export-resolution.ts (1)
160-182: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail # Map the file and inspect the relevant regions with line numbers. ast-grep outline src/route-export-resolution.ts --view expanded || true wc -l src/route-export-resolution.ts sed -n '1,280p' src/route-export-resolution.ts | cat -n # Locate the specific functions and their call sites. rg -n "resolvePackageExports|resolveExportAllModule|collectClientOnlyStubExportNames|PACKAGE_RESOLUTION_NOT_APPLICABLE|createRequire|packageSubpath" src/route-export-resolution.ts src -SRepository: rstackjs/rsbuild-plugin-react-router
Length of output: 14053
Package subpath resolution misses common
exportscases.
resolvePackageExports()ignorespackageSubpathwhen"exports"is absent, sopkg/subpathresolves to the package entry instead of the submodule. The"exports"branch also only does exact-key lookups, so pattern exports like"./*"returnnull, andresolveExportAllModule()treats that as final instead of falling back tocreateRequire(...).resolve(). This can produce the wrong stub surface or reject valid imports.🤖 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/route-export-resolution.ts` around lines 160 - 182, Update resolvePackageExports and related callers so subpath imports are handled correctly: when packageJson.exports is absent, do not always fall back to module/main for every packageSubpath, and make sure pkg/subpath is not resolved as the package entry. In the exports branch, enhance the lookup logic in resolvePackageExports to support non-exact export shapes such as pattern mappings like "./*" instead of only direct packageSubpath keys. Then adjust resolveExportAllModule so a null result from resolvePackageExports is treated as “not matched yet” and it can still fall back to createRequire(...).resolve() before rejecting the import.src/typegen.ts (1)
22-30: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Don't swallow unexpected
typegen --watchexits.This catch path ignores every rejection. If the watcher exits because the CLI crashes or the route config is temporarily invalid, typegen is dead for the rest of the dev session and nothing reports that state. Only suppress the intentional shutdown path; unexpected exits should be logged or failed loudly.
🤖 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/typegen.ts` around lines 22 - 30, The `typegen --watch` promise handling in `typegenProcess` is swallowing every rejection, which hides unexpected watcher exits. Update the `.catch` path so it only ignores the intentional shutdown case from server teardown, and for any other rejection in `src/typegen.ts` surface the failure by logging or rethrowing it. Use the existing `typegenProcess` cleanup in `.finally()` to keep the process reset behavior intact while making crashes or invalid config failures visible.src/virtual-modules.ts (1)
11-19: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Reject backslash path separators in virtual module ids.
The current traversal check only splits on
/, sovirtual/react-router/foo\..\barpasses validation but can be interpreted as traversal on Windows-style paths. Reject\inside segments before constructing thenode_modules/...path.🛡️ Proposed fix
if ( !relativeId || - segments.some(segment => !segment || segment === '.' || segment === '..') + segments.some( + segment => + !segment || + segment === '.' || + segment === '..' || + segment.includes('\\') + ) ) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const segments = relativeId.split('/'); if ( !relativeId || segments.some( segment => !segment || segment === '.' || segment === '..' || segment.includes('\\') ) ) { throw new Error(`Invalid virtual module id: ${moduleId}`); } return `node_modules/${moduleId}.js`;🤖 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/virtual-modules.ts` around lines 11 - 19, The virtual module ID validation in the module path builder does not block backslash separators, so a Windows-style traversal pattern can slip through. Update the validation in the relativeId/segments check to reject any segment containing backslashes before returning the node_modules path, keeping the existing dot-segment and empty-segment checks in place.
6ad99ea to
e16d85f
Compare
dff4b2a to
559bb2e
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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.
Inline comments:
In `@scripts/bench-builds.mjs`:
- Around line 256-283: The benchmark timing in the child process lifecycle is
measuring shutdown latency instead of pure readiness time. Update the
spawn/finish flow in the benchmark helper so the readiness timestamp is captured
when the server becomes ready (around the ready-marking logic) and have the
resolved result use that recorded start-to-ready duration, while keeping
finish() only for cleanup/settlement. Use the existing child spawn, ready, and
finish symbols to wire this in without including SIGTERM/SIGKILL teardown time
in wallMs.
In `@scripts/report-benchmark-ci.mjs`:
- Line 124: The benchmark report logic currently picks a single mode via the
report-generation path, which can hide a build-vs-dev mismatch and produce
misleading CI output. Update the comparison/reporting flow in
report-benchmark-ci.mjs so that the mode selection in the benchmark summary
checks both head and base modes explicitly, and reject the report when they
differ instead of defaulting through head.mode/base.mode. Use the existing
benchmark result handling around the mode field in the reporting script to
surface the mismatch before generating the final CI report.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 547b74ae-d610-4bf7-964c-5fc1e235c7a5
📒 Files selected for processing (18)
.github/workflows/benchmark.ymlREADME.mdbenchmarks/README.mdscripts/bench-builds.mjsscripts/benchmark/fixture.mjsscripts/report-benchmark-ci.mjsscripts/test-package-interop.mjssrc/dev-generation.tssrc/dev-runtime-artifacts.tssrc/dev-runtime-compilation.tssrc/dev-runtime-controller.tssrc/index.tssrc/parallel-route-transforms.tssrc/types.tstests/benchmark-fixture.test.tstests/dev-runtime-controller.test.tstests/index.test.tstests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (1)
- benchmarks/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
- src/dev-runtime-compilation.ts
- src/types.ts
- scripts/test-package-interop.mjs
- tests/benchmark-fixture.test.ts
- src/dev-generation.ts
- src/dev-runtime-artifacts.ts
- src/dev-runtime-controller.ts
- tests/dev-runtime-controller.test.ts
- tests/index.test.ts
- src/parallel-route-transforms.ts
- scripts/benchmark/fixture.mjs
- README.md
- src/index.ts
559bb2e to
0c1f1e5
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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.
Inline comments:
In `@README.md`:
- Around line 447-457: The dev-server sample still closes over currentDevServer
and mounts its middlewares/handler on a shared app, so rerunning startServer()
would keep binding requests to the first server. Update the README example
around createDevServer, startServer, and onRouteTopologyChange to either
explicitly document this as a full-process-restart flow or refactor it to use
replaceable delegates that swap the active handler without stacking stale
middleware, and make sure the text matches the serialized close-then-recreate
behavior described in the surrounding prose.
In `@src/server-utils.ts`:
- Around line 117-124: The route discovery contract is inconsistent:
`resolveServerBuildModule` currently rejects `undefined` even though the
`routeDiscovery` field is treated as optional elsewhere. Update
`isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make
the `routeDiscovery` requirement consistent across the
`resolveServerBuildModule` validation path and related type checks so local
builds with no route discovery do not throw.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b8c10ff6-a873-490f-a640-aeed1b40dcca
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (125)
.changeset/bright-routes-run.md.changeset/coherent-dev-generations.md.changeset/lazy-entry-hydration.md.changeset/quiet-topology-order.md.changeset/sharp-routes-heal.md.changeset/warm-watchers-rest.mdREADME.mdbenchmarks/README.mdconfig/package.jsonconfig/rslib.config.tsdocs/coherent-dev-generations.mdexamples/client-only/package.jsonexamples/cloudflare/package.jsonexamples/custom-node-server/app/load-context.d.tsexamples/custom-node-server/app/routes/projects/edit.tsxexamples/custom-node-server/app/routes/projects/index.tsxexamples/custom-node-server/app/routes/projects/layout.tsxexamples/custom-node-server/app/routes/projects/project.tsxexamples/custom-node-server/app/routes/projects/settings.tsxexamples/custom-node-server/package.jsonexamples/custom-node-server/playwright.config.tsexamples/custom-node-server/rsbuild.config.tsexamples/custom-node-server/scripts/smoke-production.mjsexamples/custom-node-server/server.jsexamples/custom-node-server/server/index.tsexamples/custom-node-server/tsconfig.jsonexamples/default-template/app/dev-routes.tsexamples/default-template/app/routes.tsexamples/default-template/package.jsonexamples/default-template/playwright.config.tsexamples/default-template/react-router.config.tsexamples/default-template/rsbuild.config.tsexamples/default-template/tests/e2e/dev-route-watch.test.tsexamples/default-template/tests/e2e/lazy-compilation.test.tsexamples/epic-stack/package.jsonexamples/federation/epic-stack-remote/package.jsonexamples/federation/epic-stack/package.jsonexamples/prerender/package.jsonexamples/spa-mode/package.jsonpackage.jsonrslib.config.tsscripts/bench-builds.mjsscripts/benchmark/fixture.mjsscripts/compare-benchmarks.mjsscripts/test-package-interop.mjssrc/babel.tssrc/bounded-cache.tssrc/build-manifest.tssrc/build-output-transforms.tssrc/concurrency.tssrc/constants.tssrc/dev-generation.tssrc/dev-runtime-artifacts.tssrc/dev-runtime-compilation.tssrc/dev-runtime-controller.tssrc/dev-runtime-session.tssrc/dev-server.tssrc/export-utils.tssrc/index.tssrc/lazy-compilation.tssrc/manifest.tssrc/modify-browser-manifest.tssrc/parallel-route-transform-protocol.tssrc/parallel-route-transform-worker.tssrc/parallel-route-transforms.tssrc/performance.tssrc/plugin-utils.tssrc/prerender-build.tssrc/prerender.tssrc/react-router-config.tssrc/route-artifacts.tssrc/route-ast.tssrc/route-chunks.tssrc/route-component-transform.tssrc/route-export-pruning.tssrc/route-export-resolution.tssrc/route-transform-tasks.tssrc/route-watch.tssrc/server-build-plan.tssrc/server-utils.tssrc/templates/entry.server.tsxsrc/typegen.tssrc/types.tssrc/virtual-modules.tssrc/warnings/warn-on-client-source-maps.tssrc/yuku.tstests/benchmark-fixture.test.tstests/bounded-cache.test.tstests/build-manifest.test.tstests/build-output-transforms.test.tstests/client-modules.test.tstests/dev-generation-multi-entry.test.tstests/dev-generation.test.tstests/dev-runtime-controller.test.tstests/dev-runtime.integration.test.tstests/dev-server.test.tstests/export-utils.test.tstests/features.test.tstests/fixtures/dev-runtime/app/entry.client.tsxtests/fixtures/dev-runtime/app/entry.server.tsxtests/fixtures/dev-runtime/app/root.tsxtests/fixtures/dev-runtime/app/routes.tstests/fixtures/dev-runtime/app/routes/index.tsxtests/fixtures/dev-runtime/app/routes/other.tsxtests/fixtures/dev-runtime/react-router.config.tstests/fixtures/dev-runtime/server/index.tstests/index.test.tstests/lazy-compilation.test.tstests/manifest-split-route-modules.test.tstests/manifest.test.tstests/modify-browser-manifest.test.tstests/parallel-route-transforms.test.tstests/performance.test.tstests/plugin-utils.test.tstests/prerender.test.tstests/react-router-config.test.tstests/remove-exports.test.tstests/route-artifacts.test.tstests/route-chunks-cache.test.tstests/route-chunks.test.tstests/route-watch.test.tstests/server-build-plan.test.tstests/server-utils.test.tstests/setup.tstests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (10)
- tests/plugin-utils.test.ts
- tests/react-router-config.test.ts
- src/babel.ts
- tests/manifest-split-route-modules.test.ts
- tests/prerender.test.ts
- tests/performance.test.ts
- examples/custom-node-server/server/index.ts
- tests/modify-browser-manifest.test.ts
- tests/manifest.test.ts
- tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (16)
- tests/fixtures/dev-runtime/app/routes/index.tsx
- examples/custom-node-server/app/routes/projects/index.tsx
- examples/custom-node-server/app/routes/projects/layout.tsx
- config/package.json
- .changeset/warm-watchers-rest.md
- .changeset/bright-routes-run.md
- .changeset/lazy-entry-hydration.md
- .changeset/coherent-dev-generations.md
- .changeset/sharp-routes-heal.md
- src/warnings/warn-on-client-source-maps.ts
- examples/federation/epic-stack-remote/package.json
- .changeset/quiet-topology-order.md
- examples/custom-node-server/app/routes/projects/project.tsx
- benchmarks/README.md
- examples/client-only/package.json
- docs/coherent-dev-generations.md
🚧 Files skipped from review as they are similar to previous changes (83)
- tests/build-manifest.test.ts
- tests/fixtures/dev-runtime/app/root.tsx
- src/parallel-route-transform-protocol.ts
- examples/custom-node-server/tsconfig.json
- tests/fixtures/dev-runtime/react-router.config.ts
- rslib.config.ts
- tests/fixtures/dev-runtime/app/entry.server.tsx
- examples/default-template/app/routes.ts
- tests/fixtures/dev-runtime/app/routes/other.tsx
- tests/fixtures/dev-runtime/app/routes.ts
- examples/custom-node-server/app/load-context.d.ts
- examples/spa-mode/package.json
- tests/fixtures/dev-runtime/app/entry.client.tsx
- src/virtual-modules.ts
- tests/export-utils.test.ts
- examples/prerender/package.json
- examples/custom-node-server/rsbuild.config.ts
- examples/federation/epic-stack/package.json
- examples/custom-node-server/app/routes/projects/settings.tsx
- examples/default-template/app/dev-routes.ts
- examples/custom-node-server/app/routes/projects/edit.tsx
- examples/default-template/tests/e2e/dev-route-watch.test.ts
- src/bounded-cache.ts
- examples/cloudflare/package.json
- src/templates/entry.server.tsx
- examples/default-template/rsbuild.config.ts
- src/concurrency.ts
- src/types.ts
- tests/fixtures/dev-runtime/server/index.ts
- examples/custom-node-server/playwright.config.ts
- src/typegen.ts
- examples/epic-stack/package.json
- src/build-manifest.ts
- examples/default-template/react-router.config.ts
- scripts/compare-benchmarks.mjs
- tests/dev-server.test.ts
- src/lazy-compilation.ts
- examples/default-template/package.json
- src/dev-server.ts
- tests/bounded-cache.test.ts
- src/yuku.ts
- src/constants.ts
- examples/custom-node-server/package.json
- src/parallel-route-transform-worker.ts
- src/dev-runtime-compilation.ts
- tests/dev-generation-multi-entry.test.ts
- src/server-build-plan.ts
- src/route-ast.ts
- examples/custom-node-server/server.js
- src/dev-runtime-session.ts
- src/route-artifacts.ts
- examples/custom-node-server/scripts/smoke-production.mjs
- tests/build-output-transforms.test.ts
- src/performance.ts
- scripts/test-package-interop.mjs
- src/dev-runtime-artifacts.ts
- tests/client-modules.test.ts
- examples/default-template/playwright.config.ts
- src/dev-generation.ts
- tests/lazy-compilation.test.ts
- src/route-export-resolution.ts
- src/route-component-transform.ts
- package.json
- src/react-router-config.ts
- config/rslib.config.ts
- src/prerender.ts
- src/route-export-pruning.ts
- src/export-utils.ts
- tests/index.test.ts
- src/build-output-transforms.ts
- src/route-watch.ts
- src/parallel-route-transforms.ts
- src/route-transform-tasks.ts
- tests/dev-generation.test.ts
- src/plugin-utils.ts
- examples/default-template/tests/e2e/lazy-compilation.test.ts
- src/manifest.ts
- tests/features.test.ts
- src/modify-browser-manifest.ts
- tests/dev-runtime-controller.test.ts
- src/prerender-build.ts
- src/route-chunks.ts
- src/index.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 2
🤖 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.
Inline comments:
In `@README.md`:
- Around line 447-457: The dev-server sample still closes over currentDevServer
and mounts its middlewares/handler on a shared app, so rerunning startServer()
would keep binding requests to the first server. Update the README example
around createDevServer, startServer, and onRouteTopologyChange to either
explicitly document this as a full-process-restart flow or refactor it to use
replaceable delegates that swap the active handler without stacking stale
middleware, and make sure the text matches the serialized close-then-recreate
behavior described in the surrounding prose.
In `@src/server-utils.ts`:
- Around line 117-124: The route discovery contract is inconsistent:
`resolveServerBuildModule` currently rejects `undefined` even though the
`routeDiscovery` field is treated as optional elsewhere. Update
`isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make
the `routeDiscovery` requirement consistent across the
`resolveServerBuildModule` validation path and related type checks so local
builds with no route discovery do not throw.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b8c10ff6-a873-490f-a640-aeed1b40dcca
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (125)
.changeset/bright-routes-run.md.changeset/coherent-dev-generations.md.changeset/lazy-entry-hydration.md.changeset/quiet-topology-order.md.changeset/sharp-routes-heal.md.changeset/warm-watchers-rest.mdREADME.mdbenchmarks/README.mdconfig/package.jsonconfig/rslib.config.tsdocs/coherent-dev-generations.mdexamples/client-only/package.jsonexamples/cloudflare/package.jsonexamples/custom-node-server/app/load-context.d.tsexamples/custom-node-server/app/routes/projects/edit.tsxexamples/custom-node-server/app/routes/projects/index.tsxexamples/custom-node-server/app/routes/projects/layout.tsxexamples/custom-node-server/app/routes/projects/project.tsxexamples/custom-node-server/app/routes/projects/settings.tsxexamples/custom-node-server/package.jsonexamples/custom-node-server/playwright.config.tsexamples/custom-node-server/rsbuild.config.tsexamples/custom-node-server/scripts/smoke-production.mjsexamples/custom-node-server/server.jsexamples/custom-node-server/server/index.tsexamples/custom-node-server/tsconfig.jsonexamples/default-template/app/dev-routes.tsexamples/default-template/app/routes.tsexamples/default-template/package.jsonexamples/default-template/playwright.config.tsexamples/default-template/react-router.config.tsexamples/default-template/rsbuild.config.tsexamples/default-template/tests/e2e/dev-route-watch.test.tsexamples/default-template/tests/e2e/lazy-compilation.test.tsexamples/epic-stack/package.jsonexamples/federation/epic-stack-remote/package.jsonexamples/federation/epic-stack/package.jsonexamples/prerender/package.jsonexamples/spa-mode/package.jsonpackage.jsonrslib.config.tsscripts/bench-builds.mjsscripts/benchmark/fixture.mjsscripts/compare-benchmarks.mjsscripts/test-package-interop.mjssrc/babel.tssrc/bounded-cache.tssrc/build-manifest.tssrc/build-output-transforms.tssrc/concurrency.tssrc/constants.tssrc/dev-generation.tssrc/dev-runtime-artifacts.tssrc/dev-runtime-compilation.tssrc/dev-runtime-controller.tssrc/dev-runtime-session.tssrc/dev-server.tssrc/export-utils.tssrc/index.tssrc/lazy-compilation.tssrc/manifest.tssrc/modify-browser-manifest.tssrc/parallel-route-transform-protocol.tssrc/parallel-route-transform-worker.tssrc/parallel-route-transforms.tssrc/performance.tssrc/plugin-utils.tssrc/prerender-build.tssrc/prerender.tssrc/react-router-config.tssrc/route-artifacts.tssrc/route-ast.tssrc/route-chunks.tssrc/route-component-transform.tssrc/route-export-pruning.tssrc/route-export-resolution.tssrc/route-transform-tasks.tssrc/route-watch.tssrc/server-build-plan.tssrc/server-utils.tssrc/templates/entry.server.tsxsrc/typegen.tssrc/types.tssrc/virtual-modules.tssrc/warnings/warn-on-client-source-maps.tssrc/yuku.tstests/benchmark-fixture.test.tstests/bounded-cache.test.tstests/build-manifest.test.tstests/build-output-transforms.test.tstests/client-modules.test.tstests/dev-generation-multi-entry.test.tstests/dev-generation.test.tstests/dev-runtime-controller.test.tstests/dev-runtime.integration.test.tstests/dev-server.test.tstests/export-utils.test.tstests/features.test.tstests/fixtures/dev-runtime/app/entry.client.tsxtests/fixtures/dev-runtime/app/entry.server.tsxtests/fixtures/dev-runtime/app/root.tsxtests/fixtures/dev-runtime/app/routes.tstests/fixtures/dev-runtime/app/routes/index.tsxtests/fixtures/dev-runtime/app/routes/other.tsxtests/fixtures/dev-runtime/react-router.config.tstests/fixtures/dev-runtime/server/index.tstests/index.test.tstests/lazy-compilation.test.tstests/manifest-split-route-modules.test.tstests/manifest.test.tstests/modify-browser-manifest.test.tstests/parallel-route-transforms.test.tstests/performance.test.tstests/plugin-utils.test.tstests/prerender.test.tstests/react-router-config.test.tstests/remove-exports.test.tstests/route-artifacts.test.tstests/route-chunks-cache.test.tstests/route-chunks.test.tstests/route-watch.test.tstests/server-build-plan.test.tstests/server-utils.test.tstests/setup.tstests/warn-on-client-source-maps.test.ts
💤 Files with no reviewable changes (10)
- tests/plugin-utils.test.ts
- tests/react-router-config.test.ts
- src/babel.ts
- tests/manifest-split-route-modules.test.ts
- tests/prerender.test.ts
- tests/performance.test.ts
- examples/custom-node-server/server/index.ts
- tests/modify-browser-manifest.test.ts
- tests/manifest.test.ts
- tests/parallel-route-transforms.test.ts
✅ Files skipped from review due to trivial changes (16)
- tests/fixtures/dev-runtime/app/routes/index.tsx
- examples/custom-node-server/app/routes/projects/index.tsx
- examples/custom-node-server/app/routes/projects/layout.tsx
- config/package.json
- .changeset/warm-watchers-rest.md
- .changeset/bright-routes-run.md
- .changeset/lazy-entry-hydration.md
- .changeset/coherent-dev-generations.md
- .changeset/sharp-routes-heal.md
- src/warnings/warn-on-client-source-maps.ts
- examples/federation/epic-stack-remote/package.json
- .changeset/quiet-topology-order.md
- examples/custom-node-server/app/routes/projects/project.tsx
- benchmarks/README.md
- examples/client-only/package.json
- docs/coherent-dev-generations.md
🚧 Files skipped from review as they are similar to previous changes (83)
- tests/build-manifest.test.ts
- tests/fixtures/dev-runtime/app/root.tsx
- src/parallel-route-transform-protocol.ts
- examples/custom-node-server/tsconfig.json
- tests/fixtures/dev-runtime/react-router.config.ts
- rslib.config.ts
- tests/fixtures/dev-runtime/app/entry.server.tsx
- examples/default-template/app/routes.ts
- tests/fixtures/dev-runtime/app/routes/other.tsx
- tests/fixtures/dev-runtime/app/routes.ts
- examples/custom-node-server/app/load-context.d.ts
- examples/spa-mode/package.json
- tests/fixtures/dev-runtime/app/entry.client.tsx
- src/virtual-modules.ts
- tests/export-utils.test.ts
- examples/prerender/package.json
- examples/custom-node-server/rsbuild.config.ts
- examples/federation/epic-stack/package.json
- examples/custom-node-server/app/routes/projects/settings.tsx
- examples/default-template/app/dev-routes.ts
- examples/custom-node-server/app/routes/projects/edit.tsx
- examples/default-template/tests/e2e/dev-route-watch.test.ts
- src/bounded-cache.ts
- examples/cloudflare/package.json
- src/templates/entry.server.tsx
- examples/default-template/rsbuild.config.ts
- src/concurrency.ts
- src/types.ts
- tests/fixtures/dev-runtime/server/index.ts
- examples/custom-node-server/playwright.config.ts
- src/typegen.ts
- examples/epic-stack/package.json
- src/build-manifest.ts
- examples/default-template/react-router.config.ts
- scripts/compare-benchmarks.mjs
- tests/dev-server.test.ts
- src/lazy-compilation.ts
- examples/default-template/package.json
- src/dev-server.ts
- tests/bounded-cache.test.ts
- src/yuku.ts
- src/constants.ts
- examples/custom-node-server/package.json
- src/parallel-route-transform-worker.ts
- src/dev-runtime-compilation.ts
- tests/dev-generation-multi-entry.test.ts
- src/server-build-plan.ts
- src/route-ast.ts
- examples/custom-node-server/server.js
- src/dev-runtime-session.ts
- src/route-artifacts.ts
- examples/custom-node-server/scripts/smoke-production.mjs
- tests/build-output-transforms.test.ts
- src/performance.ts
- scripts/test-package-interop.mjs
- src/dev-runtime-artifacts.ts
- tests/client-modules.test.ts
- examples/default-template/playwright.config.ts
- src/dev-generation.ts
- tests/lazy-compilation.test.ts
- src/route-export-resolution.ts
- src/route-component-transform.ts
- package.json
- src/react-router-config.ts
- config/rslib.config.ts
- src/prerender.ts
- src/route-export-pruning.ts
- src/export-utils.ts
- tests/index.test.ts
- src/build-output-transforms.ts
- src/route-watch.ts
- src/parallel-route-transforms.ts
- src/route-transform-tasks.ts
- tests/dev-generation.test.ts
- src/plugin-utils.ts
- examples/default-template/tests/e2e/lazy-compilation.test.ts
- src/manifest.ts
- tests/features.test.ts
- src/modify-browser-manifest.ts
- tests/dev-runtime-controller.test.ts
- src/prerender-build.ts
- src/route-chunks.ts
- src/index.ts
🛑 Comments failed to post (2)
README.md (1)
447-457: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
This sample still binds requests to the first dev server.
The prose says
onRouteTopologyChangeshould recreate the dev server, but this example mountscurrentDevServer.middlewaresand the request handler onto a sharedappand closes overcurrentDevServer. Re-runningstartServer()would stack stale middleware/handlers and keep requests on the old server. Please either document this as a full-process-restart flow or rewrite the sample around replaceable delegates.Also applies to: 459-525
🧰 Tools
🪛 LanguageTool
[style] ~450-~450: Consider replacing this word to strengthen your wording.
Context: ...tic API. The callback is a notification and is not awaited, so it can safely start ...(AND_THAT)
🤖 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 `@README.md` around lines 447 - 457, The dev-server sample still closes over currentDevServer and mounts its middlewares/handler on a shared app, so rerunning startServer() would keep binding requests to the first server. Update the README example around createDevServer, startServer, and onRouteTopologyChange to either explicitly document this as a full-process-restart flow or refactor it to use replaceable delegates that swap the active handler without stacking stale middleware, and make sure the text matches the serialized close-then-recreate behavior described in the surrounding prose.src/server-utils.ts (1)
117-124: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Accept
undefinedroute discovery or make the contract required.Lines 23-31 allow
routeDiscoveryto beundefined, but Line 162 rejects that viaisRouteDiscovery, causingresolveServerBuildModuleto throw for an otherwise valid local build shape.Proposed fix
function isRouteDiscovery(value: unknown): boolean { return ( - isRecord(value) && - (value.mode === 'initial' || + value === undefined || + (isRecord(value) && + (value.mode === 'initial' || (value.mode === 'lazy' && (value.manifestPath === undefined || - typeof value.manifestPath === 'string'))) + typeof value.manifestPath === 'string')))) ); }Also applies to: 162-162
🤖 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/server-utils.ts` around lines 117 - 124, The route discovery contract is inconsistent: `resolveServerBuildModule` currently rejects `undefined` even though the `routeDiscovery` field is treated as optional elsewhere. Update `isRouteDiscovery` in `server-utils.ts` to accept `undefined` as valid, or make the `routeDiscovery` requirement consistent across the `resolveServerBuildModule` validation path and related type checks so local builds with no route discovery do not throw.
Goal
Let React Router development servers start faster by allowing Rsbuild's
webandnodecompilers to run in parallel during development, while preserving coherent committed dev generations. Production builds keep the explicitnode -> webcompiler dependency.What changed
dependencies: ['web']edge from the dev node compiler config, keeping it forbuild.Validation
corepack pnpm run test:core- 35 files, 357 tests passedcorepack pnpm exec tsc --noEmitcorepack pnpm run format:checkcorepack pnpm run buildtimeout 60s corepack pnpm run test:package-interop- exits 0git diff --check