Skip to content

rsbuild plugin#7228

Merged
schiller-manuel merged 29 commits intomainfrom
rsb
Apr 24, 2026
Merged

rsbuild plugin#7228
schiller-manuel merged 29 commits intomainfrom
rsb

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel commented Apr 19, 2026

Summary by CodeRabbit

  • New Features

    • Rsbuild toolchain support added for React, Solid, and Vue starters and plugin entrypoints; new server-fn HMR page for dev testing
  • Behavior/UX

    • Import-protection messaging clarified: dev may show warnings that are deferred/removed at build; guidance added for mixed-barrel exports
  • Documentation

    • Updated import-protection guide and onViolation docs (snippet now documents highlightLine)
  • Chores

    • Consolidated post-build/prerender flows and expanded E2E/tooling configs and test coverage

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Consolidates import-protection into a shared adapter-agnostic core, adds Rsbuild/Rspack adapter and plugin surfaces (including virtual-module and dev-server support), introduces many rsbuild integration pieces (planning, virtual modules, compiler host, import-protection orchestration), exposes rsbuild RSC decode entrypoints/types, and updates numerous E2E configs/tests to be toolchain-aware (Vite vs Rsbuild).

Changes

Cohort / File(s) Summary
Docs & Guides
docs/start/framework/react/guide/import-protection.md, .changeset/add-rsbuild-start-support.md
Doc updates describing import-protection ownership/behavior and new changeset for rsbuild support.
Import-protection core (new API)
packages/start-plugin-core/src/import-protection/*, .../constants.ts, analysis.ts, rewrite.ts, sourceLocation.ts, utils.ts, adapterUtils.ts, trace.ts
New centralized import-protection implementation: AST analysis, usage-location search, denied-import rewriting, source-location helpers, matcher/adapter utilities, constants and dedupe/trace utilities; ViolationInfo shape changed.
Import-protection adapters
packages/start-plugin-core/src/vite/import-protection-plugin/*, packages/start-plugin-core/src/rsbuild/import-protection.ts, packages/start-plugin-core/src/rsbuild/*
Vite adapter refactored to use shared core; new Rsbuild adapter implements transform/post-process, virtual-module plumbing, deferred reporting, and Rsbuild-specific internals/docs.
Rsbuild plugin & infra
packages/start-plugin-core/src/rsbuild/plugin.ts, planning.ts, virtual-modules.ts, normalized-client-build.ts, start-compiler-host.ts, types.ts, post-build.ts
New rsbuild integration: environment planning, virtual-module registration, client-build normalization, compiler transforms, post-build adapter and APIs.
Start post-build / prerender refactor
packages/start-plugin-core/src/post-build.ts, prerender.ts, packages/start-plugin-core/src/vite/post-server-build.ts, packages/start-plugin-core/src/vite/prerender.ts
Extracted post-build/prerender orchestration into adapter-agnostic APIs and moved Vite/Rsbuild-specific logic into adapter layers.
RSC rsbuild entrypoints & types
packages/react-start-rsc/src/rsbuild/*, packages/react-start/src/rsbuild/*, packages/react-start-rsc/src/react-server-dom-rspack.d.ts, packages/react-start-rsc/src/global.d.ts
Added rsbuild/browser- and ssr-decode modules and ambient typings for react-server-dom-rspack / rspack RSC integrations; new package exports for these entrypoints.
Framework rsbuild plugins
packages/react-start/src/plugin/rsbuild.ts, packages/solid-start/src/plugin/rsbuild.ts, packages/vue-start/src/plugin/rsbuild.ts, packages/solid-start/vite.config.ts, packages/react-start/vite.config.ts
New framework-specific rsbuild plugin entry files wiring tanStackStartRsbuild and RSC-related options.
Removed legacy pieces / moved files
packages/start-plugin-core/src/import-protection-plugin/* (deleted: INTERNALS.md, postCompileUsage.ts, rewriteDeniedImports.ts)
Deleted legacy adapter-local implementations replaced by shared import-protection core and rsbuild/vite adapters.
Vite plugin surface & other refactors
packages/start-plugin-core/src/vite/*, packages/start-plugin-core/src/index.ts
Vite plugin rewired to call shared import-protection utilities; index exports extended with rsbuild-related exports/types.
Start compiler & HMR updates
packages/start-plugin-core/src/start-compiler/*, vite/start-compiler-plugin/*, start-compiler/load-module.ts (deleted)
Start compiler gained transitive importer discovery, improved invalidation, HMR merges; Vite loader removed in favor of unified ingestion and rsbuild host.
Router & HMR changes
packages/router-plugin/src/core/hmr/*, packages/router-plugin/src/core/code-splitter/*, packages/router-plugin/src/config.ts, packages/router-generator/src/*
HMR flow refactored to choose adapter by hmrStyle (vite
E2E: toolchain, configs, and tests
e2e/**/rsbuild.config.ts, e2e/**/postcss.config.mjs, e2e/**/vite.config.ts, e2e/**/server.js, e2e/**/start-mode-config.ts, e2e/**/package.json, e2e/**/playwright.config.ts, e2e/react-start/import-protection/tests/**
Add rsbuild configs, tailwind postcss files, environment-driven E2E wiring (E2E_TOOLCHAIN, E2E_DIST_DIR, E2E_PORT_KEY), dynamic server entry resolution, startMode helpers, and tests updated to be toolchain-aware and normalize violation parsing.
Misc package.json / exports updates
packages/*/package.json (react-start, react-start-rsc, solid-start, vue-start, start-plugin-core, etc.)
Added rsbuild export subpaths, peer/dev deps for @rsbuild/core and related packages, and adjusted peerDependency ranges/metadata.
Tests: new & updated suites
packages/start-plugin-core/tests/**, packages/router-plugin/tests/**, e2e/**/tests/**
Many tests rewired to new import-protection core and rsbuild adapters; added rsbuild-specific tests (output dir, post-build), expanded analysis/sourceLocation coverage, and HMR/e2e updates.
Small/UI tweaks
packages/react-router/src/Asset.tsx, packages/react-router/src/ClientOnly.tsx, packages/react-router-devtools/*, various route root changes
Minor whitespace, client directive additions, and switching stylesheet imports to side-effect CSS imports in several E2E apps.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  rect rgba(200,230,201,0.5)
    participant Dev as Developer / CLI
    participant Rsbuild as Rsbuild Plugin
    participant Rspack as Rspack Compiler
    participant Virtual as VirtualModules (fs/.virtual)
    participant Reporter as ImportProtection Reporter
    participant Post as PostBuild/Prerender
  end
  Dev->>Rsbuild: invoke build/dev (tanStackStartRsbuild)
  Rsbuild->>Rspack: modify config, register transforms, register virtual modules
  Rspack->>Rsbuild: transform modules (post-transform)
  Rsbuild->>Virtual: emit queued virtual mock/manifest modules
  Rsbuild->>Reporter: analyze transforms, compute/defer violations
  Reporter->>Virtual: read mock-edge payloads & resolved modules
  Reporter->>Rsbuild: emit deduped warnings/errors or call onViolation
  Rsbuild->>Post: on build completion -> postBuildWithRsbuild(adapter)
  Post->>Virtual: read client manifest -> prerender pages/output
  Post->>Dev: finish (artifact outputs & reports)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • Sheraff

Poem

🐰 I hopped through code with tiny feet and care,
Core rules gathered, adapters took their place,
Virtual mocks sprout softly from .virtual lairs,
Rsbuild hums, RSC finds its space,
A carrot cheer — Start leaps forward, light and fair!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rsb

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 19, 2026

View your CI Pipeline Execution ↗ for commit d59b5be

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 2m 45s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 4s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-24 01:15:50 UTC

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (13)
e2e/solid-start/basic/server.js (1)

16-28: Use an explicit error instead of a silent fallback to a missing file.

Line 27 currently returns server.js even when neither server.js nor index.js exists, which defers failure to the dynamic import on Line 32. Throwing early makes e2e failures much easier to diagnose.

Suggested patch
 function resolveDistServerEntryPath() {
   const serverJsPath = path.resolve(distDir, 'server', 'server.js')
   if (fs.existsSync(serverJsPath)) {
     return serverJsPath
   }

   const indexJsPath = path.resolve(distDir, 'server', 'index.js')
   if (fs.existsSync(indexJsPath)) {
     return indexJsPath
   }

-  return serverJsPath
+  throw new Error(
+    `Could not find server entry. Checked: ${serverJsPath} and ${indexJsPath}`,
+  )
 }

Also applies to: 31-32

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/solid-start/basic/server.js` around lines 16 - 28, The function
resolveDistServerEntryPath currently returns serverJsPath even if neither
server.js nor index.js exist; change it to check both serverJsPath and
indexJsPath and if neither exists throw an explicit Error (including the
resolved paths like serverJsPath and indexJsPath) instead of returning a missing
path so the failure surfaces immediately before the dynamic import that uses the
returned value.
e2e/react-start/basic/server.js (1)

16-28: Fail fast when no built server entry exists.

Line 27 returns a non-existent path if both candidates are missing, so the import on Line 32 fails later with a less clear error. Throw an explicit error after both checks.

Suggested patch
 function resolveDistServerEntryPath() {
   const serverJsPath = path.resolve(distDir, 'server', 'server.js')
   if (fs.existsSync(serverJsPath)) {
     return serverJsPath
   }

   const indexJsPath = path.resolve(distDir, 'server', 'index.js')
   if (fs.existsSync(indexJsPath)) {
     return indexJsPath
   }

-  return serverJsPath
+  throw new Error(
+    `Could not find server entry. Checked: ${serverJsPath} and ${indexJsPath}`,
+  )
 }

Also applies to: 31-32

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/basic/server.js` around lines 16 - 28, The helper
resolveDistServerEntryPath currently returns serverJsPath even when neither
server/server.js nor server/index.js exist; change it to throw a clear Error
when both fs.existsSync checks fail instead of returning a non-existent path
(include the tested paths like serverJsPath and indexJsPath or distDir in the
error message) so callers (e.g., the subsequent dynamic import of the resolved
path) fail fast with a descriptive message; update any similar logic around the
import to rely on this thrown error rather than receiving a bogus path.
e2e/vue-start/basic/server.js (1)

16-28: Prefer explicit failure when no server entry exists.

If neither candidate file exists, Line 27 returns a non-existent path and fails later during import(...). Throwing here gives clearer diagnostics.

Proposed refactor
 function resolveDistServerEntryPath() {
   const serverJsPath = path.resolve(distDir, 'server', 'server.js')
   if (fs.existsSync(serverJsPath)) {
     return serverJsPath
   }

   const indexJsPath = path.resolve(distDir, 'server', 'index.js')
   if (fs.existsSync(indexJsPath)) {
     return indexJsPath
   }

-  return serverJsPath
+  throw new Error(
+    `No server entry found. Checked:\n- ${serverJsPath}\n- ${indexJsPath}`,
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/vue-start/basic/server.js` around lines 16 - 28, The function
resolveDistServerEntryPath currently returns serverJsPath even when neither
candidate exists, causing a later import to fail with unclear diagnostics;
update resolveDistServerEntryPath to check both serverJsPath and indexJsPath and
throw a clear Error (including the checked paths) if neither
fs.existsSync(serverJsPath) nor fs.existsSync(indexJsPath) is true so callers
get immediate, descriptive failure instead of a broken path.
e2e/react-start/import-protection/tests/error-mode.setup.ts (1)

22-25: Resolve E2E_TOOLCHAIN once and reuse it consistently.

Line 22 and Line 126 compute toolchain separately. Keeping one resolved value avoids drift between port-key derivation and spawned command selection.

♻️ Proposed refactor
-const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
+const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
 const e2ePortKey =
   process.env.E2E_PORT_KEY ?? `${packageJson.name}-${toolchain}`
@@
-  const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
   const command =
     toolchain === 'rsbuild'
       ? ['exec', 'rsbuild', 'dev', '--port', String(port)]
       : ['exec', 'vite', 'dev', '--port', String(port)]

Also applies to: 126-130

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/tests/error-mode.setup.ts` around lines 22
- 25, Resolve E2E_TOOLCHAIN once at module load and reuse that single constant
(currently declared as toolchain alongside e2ePortKey) instead of recomputing it
later; modify the later block that currently recomputes toolchain (the code that
selects the spawned command and port key around lines 126–130) to reference the
module-scoped toolchain and the existing e2ePortKey, ensuring the same resolved
value is used for both port-key derivation and spawned command selection.
e2e/react-start/import-protection/package.json (1)

25-26: Version pinning inconsistency with main package.

The @rsbuild packages here are pinned to exact versions (2.0.0-rc.3) without the caret, while packages/react-start/package.json uses ^2.0.0-rc.3. Consider aligning the version specification style for consistency across the monorepo.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/package.json` around lines 25 - 26, In
package.json update the dependency entries for "@rsbuild/core" and
"@rsbuild/plugin-react" to use the same caret-style version specifier as the
main package (e.g. change "2.0.0-rc.3" to "^2.0.0-rc.3") so the version pinning
is consistent across the monorepo; locate the two keys "@rsbuild/core" and
"@rsbuild/plugin-react" in e2e/react-start/import-protection/package.json and
modify their version strings to include the leading caret.
packages/start-plugin-core/tests/rsbuild-post-build.test.ts (1)

15-16: Replace any in test setup to preserve strict typing.

any is used in three places and can hide real API drift in this integration test.

Suggested refactor
-    const prerenderSpy = vi.fn(async ({ handler }: any) => {
+    const prerenderSpy = vi.fn(
+      async ({ handler }: { handler: { request: (path: string) => Promise<Response> } }) => {
       const response = await handler.request('/posts')
       expect(await response.text()).toBe('ok')
-    })
+      },
+    )

     vi.doMock('../src/prerender', async () => {
-      const actual = await vi.importActual<any>('../src/prerender')
+      const actual =
+        await vi.importActual<typeof import('../src/prerender')>(
+          '../src/prerender',
+        )
       return {
         ...actual,
         prerender: prerenderSpy,
@@
-        } as any,
+        } satisfies Parameters<typeof postBuildWithRsbuild>[0]['startConfig'],

As per coding guidelines **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.

Also applies to: 21-21, 49-49

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts` around lines 15
- 16, The test uses the unsafe type any in the prerenderSpy callback (and two
other spots), which weakens strict-mode checks; update the signature to use a
concrete type for the callback parameter (e.g., import or declare an appropriate
Handler/PrerenderContext type and use { handler: Handler } or the specific
interface used by the prerender API) and replace the other two any usages with
the same type so the test preserves strict typing and catches API drift (refer
to prerenderSpy and the handler parameter name to locate affected code).
packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts (1)

2-16: Import order should be alphabetically sorted.

ESLint reports that ImportLocCache should be sorted alphabetically among the imports. Consider reordering to satisfy the sort-imports rule.

Proposed fix
 import {
+  ImportLocCache,
+  buildCodeSnippet,
+  buildLineIndex,
   createImportSpecifierLocationIndex,
-  ImportLocCache,
-  buildCodeSnippet,
-  buildLineIndex,
   findOriginalUsageLocation,
   getOrCreateOriginalTransformResult,
   normalizeSourceMap,
   pickOriginalCodeFromSourcesContent,
 } from '../../src/import-protection/sourceLocation'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts`
around lines 2 - 16, Reorder the named imports in the import statement from
'../../src/import-protection/sourceLocation' so they are alphabetically sorted
(e.g., place ImportLocCache in its correct alphabetical position among
createImportSpecifierLocationIndex, buildCodeSnippet, buildLineIndex,
findOriginalUsageLocation, getOrCreateOriginalTransformResult,
normalizeSourceMap, pickOriginalCodeFromSourcesContent) to satisfy the
sort-imports ESLint rule; do the same for the type imports (SourceMapLike,
TransformResult, TransformResultProvider) so both import lists are alphabetized.
packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts (1)

436-438: Type assertion suggests potential type mismatch.

The cast as EnvRules on the return value of getImportProtectionRulesForEnvironment suggests the shared utility's return type may not perfectly match the Vite-specific EnvRules type. Consider aligning the types or adding a comment explaining why the cast is necessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts`
around lines 436 - 438, The explicit cast in getRulesForEnvironment indicates a
type mismatch; remove the unsafe "as EnvRules" and align types by either
changing getRulesForEnvironment's return type to match the utility (e.g., use
ReturnType<typeof getImportProtectionRulesForEnvironment>) or update
getImportProtectionRulesForEnvironment to return the EnvRules shape; if you must
keep a cast, add a brief comment explaining why the shapes are compatible and
add a runtime validation (or a narrow mapper) inside getRulesForEnvironment to
convert/validate the utility output into EnvRules (referencing
getRulesForEnvironment and getImportProtectionRulesForEnvironment).
packages/start-plugin-core/src/rsbuild/import-protection.ts (2)

1361-1363: Redundant assignment - was filtering intended?

relevantModules is assigned directly from allModules without any filtering. If filtering was intended (e.g., to exclude certain module types), this should be added. Otherwise, consider using allModules directly.

♻️ Remove redundant assignment
       const allModules = Array.from(context.compilation.modules)
-      const relevantModules = allModules
 
       const provider = await buildTransformResultProvider({
-        modules: relevantModules,
+        modules: allModules,
         root: config.root,
         loadOriginalCode: loadOriginalCodeFromCompilation,
         preloaded: envState.buildTransformResults,
       })

And update subsequent usages of relevantModules to allModules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines
1361 - 1363, The assignment of relevantModules to
Array.from(context.compilation.modules) is redundant because relevantModules
just mirrors allModules; either remove the irrelevant variable or implement the
intended filtering. Replace usages of relevantModules with allModules and delete
the declaration of relevantModules, or if filtering was intended, apply the
filter to produce relevantModules (e.g., filter by module type/path) where
currently const relevantModules = allModules is declared and used in this file
(look for relevantModules and allModules references in import-protection.ts and
update accordingly).

1-3: Importing normalizePath from vite in a Rsbuild adapter.

This imports normalizePath from the vite package in a Rsbuild-specific module. While this works (vite is likely a dev dependency), consider extracting path normalization to a shared utility to avoid coupling Rsbuild code to Vite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines 1 -
3, The import of normalizePath from 'vite' in import-protection.ts couples the
Rsbuild adapter to Vite; remove the direct import and instead call a shared
path-normalization utility (e.g., create or use an existing util like
normalizePathSafe or normalizeFilePath) from a common helper module; update
references in import-protection.ts to use that utility and keep the existing
resolvePath import for node:path unchanged so code uses resolvePath and the new
shared normalize function rather than importing normalizePath from 'vite'.
packages/start-plugin-core/src/rsbuild/normalized-client-build.ts (1)

268-282: Minor: Redundant null check for entryChunkFileName.

At line 270, entryChunkFileName is checked again, but it's guaranteed to be defined due to the throw on line 258-260. The check is harmless but redundant.

♻️ Optional simplification
   const rscEntrypoint = compilation.entrypoints.get('rsc')
 
-  if (rscEntrypoint && entryChunkFileName) {
+  if (rscEntrypoint) {
     const mainEntryChunk = chunksByFileName.get(entryChunkFileName)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/normalized-client-build.ts` around
lines 268 - 282, The code redundantly checks entryChunkFileName even though
earlier logic guarantees it; simplify the conditional by removing the redundant
entryChunkFileName check so the block uses only rscEntrypoint (i.e., change `if
(rscEntrypoint && entryChunkFileName)` to `if (rscEntrypoint)`), keeping the
inner lookup of `mainEntryChunk = chunksByFileName.get(entryChunkFileName)` and
the rest of the logic around `mainEntryChunk` and `rscEntrypoint.chunks`
unchanged.
packages/start-plugin-core/src/rsbuild/start-router-plugin.ts (1)

58-60: Empty catch blocks may hide unexpected errors.

The empty catch blocks silently swallow all errors, not just "module not found" errors. Consider logging or checking the error type to avoid masking unexpected failures during development.

♻️ Proposed improvement to differentiate error types
       } catch {
-        // router-plugin/rspack not available — skip
+        // router-plugin/rspack not available — skip generator
+        // Note: This also silently ignores other potential errors
       }

Alternatively, check the error code:

} catch (e: unknown) {
  if (!(e instanceof Error && 'code' in e && e.code === 'ERR_MODULE_NOT_FOUND')) {
    console.warn('[TanStack Start] Unexpected error loading router-plugin/rspack:', e)
  }
}

Also applies to: 81-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/start-router-plugin.ts` around lines
58 - 60, Replace the empty catch blocks around the dynamic import of
'router-plugin/rspack' (and the similar block at lines handling the second
import) with typed error handling: catch the error as e: unknown, check if it's
an Error with a code property equal to 'ERR_MODULE_NOT_FOUND' and silently
ignore only that case, otherwise log or warn the unexpected error (e.g.,
console.warn('[TanStack Start] Unexpected error loading router-plugin/rspack:',
e)); this preserves the original behavior for missing module but surfaces other
failures for debugging.
packages/start-plugin-core/src/import-protection/sourceLocation.ts (1)

108-130: Unify source-map normalization in one path.

normalizeSourceMap() now hardens file, version, names, and sourcesContent, but getSourceMapConsumer() still goes through toRawSourceMap() instead. Keeping two normalization paths here is easy to drift and leaves the SourceMapConsumer call site with different behavior than the new helper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts` around
lines 108 - 130, The code duplicates source-map normalization:
normalizeSourceMap hardens file/version/names/sourcesContent but
getSourceMapConsumer still uses toRawSourceMap(), causing drift; update the
getSourceMapConsumer (or the call site that uses toRawSourceMap) to pass its
input through normalizeSourceMap() before constructing the SourceMapConsumer (or
have toRawSourceMap delegate to normalizeSourceMap) so there is a single
normalization path; ensure you reference normalizeSourceMap(...) when preparing
the map and remove the separate hardening in toRawSourceMap/getSourceMapConsumer
so both consumers receive the same normalized map shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/react-start/import-protection/package.json`:
- Line 10: The current "start" script pipes the raw stdout of node -e importing
server.js (and calling resolveStartCommand()) directly into sh, which is fragile
if server.js prints any extra output; change this by replacing the one-liner
with a dedicated script (e.g., start-server.js) that imports ./server.js, calls
resolveStartCommand(), validates/escapes the returned string, and then executes
it via a safe API (child_process.exec/execFile/spawn) or writes a JSON payload
to stdout for a wrapper to parse; update the package.json "start" to run that
dedicated script instead.

In `@e2e/react-start/import-protection/server.js`:
- Around line 10-22: The function resolveDistServerEntryPath currently returns
serverJsPath even when neither server.js nor index.js exists; update
resolveDistServerEntryPath to detect the missing entry and throw a clear error
(or log and throw) instead of returning a non-existent path: after checking
serverJsPath and indexJsPath, if neither exists use distDir, serverJsPath and
indexJsPath in the error message to make it clear which files were expected and
where, and ensure any caller of resolveDistServerEntryPath will receive the
thrown error rather than a bogus path.

In `@e2e/react-start/import-protection/start-mode-config.ts`:
- Line 2: The current line force-casts process.env.BEHAVIOR with "as 'mock' |
'error'", which allows invalid values; replace it with runtime validation:
implement a small validator (e.g., isBehavior or a Set of allowed values) that
checks process.env.BEHAVIOR === 'mock' || === 'error' and only then assigns it
to the behavior const, otherwise fall back to the default 'mock' (or throw if
you prefer stricter behavior); update the variable declaration for behavior to
use that validated value instead of the type assertion so invalid env values are
handled safely.

In `@e2e/react-start/import-protection/tests/violations.utils.ts`:
- Around line 32-36: The stripBoxPrefix function is over-greedy: its /^\s*\|\s?/
pattern removes leading '|' even when it's part of code-gutter lines like "   |
^"; update the logic in stripBoxPrefix so it only strips a pipe used as a true
line prefix (e.g. at column 0) rather than any pipe after leading whitespace —
change the pipe removal from /^\s*\|\s?/ to a stricter match (e.g. /^\|\s?/) or
equivalent check so code-gutter continuation lines are preserved while still
removing box-border pipes and the existing '│' handling remains intact.

In `@e2e/vue-start/basic-vue-query/src/utils/posts.tsx`:
- Around line 10-14: The code builds a mutable queryURL and can produce
"http://localhost:undefined" when VITE_EXTERNAL_PORT is missing; change to
immutable URL construction and validate the test port: when
import.meta.env.VITE_NODE_ENV === 'test' verify
import.meta.env.VITE_EXTERNAL_PORT is present (and optionally parseInt/validate
it's a number) and throw a clear error if not, otherwise construct a const
testBase = `http://localhost:${port}` and assign queryURL to that value (do not
mutate a top-level let); reference the queryURL variable and the
import.meta.env.VITE_EXTERNAL_PORT check in posts.tsx so failures fail fast with
an explicit error message.

In `@packages/react-start-rsc/src/react-server-dom-rspack.d.ts`:
- Around line 33-45: The ambient declaration incorrectly exposes
createFromReadableStream and loadServerAction from
react-server-dom-rspack/server even though those symbols are not exported there;
remove the createFromReadableStream and loadServerAction function declarations
from the server ambient module so the typings match runtime exports (keep
decodeReply and createTemporaryReferenceSet), and if you need typings for
createFromReadableStream or loadServerAction add them to the correct module
declarations (e.g., the client-side declaration for createFromReadableStream and
the TanStack RSC resolver entry for loadServerAction) so invalid imports no
longer type-check.

In `@packages/react-start/package.json`:
- Around line 156-170: The package currently pins "@rsbuild/core" to an
unavailable pre-release "^2.0.0-rc.3"; update the dependency entry for
"@rsbuild/core" in package.json to the latest known stable "1.7.5" (or
alternatively confirm the correct pre-release tag exists and replace with the
accurate tag) and ensure the same change is reflected in devDependencies to keep
runtime and dev-time versions consistent.

In `@packages/start-plugin-core/src/import-protection/analysis.ts`:
- Around line 658-668: When handling function nodes in the visitor (the branch
that builds functionCtx using collectFunctionBindings and calls
visit(node.body)), also traverse parameter initializers so default values and
destructuring defaults are analyzed; for example, iterate node.params (and
nested patterns) and call visit on any param.initializer or default expression
using the same functionCtx before visiting node.body so usages like fn(x =
serverOnly()) are detected.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts`:
- Around line 346-355: In createImportSpecifierLocationIndex's find
implementation remove the preliminary text-scan short-circuit that uses
result.code.includes(source) and instead always call
getImportSpecifierLocationFromResult(result, source) as the single source of
truth; update the find function in the createImportSpecifierLocationIndex export
to return the result of getImportSpecifierLocationFromResult(result, source) (or
-1 only if that helper indicates no match), ensuring TransformResult is passed
through unchanged.

In `@packages/start-plugin-core/src/post-build.ts`:
- Around line 42-45: The code spreads startConfig.spa.prerender.headers without
guaranteeing it exists, which can cause a runtime/type error; update the code
that builds the prerender headers (the object using ...startConfig.spa.prerender
and HEADERS.TSS_SHELL) to guard against undefined by either defaulting headers
in the schema transform (e.g., set headers: opts.headers ?? {} inside the
prerender transform) or by changing the spread site to use a safe fallback like
... (startConfig.spa.prerender.headers ?? {}), ensuring HEADERS.TSS_SHELL is
still added.

In `@packages/start-plugin-core/src/prerender.ts`:
- Around line 235-244: The code only follows absolute and root-relative Location
headers; fix by resolving relative Location values before deciding same-origin:
read the header via response.headers.get('location'), create a resolved URL
using new URL(location, new URL(requestPathOrUrl, 'http://localhost')) (use the
same original request identifier passed into requestWithRedirects), then if
resolvedUrl.origin is localhost (or resolvedUrl.hostname === 'localhost') or the
resolved path is same-origin, call requestWithRedirects(resolvedUrl.pathname +
resolvedUrl.search + resolvedUrl.hash, options, maxRedirects - 1); otherwise
keep the existing logger.warn branch. Update handling in the isRedirectResponse
block and keep references to isRedirectResponse, requestWithRedirects, logger,
response.headers.get('location'), maxRedirects, and options.

In `@packages/start-plugin-core/src/rsbuild/dev-server.ts`:
- Around line 93-109: The error HTML directly interpolates the exception message
(e instanceof Error ? e.message : String(e)) into the Response body causing
possible HTML injection; update the dev-server error path that constructs the
Response (the sendNodeResponse / new Response call) to escape HTML special
characters in the error text before interpolation (e.g., use an escapeHtml
utility or encode the message) or render the error as text/plain, ensuring you
sanitize the value derived from e.message (or String(e)) before placing it into
the HTML response.

In `@packages/start-plugin-core/src/rsbuild/plugin.ts`:
- Around line 207-217: The plugin is reading resolvedStartConfig.root during
setup (used in registerStartCompilerTransforms) before modifyRsbuildConfig
copies rsbuildConfig.root into the resolved config, causing defaulting to
process.cwd() for apps with a custom rsbuild root; fix by deferring use of the
start root until after rsbuild config resolution—e.g. change the root argument
passed to registerStartCompilerTransforms to read from rsbuildConfig.root (or,
if rsbuildConfig may not yet exist, pass a function/closure that returns the
resolved start root after modifyRsbuildConfig runs) so
registerStartCompilerTransforms always receives the correct root instead of
process.cwd(); reference registerStartCompilerTransforms,
resolvedStartConfig.root, modifyRsbuildConfig, and rsbuildConfig.root when
making the change.

In `@packages/start-plugin-core/src/rsbuild/virtual-modules.ts`:
- Around line 315-352: getInitialContent() currently treats the provider
environment as a client and emits 'export {}' for paths.serverFnResolver, so the
provider bundle never gets the real server-fn resolver; modify the logic that
sets content[paths.serverFnResolver] to also generate the resolver when
environmentName equals the provider env (i.e., treat the provider env like the
server for this module). Concretely, update the condition that builds
resolverContent (the block using generateServerFnResolverModule with
opts.serverFnsById, includeClientReferencedCheck, useStaticImports) so it runs
when isServerEnv OR environmentName === serverFnProviderEnv (or by expanding
isServerEnv to include the provider env), and ensure
content[paths.serverFnResolver] is assigned that resolverContent for both SSR
and provider environments instead of 'export {}'. Also mirror this same change
for the other occurrence noted (lines ~511-537) so both bundles get rewritten
resolver modules.

In `@packages/start-plugin-core/tests/importProtection/analysis.exports.test.ts`:
- Around line 4-7: The import specifier list for the import from
'../../src/import-protection/analysis' is not alphabetically sorted and fails
the sort-imports rule; reorder the named imports so they are alphabetically
sorted (getMockExportNamesBySource, getNamedExports, isValidExportName) in the
import statement that currently lists isValidExportName,
getMockExportNamesBySource, getNamedExports.

In `@packages/start-plugin-core/tests/importProtection/utils.test.ts`:
- Line 23: The import of formatViolation in
packages/start-plugin-core/tests/importProtection/utils.test.ts is unused;
remove formatViolation from the import statement (the import line that currently
reads "import { formatViolation } from '../../src/import-protection/trace'") so
the test file no longer imports that symbol.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts`:
- Around line 1-4: Reorder the import block to satisfy the import/order rule by
grouping built-in modules first, then external packages: move "import { mkdtemp,
rm, writeFile } from 'node:fs/promises'" and "import { tmpdir } from 'node:os'"
to appear before external imports, then "import { join } from 'pathe'" and
finally the test helpers "import { describe, expect, it, vi } from 'vitest'";
ensure imports are consistently ordered (and optionally alphabetized) within
each group so symbols like mkdtemp, rm, writeFile, tmpdir, join, describe,
expect, it, vi are easy to locate.

---

Nitpick comments:
In `@e2e/react-start/basic/server.js`:
- Around line 16-28: The helper resolveDistServerEntryPath currently returns
serverJsPath even when neither server/server.js nor server/index.js exist;
change it to throw a clear Error when both fs.existsSync checks fail instead of
returning a non-existent path (include the tested paths like serverJsPath and
indexJsPath or distDir in the error message) so callers (e.g., the subsequent
dynamic import of the resolved path) fail fast with a descriptive message;
update any similar logic around the import to rely on this thrown error rather
than receiving a bogus path.

In `@e2e/react-start/import-protection/package.json`:
- Around line 25-26: In package.json update the dependency entries for
"@rsbuild/core" and "@rsbuild/plugin-react" to use the same caret-style version
specifier as the main package (e.g. change "2.0.0-rc.3" to "^2.0.0-rc.3") so the
version pinning is consistent across the monorepo; locate the two keys
"@rsbuild/core" and "@rsbuild/plugin-react" in
e2e/react-start/import-protection/package.json and modify their version strings
to include the leading caret.

In `@e2e/react-start/import-protection/tests/error-mode.setup.ts`:
- Around line 22-25: Resolve E2E_TOOLCHAIN once at module load and reuse that
single constant (currently declared as toolchain alongside e2ePortKey) instead
of recomputing it later; modify the later block that currently recomputes
toolchain (the code that selects the spawned command and port key around lines
126–130) to reference the module-scoped toolchain and the existing e2ePortKey,
ensuring the same resolved value is used for both port-key derivation and
spawned command selection.

In `@e2e/solid-start/basic/server.js`:
- Around line 16-28: The function resolveDistServerEntryPath currently returns
serverJsPath even if neither server.js nor index.js exist; change it to check
both serverJsPath and indexJsPath and if neither exists throw an explicit Error
(including the resolved paths like serverJsPath and indexJsPath) instead of
returning a missing path so the failure surfaces immediately before the dynamic
import that uses the returned value.

In `@e2e/vue-start/basic/server.js`:
- Around line 16-28: The function resolveDistServerEntryPath currently returns
serverJsPath even when neither candidate exists, causing a later import to fail
with unclear diagnostics; update resolveDistServerEntryPath to check both
serverJsPath and indexJsPath and throw a clear Error (including the checked
paths) if neither fs.existsSync(serverJsPath) nor fs.existsSync(indexJsPath) is
true so callers get immediate, descriptive failure instead of a broken path.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts`:
- Around line 108-130: The code duplicates source-map normalization:
normalizeSourceMap hardens file/version/names/sourcesContent but
getSourceMapConsumer still uses toRawSourceMap(), causing drift; update the
getSourceMapConsumer (or the call site that uses toRawSourceMap) to pass its
input through normalizeSourceMap() before constructing the SourceMapConsumer (or
have toRawSourceMap delegate to normalizeSourceMap) so there is a single
normalization path; ensure you reference normalizeSourceMap(...) when preparing
the map and remove the separate hardening in toRawSourceMap/getSourceMapConsumer
so both consumers receive the same normalized map shape.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts`:
- Around line 1361-1363: The assignment of relevantModules to
Array.from(context.compilation.modules) is redundant because relevantModules
just mirrors allModules; either remove the irrelevant variable or implement the
intended filtering. Replace usages of relevantModules with allModules and delete
the declaration of relevantModules, or if filtering was intended, apply the
filter to produce relevantModules (e.g., filter by module type/path) where
currently const relevantModules = allModules is declared and used in this file
(look for relevantModules and allModules references in import-protection.ts and
update accordingly).
- Around line 1-3: The import of normalizePath from 'vite' in
import-protection.ts couples the Rsbuild adapter to Vite; remove the direct
import and instead call a shared path-normalization utility (e.g., create or use
an existing util like normalizePathSafe or normalizeFilePath) from a common
helper module; update references in import-protection.ts to use that utility and
keep the existing resolvePath import for node:path unchanged so code uses
resolvePath and the new shared normalize function rather than importing
normalizePath from 'vite'.

In `@packages/start-plugin-core/src/rsbuild/normalized-client-build.ts`:
- Around line 268-282: The code redundantly checks entryChunkFileName even
though earlier logic guarantees it; simplify the conditional by removing the
redundant entryChunkFileName check so the block uses only rscEntrypoint (i.e.,
change `if (rscEntrypoint && entryChunkFileName)` to `if (rscEntrypoint)`),
keeping the inner lookup of `mainEntryChunk =
chunksByFileName.get(entryChunkFileName)` and the rest of the logic around
`mainEntryChunk` and `rscEntrypoint.chunks` unchanged.

In `@packages/start-plugin-core/src/rsbuild/start-router-plugin.ts`:
- Around line 58-60: Replace the empty catch blocks around the dynamic import of
'router-plugin/rspack' (and the similar block at lines handling the second
import) with typed error handling: catch the error as e: unknown, check if it's
an Error with a code property equal to 'ERR_MODULE_NOT_FOUND' and silently
ignore only that case, otherwise log or warn the unexpected error (e.g.,
console.warn('[TanStack Start] Unexpected error loading router-plugin/rspack:',
e)); this preserves the original behavior for missing module but surfaces other
failures for debugging.

In `@packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts`:
- Around line 436-438: The explicit cast in getRulesForEnvironment indicates a
type mismatch; remove the unsafe "as EnvRules" and align types by either
changing getRulesForEnvironment's return type to match the utility (e.g., use
ReturnType<typeof getImportProtectionRulesForEnvironment>) or update
getImportProtectionRulesForEnvironment to return the EnvRules shape; if you must
keep a cast, add a brief comment explaining why the shapes are compatible and
add a runtime validation (or a narrow mapper) inside getRulesForEnvironment to
convert/validate the utility output into EnvRules (referencing
getRulesForEnvironment and getImportProtectionRulesForEnvironment).

In `@packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts`:
- Around line 2-16: Reorder the named imports in the import statement from
'../../src/import-protection/sourceLocation' so they are alphabetically sorted
(e.g., place ImportLocCache in its correct alphabetical position among
createImportSpecifierLocationIndex, buildCodeSnippet, buildLineIndex,
findOriginalUsageLocation, getOrCreateOriginalTransformResult,
normalizeSourceMap, pickOriginalCodeFromSourcesContent) to satisfy the
sort-imports ESLint rule; do the same for the type imports (SourceMapLike,
TransformResult, TransformResultProvider) so both import lists are alphabetized.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts`:
- Around line 15-16: The test uses the unsafe type any in the prerenderSpy
callback (and two other spots), which weakens strict-mode checks; update the
signature to use a concrete type for the callback parameter (e.g., import or
declare an appropriate Handler/PrerenderContext type and use { handler: Handler
} or the specific interface used by the prerender API) and replace the other two
any usages with the same type so the test preserves strict typing and catches
API drift (refer to prerenderSpy and the handler parameter name to locate
affected code).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3daa192-f5bb-408e-a4fb-0ce3c1137ec3

📥 Commits

Reviewing files that changed from the base of the PR and between cbf9ecf and 498f9fc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (110)
  • docs/start/framework/react/guide/import-protection.md
  • e2e/.gitignore
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • e2e/react-start/basic/.gitignore
  • e2e/react-start/basic/package.json
  • e2e/react-start/basic/postcss.config.mjs
  • e2e/react-start/basic/rsbuild.config.ts
  • e2e/react-start/basic/server.js
  • e2e/react-start/basic/src/routes/__root.tsx
  • e2e/react-start/basic/start-mode-config.ts
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/import-protection/package.json
  • e2e/react-start/import-protection/playwright.config.ts
  • e2e/react-start/import-protection/rsbuild.config.ts
  • e2e/react-start/import-protection/server.js
  • e2e/react-start/import-protection/start-mode-config.ts
  • e2e/react-start/import-protection/tests/error-mode.setup.ts
  • e2e/react-start/import-protection/tests/import-protection.spec.ts
  • e2e/react-start/import-protection/tests/violations.setup.ts
  • e2e/react-start/import-protection/tests/violations.utils.ts
  • e2e/react-start/import-protection/vite.config.ts
  • e2e/react-start/serialization-adapters/src/routes/server-function/late-raw-stream.tsx
  • e2e/solid-start/basic/package.json
  • e2e/solid-start/basic/postcss.config.mjs
  • e2e/solid-start/basic/rsbuild.config.ts
  • e2e/solid-start/basic/server.js
  • e2e/solid-start/basic/src/routes/__root.tsx
  • e2e/vue-start/basic-vue-query/src/utils/posts.tsx
  • e2e/vue-start/basic/package.json
  • e2e/vue-start/basic/postcss.config.mjs
  • e2e/vue-start/basic/rsbuild.config.ts
  • e2e/vue-start/basic/server.js
  • e2e/vue-start/basic/src/routes/__root.tsx
  • packages/react-router/src/Asset.tsx
  • packages/react-router/src/ClientOnly.tsx
  • packages/react-start-rsc/package.json
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/react-start-rsc/src/createCompositeComponent.ts
  • packages/react-start-rsc/src/global.d.ts
  • packages/react-start-rsc/src/react-server-dom-rspack.d.ts
  • packages/react-start-rsc/src/renderServerComponent.ts
  • packages/react-start-rsc/src/rsbuild/browser-decode.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • packages/react-start-rsc/vite.config.ts
  • packages/react-start/package.json
  • packages/react-start/src/plugin/rsbuild.ts
  • packages/react-start/vite.config.ts
  • packages/router-plugin/package.json
  • packages/router-utils/src/ast.ts
  • packages/router-utils/tests/stripTypeExports.test.ts
  • packages/solid-start/package.json
  • packages/solid-start/src/plugin/rsbuild.ts
  • packages/solid-start/vite.config.ts
  • packages/start-plugin-core/package.json
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts
  • packages/start-plugin-core/src/import-protection/INTERNALS.md
  • packages/start-plugin-core/src/import-protection/adapterUtils.ts
  • packages/start-plugin-core/src/import-protection/analysis.ts
  • packages/start-plugin-core/src/import-protection/ast.ts
  • packages/start-plugin-core/src/import-protection/constants.ts
  • packages/start-plugin-core/src/import-protection/defaults.ts
  • packages/start-plugin-core/src/import-protection/extensionlessAbsoluteIdResolver.ts
  • packages/start-plugin-core/src/import-protection/matchers.ts
  • packages/start-plugin-core/src/import-protection/rewrite.ts
  • packages/start-plugin-core/src/import-protection/sourceLocation.ts
  • packages/start-plugin-core/src/import-protection/trace.ts
  • packages/start-plugin-core/src/import-protection/utils.ts
  • packages/start-plugin-core/src/import-protection/virtualModules.ts
  • packages/start-plugin-core/src/index.ts
  • packages/start-plugin-core/src/post-build.ts
  • packages/start-plugin-core/src/prerender.ts
  • packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
  • packages/start-plugin-core/src/rsbuild/dev-server.ts
  • packages/start-plugin-core/src/rsbuild/import-protection.ts
  • packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
  • packages/start-plugin-core/src/rsbuild/planning.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/post-build.ts
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • packages/start-plugin-core/src/rsbuild/start-compiler-host.ts
  • packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
  • packages/start-plugin-core/src/rsbuild/types.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts
  • packages/start-plugin-core/src/start-compiler/load-module.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/types.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/virtualModules.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/src/vite/post-server-build.ts
  • packages/start-plugin-core/src/vite/prerender.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • packages/start-plugin-core/tests/importProtection/analysis.exports.test.ts
  • packages/start-plugin-core/tests/importProtection/analysis.usage.test.ts
  • packages/start-plugin-core/tests/importProtection/defaults.test.ts
  • packages/start-plugin-core/tests/importProtection/matchers.test.ts
  • packages/start-plugin-core/tests/importProtection/rewrite.test.ts
  • packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts
  • packages/start-plugin-core/tests/importProtection/trace.test.ts
  • packages/start-plugin-core/tests/importProtection/utils.test.ts
  • packages/start-plugin-core/tests/importProtection/virtualModules.test.ts
  • packages/start-plugin-core/tests/post-server-build.test.ts
  • packages/start-plugin-core/tests/prerender-ssrf.test.ts
  • packages/start-plugin-core/tests/rsbuild-output-directory.test.ts
  • packages/start-plugin-core/tests/rsbuild-post-build.test.ts
  • packages/vue-start/package.json
  • packages/vue-start/src/plugin/rsbuild.ts
  • packages/vue-start/vite.config.ts
💤 Files with no reviewable changes (7)
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • packages/start-plugin-core/src/import-protection/trace.ts
  • packages/react-start-rsc/src/renderServerComponent.ts
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts

Comment thread e2e/react-start/import-protection/package.json Outdated
Comment on lines +10 to +22
function resolveDistServerEntryPath() {
const serverJsPath = path.resolve(distDir, 'server', 'server.js')
if (fs.existsSync(serverJsPath)) {
return serverJsPath
}

const indexJsPath = path.resolve(distDir, 'server', 'index.js')
if (fs.existsSync(indexJsPath)) {
return indexJsPath
}

return serverJsPath
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fallback returns potentially non-existent path.

When neither server.js nor index.js exists in the server directory, line 21 returns serverJsPath anyway. This will cause a confusing runtime error from srvx rather than a clear error message here.

Consider either throwing an error or logging a warning when no valid server entry is found:

🛠️ Suggested improvement
   const indexJsPath = path.resolve(distDir, 'server', 'index.js')
   if (fs.existsSync(indexJsPath)) {
     return indexJsPath
   }

-  return serverJsPath
+  console.warn(`Warning: No server entry found at ${serverJsPath} or ${indexJsPath}`)
+  return serverJsPath
📝 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.

Suggested change
function resolveDistServerEntryPath() {
const serverJsPath = path.resolve(distDir, 'server', 'server.js')
if (fs.existsSync(serverJsPath)) {
return serverJsPath
}
const indexJsPath = path.resolve(distDir, 'server', 'index.js')
if (fs.existsSync(indexJsPath)) {
return indexJsPath
}
return serverJsPath
}
function resolveDistServerEntryPath() {
const serverJsPath = path.resolve(distDir, 'server', 'server.js')
if (fs.existsSync(serverJsPath)) {
return serverJsPath
}
const indexJsPath = path.resolve(distDir, 'server', 'index.js')
if (fs.existsSync(indexJsPath)) {
return indexJsPath
}
console.warn(`Warning: No server entry found at ${serverJsPath} or ${indexJsPath}`)
return serverJsPath
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/server.js` around lines 10 - 22, The
function resolveDistServerEntryPath currently returns serverJsPath even when
neither server.js nor index.js exists; update resolveDistServerEntryPath to
detect the missing entry and throw a clear error (or log and throw) instead of
returning a non-existent path: after checking serverJsPath and indexJsPath, if
neither exists use distDir, serverJsPath and indexJsPath in the error message to
make it clear which files were expected and where, and ensure any caller of
resolveDistServerEntryPath will receive the thrown error rather than a bogus
path.

@@ -0,0 +1,13 @@
export function getStartModeConfig() {
const behavior = (process.env.BEHAVIOR ?? 'mock') as 'mock' | 'error'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no unsafe assertion remains for BEHAVIOR union narrowing
rg -nP "process\.env\.BEHAVIOR.*as\s*'mock'\s*\|\s*'error'" -C2

Repository: TanStack/router

Length of output: 973


Replace the type assertion with runtime validation for BEHAVIOR.

Line 2 uses as 'mock' | 'error' to force-cast any string, allowing invalid env values to silently pass through. Implement proper runtime narrowing instead.

Suggested fix
 export function getStartModeConfig() {
-  const behavior = (process.env.BEHAVIOR ?? 'mock') as 'mock' | 'error'
+  const rawBehavior = process.env.BEHAVIOR
+  const behavior: 'mock' | 'error' =
+    rawBehavior === 'error' || rawBehavior === 'mock' ? rawBehavior : 'mock'
📝 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.

Suggested change
const behavior = (process.env.BEHAVIOR ?? 'mock') as 'mock' | 'error'
export function getStartModeConfig() {
const rawBehavior = process.env.BEHAVIOR
const behavior: 'mock' | 'error' =
rawBehavior === 'error' || rawBehavior === 'mock' ? rawBehavior : 'mock'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/start-mode-config.ts` at line 2, The
current line force-casts process.env.BEHAVIOR with "as 'mock' | 'error'", which
allows invalid values; replace it with runtime validation: implement a small
validator (e.g., isBehavior or a Set of allowed values) that checks
process.env.BEHAVIOR === 'mock' || === 'error' and only then assigns it to the
behavior const, otherwise fall back to the default 'mock' (or throw if you
prefer stricter behavior); update the variable declaration for behavior to use
that validated value instead of the type assertion so invalid env values are
handled safely.

Comment on lines +32 to +36
const stripBoxPrefix = (line: string) =>
line
.replace(/^\s*│\s?/, '')
.replace(/^\s*\|\s?/, '')
.trimEnd()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve code-gutter lines when stripping | prefixes

Line 35 removes any leading |, which can also strip snippet continuation lines (e.g. | ^) in non-boxed logs and break snippet parsing.

Proposed fix
-  const stripBoxPrefix = (line: string) =>
-    line
-      .replace(/^\s*│\s?/, '')
-      .replace(/^\s*\|\s?/, '')
-      .trimEnd()
+  const stripBoxPrefix = (line: string) => {
+    const stripped = line.replace(/^\s*│\s?/, '').trimEnd()
+    // Strip ASCII border prefixes, but keep code-gutter continuation lines.
+    return /^\s*\|\s+(?:Importer:|Import:|Resolved:|Denied by|Trace:|Code:|\d+\.|[> ]\s*\d+\s*\|)/.test(
+      stripped,
+    )
+      ? stripped.replace(/^\s*\|\s?/, '').trimEnd()
+      : stripped
+  }
📝 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.

Suggested change
const stripBoxPrefix = (line: string) =>
line
.replace(/^\s*\s?/, '')
.replace(/^\s*\|\s?/, '')
.trimEnd()
const stripBoxPrefix = (line: string) => {
const stripped = line.replace(/^\s*\s?/, '').trimEnd()
// Strip ASCII border prefixes, but keep code-gutter continuation lines.
return /^\s*\|\s+(?:Importer:|Import:|Resolved:|Denied by|Trace:|Code:|\d+\.|[> ]\s*\d+\s*\|)/.test(
stripped,
)
? stripped.replace(/^\s*\|\s?/, '').trimEnd()
: stripped
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/tests/violations.utils.ts` around lines 32
- 36, The stripBoxPrefix function is over-greedy: its /^\s*\|\s?/ pattern
removes leading '|' even when it's part of code-gutter lines like "   | ^";
update the logic in stripBoxPrefix so it only strips a pipe used as a true line
prefix (e.g. at column 0) rather than any pipe after leading whitespace — change
the pipe removal from /^\s*\|\s?/ to a stricter match (e.g. /^\|\s?/) or
equivalent check so code-gutter continuation lines are preserved while still
removing box-border pipes and the existing '│' handling remains intact.

Comment on lines +10 to +14
let queryURL = 'https://jsonplaceholder.typicode.com'

if (import.meta.env.VITE_NODE_ENV === 'test') {
queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when test port is missing instead of building an invalid URL.

Line 13 can silently become http://localhost:undefined when VITE_EXTERNAL_PORT is absent, which makes E2E failures harder to diagnose. Prefer immutable URL construction with explicit validation.

Suggested patch
-let queryURL = 'https://jsonplaceholder.typicode.com'
-
-if (import.meta.env.VITE_NODE_ENV === 'test') {
-  queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
-}
+const queryURL =
+  import.meta.env.VITE_NODE_ENV === 'test'
+    ? (() => {
+        const port = import.meta.env.VITE_EXTERNAL_PORT
+        if (!port) {
+          throw new Error(
+            'Missing VITE_EXTERNAL_PORT while VITE_NODE_ENV is "test"',
+          )
+        }
+        return `http://localhost:${port}`
+      })()
+    : 'https://jsonplaceholder.typicode.com'

As per coding guidelines: **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.

📝 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.

Suggested change
let queryURL = 'https://jsonplaceholder.typicode.com'
if (import.meta.env.VITE_NODE_ENV === 'test') {
queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}`
}
const queryURL =
import.meta.env.VITE_NODE_ENV === 'test'
? (() => {
const port = import.meta.env.VITE_EXTERNAL_PORT
if (!port) {
throw new Error(
'Missing VITE_EXTERNAL_PORT while VITE_NODE_ENV is "test"',
)
}
return `http://localhost:${port}`
})()
: 'https://jsonplaceholder.typicode.com'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/vue-start/basic-vue-query/src/utils/posts.tsx` around lines 10 - 14, The
code builds a mutable queryURL and can produce "http://localhost:undefined" when
VITE_EXTERNAL_PORT is missing; change to immutable URL construction and validate
the test port: when import.meta.env.VITE_NODE_ENV === 'test' verify
import.meta.env.VITE_EXTERNAL_PORT is present (and optionally parseInt/validate
it's a number) and throw a clear error if not, otherwise construct a const
testBase = `http://localhost:${port}` and assign queryURL to that value (do not
mutate a top-level let); reference the queryURL variable and the
import.meta.env.VITE_EXTERNAL_PORT check in posts.tsx so failures fail fast with
an explicit error message.

Comment thread packages/start-plugin-core/src/rsbuild/plugin.ts
Comment thread packages/start-plugin-core/src/rsbuild/virtual-modules.ts
} from '../../src/import-protection-plugin/utils'
} from '../../src/import-protection/utils'
import { compileMatchers } from '../../src/import-protection/matchers'
import { formatViolation } from '../../src/import-protection/trace'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused import formatViolation.

Static analysis indicates formatViolation is imported but never used in this test file.

🧹 Proposed fix
-import { formatViolation } from '../../src/import-protection/trace'
📝 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.

Suggested change
import { formatViolation } from '../../src/import-protection/trace'
🧰 Tools
🪛 ESLint

[error] 23-23: 'formatViolation' is defined but never used.

(unused-imports/no-unused-imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/importProtection/utils.test.ts` at line 23,
The import of formatViolation in
packages/start-plugin-core/tests/importProtection/utils.test.ts is unused;
remove formatViolation from the import statement (the import line that currently
reads "import { formatViolation } from '../../src/import-protection/trace'") so
the test file no longer imports that symbol.

Comment on lines +1 to +4
import { describe, expect, it, vi } from 'vitest'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'pathe'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix import ordering to satisfy import/order.

This import block order will trigger the lint rule in this file.

Suggested fix
-import { describe, expect, it, vi } from 'vitest'
 import { mkdtemp, rm, writeFile } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'pathe'
+import { describe, expect, it, vi } from 'vitest'
📝 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.

Suggested change
import { describe, expect, it, vi } from 'vitest'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'pathe'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'pathe'
import { describe, expect, it, vi } from 'vitest'
🧰 Tools
🪛 ESLint

[error] 1-1: vitest import should occur after import of node:os

(import/order)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts` around lines 1 -
4, Reorder the import block to satisfy the import/order rule by grouping
built-in modules first, then external packages: move "import { mkdtemp, rm,
writeFile } from 'node:fs/promises'" and "import { tmpdir } from 'node:os'" to
appear before external imports, then "import { join } from 'pathe'" and finally
the test helpers "import { describe, expect, it, vi } from 'vitest'"; ensure
imports are consistently ordered (and optionally alphabetized) within each group
so symbols like mkdtemp, rm, writeFile, tmpdir, join, describe, expect, it, vi
are easy to locate.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

🚀 Changeset Version Preview

6 package(s) bumped directly, 4 bumped as dependents.

🟨 Minor bumps

Package Version Reason
@tanstack/start-plugin-core 1.167.35 → 1.168.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/react-start 1.167.42 → 1.167.43 Changeset
@tanstack/react-start-rsc 0.0.21 → 0.0.22 Changeset
@tanstack/router-plugin 1.167.22 → 1.167.23 Changeset
@tanstack/router-utils 1.161.6 → 1.161.7 Changeset
@tanstack/solid-start 1.167.37 → 1.167.38 Changeset
@tanstack/router-cli 1.166.33 → 1.166.34 Dependent
@tanstack/router-generator 1.166.32 → 1.166.33 Dependent
@tanstack/router-vite-plugin 1.166.37 → 1.166.38 Dependent
@tanstack/vue-start 1.167.36 → 1.167.37 Dependent

nx-cloud[bot]

This comment was marked as outdated.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 19, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7228

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7228

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7228

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7228

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7228

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7228

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7228

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7228

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7228

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7228

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7228

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7228

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7228

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7228

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7228

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7228

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7228

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7228

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7228

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7228

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7228

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7228

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7228

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7228

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7228

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7228

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7228

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7228

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7228

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7228

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7228

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7228

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7228

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7228

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7228

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7228

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7228

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7228

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7228

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7228

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7228

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7228

commit: d59b5be

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/start-plugin-core/src/import-protection/sourceLocation.ts (1)

348-353: ⚠️ Potential issue | 🟡 Minor

Remove the raw includes short-circuit.

This reintroduces a text scan ahead of the parser-based resolver, so valid specifiers can still be missed when the emitted string literal is escaped or rewritten differently. getImportSpecifierLocationFromResult() should stay the source of truth here.

Proposed change
   return {
     find(result: TransformResult, source: string): number {
-      if (!result.code.includes(source)) {
-        return -1
-      }
-
       return getImportSpecifierLocationFromResult(result, source)
     },
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts` around
lines 348 - 353, The short-circuit text-scan using result.code.includes(source)
in the find method should be removed so getImportSpecifierLocationFromResult
remains the single source of truth; update the find(TransformResult, source)
implementation to always call and return
getImportSpecifierLocationFromResult(result, source) (and not pre-check with
includes), ensuring no manual string-based checks are performed before
delegating to the parser-based resolver.
packages/start-plugin-core/tests/rsbuild-post-build.test.ts (1)

1-4: ⚠️ Potential issue | 🟡 Minor

Fix import ordering to satisfy import/order.

Node.js built-in modules should be imported before external packages.

📦 Suggested reordering
-import { describe, expect, it, vi } from 'vitest'
 import { mkdtemp, rm, writeFile } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'pathe'
+import { describe, expect, it, vi } from 'vitest'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts` around lines 1 -
4, Reorder the imports in the test so Node.js built-in modules are imported
before external packages: move "import { mkdtemp, rm, writeFile } from
'node:fs/promises'" and "import { tmpdir } from 'node:os'" to appear before the
external imports ("import { join } from 'pathe'" and "import { describe, expect,
it, vi } from 'vitest'"); keep the same imported symbols (mkdtemp, rm,
writeFile, tmpdir, join, describe, expect, it, vi) but adjust the import order
to satisfy import/order.
🧹 Nitpick comments (11)
e2e/react-start/import-protection/tests/error-mode.setup.ts (1)

126-126: Remove redundant shadowed variable.

The local toolchain on line 126 shadows the identical module-level variable from line 22. Use the module-level toolchain directly for consistency with how e2ePortKey is used.

♻️ Proposed fix
   const out = fs.createWriteStream(logFile)
-  const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
   const command =
     toolchain === 'rsbuild'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/tests/error-mode.setup.ts` at line 126,
Remove the redundant local declaration of toolchain in error-mode.setup.ts (the
const toolchain on line 126) which shadows the module-level toolchain; instead,
delete that local const and reference the existing module-level variable
wherever that local was used (same pattern as e2ePortKey) so the file
consistently uses the single module-level toolchain variable.
packages/solid-start/src/plugin/rsbuild.ts (1)

51-74: Consider adding a type annotation for babelOptions to improve type clarity.

The babelOptions parameter relies on implicit typing from the tap callback. While webpack-chain's types are loose by design, an explicit type annotation would improve readability.

♻️ Suggested type annotation
-          rule.use(CHAIN_ID.USE.BABEL).tap((babelOptions) => {
+          rule.use(CHAIN_ID.USE.BABEL).tap((babelOptions: { presets?: Array<unknown> }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/solid-start/src/plugin/rsbuild.ts` around lines 51 - 74, Add an
explicit type annotation for the tap callback parameter `babelOptions` to
improve clarity: in the `rule.use(CHAIN_ID.USE.BABEL).tap(...)` callback
annotate `babelOptions` (used where you access `babelOptions.presets` and map
presets) with an appropriate Babel type such as `TransformOptions` (imported
from '@babel/core') or your project's babel options type so the presets mapping
is typed and clearer to readers.
packages/start-plugin-core/src/import-protection/sourceLocation.ts (1)

335-340: Make the synthesized original result terminal.

The object created on Lines 336-340 keeps originalCode, so calling getOrCreateOriginalTransformResult() on that returned value creates another wrapper instead of reusing the same result. Clearing originalCode here keeps the helper idempotent and avoids unbounded nesting.

Proposed change
     result.originalResult = {
       code: result.originalCode,
       map: undefined,
-      originalCode: result.originalCode,
+      originalCode: undefined,
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts` around
lines 335 - 340, The synthesized originalResult object assigned when
result.originalResult is missing keeps originalCode set, which causes
getOrCreateOriginalTransformResult() to wrap it again; update the creation of
the fallback object (the block that sets result.originalResult = { code:
result.originalCode, map: undefined, originalCode: result.originalCode }) to
clear or omit originalCode (e.g., set originalCode: undefined) so the
synthesized original result is terminal and the helper becomes idempotent.
packages/start-plugin-core/src/rsbuild/virtual-modules.ts (1)

68-74: Consider escaping the devClientEntryUrl in the generated module.

The devClientEntryUrl is interpolated directly into a template string. If it contains special characters (unlikely but possible with custom configurations), it could break the generated JavaScript.

Suggested fix
 function generateManifestModuleDev(devClientEntryUrl: string): string {
   return `const fallbackManifest = {
   routes: {},
-  clientEntry: '${devClientEntryUrl}',
+  clientEntry: ${JSON.stringify(devClientEntryUrl)},
 }
 export const tsrStartManifest = () => globalThis[${JSON.stringify(DEV_START_MANIFEST_GLOBAL)}] ?? fallbackManifest`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/virtual-modules.ts` around lines 68 -
74, The template in generateManifestModuleDev interpolates devClientEntryUrl
directly which can break emitted JS if it contains special characters; update
generateManifestModuleDev to safely escape/serialize devClientEntryUrl when
embedding it (e.g. use JSON.stringify(devClientEntryUrl) or an equivalent
serializer) so the generated module assigns clientEntry to a properly
quoted/escaped string, keeping DEV_START_MANIFEST_GLOBAL usage unchanged.
packages/start-plugin-core/src/prerender.ts (1)

114-116: Mutating startConfig.pages during crawl could cause issues.

When page.fromCrawl is true, the page is pushed directly to startConfig.pages. This mutates the config object that was passed in, which may have unintended side effects if the caller expects the config to remain unchanged.

Consider using a local array for crawled pages
   async function prerenderPages({ outputDir }: { outputDir: string }) {
     const seen = new Set<string>()
     const prerendered = new Set<string>()
+    const crawledPages: Array<Page> = []
     // ... existing code ...

     function addCrawlPageTask(page: Page) {
       if (seen.has(page.path)) return

       seen.add(page.path)

       if (page.fromCrawl) {
-        startConfig.pages.push(page)
+        crawledPages.push(page)
       }

Then merge crawledPages back if needed after prerendering completes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/prerender.ts` around lines 114 - 116, The code
mutates the incoming config by pushing crawled pages directly into
startConfig.pages when page.fromCrawl is true; instead, collect crawled pages
into a local array (e.g., crawledPages) inside the prerender routine and avoid
modifying startConfig.pages during the crawl, then merge crawledPages back into
the config only after prerendering completes (or return them separately); update
references to startConfig.pages and the logic that checks page.fromCrawl so it
uses the local crawledPages array until the final merge.
packages/start-plugin-core/src/vite/prerender.ts (1)

41-43: Consider adding redirect: 'manual' for consistency with Rsbuild adapter.

The Rsbuild adapter in post-build.ts (line 62) adds redirect: 'manual' to prevent fetch from auto-following redirects, allowing the prerender logic to handle them. The Vite adapter doesn't include this, which could lead to inconsistent behavior between bundlers.

Suggested fix
     request(path, options) {
       const url = new URL(path, baseUrl)
-      return fetch(new Request(url, options))
+      return fetch(new Request(url, {
+        ...options,
+        redirect: 'manual',
+      }))
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/vite/prerender.ts` around lines 41 - 43, The
request method in prerender.ts currently constructs a Request with new
Request(url, options) but doesn't set redirect mode; update the Request creation
in request(path, options) to ensure redirect: 'manual' is applied (e.g., merge
options with redirect: 'manual' so existing options are preserved) before
calling fetch, to match the Rsbuild adapter behavior and let the prerender logic
handle redirects; reference the request function and the baseUrl/Request
construction when making the change.
packages/start-plugin-core/src/rsbuild/planning.ts (2)

79-85: Guard against missing react-server-dom-rspack when RSC is enabled.

require.resolve('react-server-dom-rspack/server.node') will throw if the package isn't installed. Since RSC is optional, this could cause confusing errors when rsc: true is passed but the package is missing.

Suggested improvement

Consider wrapping with a try-catch or documenting the peer dependency requirement:

     ...(opts.rsc
       ? {
           'react-server-dom-rspack/server$':
-            require.resolve('react-server-dom-rspack/server.node'),
+            (() => {
+              try {
+                return require.resolve('react-server-dom-rspack/server.node')
+              } catch {
+                throw new Error(
+                  'react-server-dom-rspack is required when RSC is enabled. Please install it as a dependency.',
+                )
+              }
+            })(),
         }
       : {}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/planning.ts` around lines 79 - 85,
When opts.rsc is true, the inline
require.resolve('react-server-dom-rspack/server.node') can throw if that package
isn't installed; wrap the resolution in a try-catch around the expression used
in the object spread (or extract into a helper like resolveRscServerModule) and
on failure either omit the mapping (so RSC is effectively disabled) or throw a
clear, actionable error message that mentions the missing peer dependency and
how to install it; reference opts.rsc and the
'react-server-dom-rspack/server.node' resolution in your change so the mapping
is guarded and fails with a descriptive message instead of an unhandled
exception.

340-345: Clarify the inverted boolean logic for includeClientReferencedCheck.

The expression !(opts.rscEnabled ? true : opts.ssrIsProvider) is difficult to parse. When rscEnabled is true, this evaluates to false. When rscEnabled is false, it evaluates to !ssrIsProvider.

Suggested clarification
-        includeClientReferencedCheck: !(opts.rscEnabled
-          ? true
-          : opts.ssrIsProvider),
+        // Skip client-referenced checks when RSC is enabled or when SSR is the provider
+        includeClientReferencedCheck: !opts.rscEnabled && !opts.ssrIsProvider,

This applies to lines 342-344 in this file. The same pattern appears in virtual-modules.ts at lines 342-344 and 514-516.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/planning.ts` around lines 340 - 345,
The inverted boolean expression for includeClientReferencedCheck is hard to
read; replace the negated ternary "!(opts.rscEnabled ? true :
opts.ssrIsProvider)" with an explicit condition that reads the intent:
includeClientReferencedCheck should be true only when opts.rscEnabled is false
AND opts.ssrIsProvider is false (i.e., assign includeClientReferencedCheck =
!opts.rscEnabled && !opts.ssrIsProvider using the includeClientReferencedCheck
identifier and opts.rscEnabled/opts.ssrIsProvider symbols), and apply the same
replacement in the corresponding occurrences in virtual-modules.ts.
packages/start-plugin-core/src/rsbuild/start-compiler-host.ts (1)

112-117: The loadModule callback has a self-reference that could be confusing.

The compiler! non-null assertion inside the loadModule callback references the outer compiler variable, which is assigned on line 143 after this closure is created. While this works because loadModule is called asynchronously after compiler is set, it's a subtle temporal dependency.

Consider using a getter pattern for clarity
+          const getCompiler = () => compiler!
           loadModule: async (moduleId: string) => {
             await loadModuleForRsbuildCompiler({
-              compiler: compiler!,
+              compiler: getCompiler(),
               id: moduleId,
             })
           },

This makes the deferred access pattern more explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/start-compiler-host.ts` around lines
112 - 117, The loadModule callback captures the outer variable compiler with a
non-null assertion (compiler!) creating a subtle temporal dependency; change
loadModule to read the latest compiler via a getter or accessor at call time
(e.g., create a getCompiler() helper that throws if compiler is unset) and pass
that result into loadModuleForRsbuildCompiler instead of using compiler!
directly; update the loadModule implementation and reference
loadModuleForRsbuildCompiler, loadModule, and the compiler variable accordingly
so the deferred access is explicit and safe.
packages/start-plugin-core/src/rsbuild/post-build.ts (1)

45-74: Consider adding error handling for handler loading failures.

If loadRequestHandler fails (e.g., server bundle not found, syntax error), the error will propagate without context. Adding a try-catch with a descriptive error would improve debuggability.

Suggested improvement
   function getRequestHandler() {
     if (!requestHandlerPromise) {
-      requestHandlerPromise = loadRequestHandler(serverOutputDirectory)
+      requestHandlerPromise = loadRequestHandler(serverOutputDirectory).catch(
+        (error) => {
+          throw new Error(
+            `Failed to load Rsbuild server bundle for prerendering`,
+            { cause: error },
+          )
+        },
+      )
     }

     return requestHandlerPromise
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/post-build.ts` around lines 45 - 74,
The getRequestHandler function currently assigns requestHandlerPromise =
loadRequestHandler(serverOutputDirectory) without handling failures; wrap the
loadRequestHandler call in a try/catch (or attach a .catch) so any load error is
rethrown with a clear, descriptive message including serverOutputDirectory and
the original error (reference symbols: getRequestHandler, requestHandlerPromise,
loadRequestHandler, serverOutputDirectory) and ensure the rejected state is
preserved so callers of request() receive the enhanced error.
packages/start-plugin-core/tests/rsbuild-post-build.test.ts (1)

39-58: Consider adding explicit mock cleanup for test isolation.

While Vitest automatically cleans up mocks between test files, explicitly resetting the module cache ensures this test won't affect others if additional tests are added to this suite later.

🧹 Proposed addition for mock cleanup
     } finally {
       await rm(serverOutputDirectory, { recursive: true, force: true })
+      vi.resetModules()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts` around lines 39
- 58, Add explicit mock/module cleanup to this test by resetting Vitest's module
cache and mocks after the test run: call vi.resetModules() (to clear the module
cache) and vi.resetAllMocks() or vi.restoreAllMocks() (to clear spies/mocks such
as prerenderSpy) either in the test's finally block after the rm(...) cleanup or
in an afterEach for the file; this ensures imports like postBuildWithRsbuild and
spies on prerenderSpy can't leak into other tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/start-plugin-core/src/prerender.ts`:
- Around line 44-50: The call to validateAndNormalizePrerenderPages is being
performed twice: once when assigning startConfig.pages and again inside the
prerenderPages function; remove the redundant second call inside prerenderPages
(the validateAndNormalizePrerenderPages invocation within prerenderPages) and
rely on the already-normalized startConfig.pages, ensuring any code in
prerenderPages uses startConfig.pages (or its local parameter) directly without
re-validating.

In `@packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts`:
- Around line 2-16: The import list in sourceLocation.test.ts is not
alphabetically ordered per eslint sort-imports; move the ImportLocCache
specifier so that it appears after buildCodeSnippet (i.e., reorder the named
imports in the import statement to: createImportSpecifierLocationIndex,
buildCodeSnippet, ImportLocCache, buildLineIndex, findOriginalUsageLocation,
getOrCreateOriginalTransformResult, normalizeSourceMap,
pickOriginalCodeFromSourcesContent) to satisfy the rule and keep the rest of the
specifiers unchanged.

---

Duplicate comments:
In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts`:
- Around line 348-353: The short-circuit text-scan using
result.code.includes(source) in the find method should be removed so
getImportSpecifierLocationFromResult remains the single source of truth; update
the find(TransformResult, source) implementation to always call and return
getImportSpecifierLocationFromResult(result, source) (and not pre-check with
includes), ensuring no manual string-based checks are performed before
delegating to the parser-based resolver.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts`:
- Around line 1-4: Reorder the imports in the test so Node.js built-in modules
are imported before external packages: move "import { mkdtemp, rm, writeFile }
from 'node:fs/promises'" and "import { tmpdir } from 'node:os'" to appear before
the external imports ("import { join } from 'pathe'" and "import { describe,
expect, it, vi } from 'vitest'"); keep the same imported symbols (mkdtemp, rm,
writeFile, tmpdir, join, describe, expect, it, vi) but adjust the import order
to satisfy import/order.

---

Nitpick comments:
In `@e2e/react-start/import-protection/tests/error-mode.setup.ts`:
- Line 126: Remove the redundant local declaration of toolchain in
error-mode.setup.ts (the const toolchain on line 126) which shadows the
module-level toolchain; instead, delete that local const and reference the
existing module-level variable wherever that local was used (same pattern as
e2ePortKey) so the file consistently uses the single module-level toolchain
variable.

In `@packages/solid-start/src/plugin/rsbuild.ts`:
- Around line 51-74: Add an explicit type annotation for the tap callback
parameter `babelOptions` to improve clarity: in the
`rule.use(CHAIN_ID.USE.BABEL).tap(...)` callback annotate `babelOptions` (used
where you access `babelOptions.presets` and map presets) with an appropriate
Babel type such as `TransformOptions` (imported from '@babel/core') or your
project's babel options type so the presets mapping is typed and clearer to
readers.

In `@packages/start-plugin-core/src/import-protection/sourceLocation.ts`:
- Around line 335-340: The synthesized originalResult object assigned when
result.originalResult is missing keeps originalCode set, which causes
getOrCreateOriginalTransformResult() to wrap it again; update the creation of
the fallback object (the block that sets result.originalResult = { code:
result.originalCode, map: undefined, originalCode: result.originalCode }) to
clear or omit originalCode (e.g., set originalCode: undefined) so the
synthesized original result is terminal and the helper becomes idempotent.

In `@packages/start-plugin-core/src/prerender.ts`:
- Around line 114-116: The code mutates the incoming config by pushing crawled
pages directly into startConfig.pages when page.fromCrawl is true; instead,
collect crawled pages into a local array (e.g., crawledPages) inside the
prerender routine and avoid modifying startConfig.pages during the crawl, then
merge crawledPages back into the config only after prerendering completes (or
return them separately); update references to startConfig.pages and the logic
that checks page.fromCrawl so it uses the local crawledPages array until the
final merge.

In `@packages/start-plugin-core/src/rsbuild/planning.ts`:
- Around line 79-85: When opts.rsc is true, the inline
require.resolve('react-server-dom-rspack/server.node') can throw if that package
isn't installed; wrap the resolution in a try-catch around the expression used
in the object spread (or extract into a helper like resolveRscServerModule) and
on failure either omit the mapping (so RSC is effectively disabled) or throw a
clear, actionable error message that mentions the missing peer dependency and
how to install it; reference opts.rsc and the
'react-server-dom-rspack/server.node' resolution in your change so the mapping
is guarded and fails with a descriptive message instead of an unhandled
exception.
- Around line 340-345: The inverted boolean expression for
includeClientReferencedCheck is hard to read; replace the negated ternary
"!(opts.rscEnabled ? true : opts.ssrIsProvider)" with an explicit condition that
reads the intent: includeClientReferencedCheck should be true only when
opts.rscEnabled is false AND opts.ssrIsProvider is false (i.e., assign
includeClientReferencedCheck = !opts.rscEnabled && !opts.ssrIsProvider using the
includeClientReferencedCheck identifier and opts.rscEnabled/opts.ssrIsProvider
symbols), and apply the same replacement in the corresponding occurrences in
virtual-modules.ts.

In `@packages/start-plugin-core/src/rsbuild/post-build.ts`:
- Around line 45-74: The getRequestHandler function currently assigns
requestHandlerPromise = loadRequestHandler(serverOutputDirectory) without
handling failures; wrap the loadRequestHandler call in a try/catch (or attach a
.catch) so any load error is rethrown with a clear, descriptive message
including serverOutputDirectory and the original error (reference symbols:
getRequestHandler, requestHandlerPromise, loadRequestHandler,
serverOutputDirectory) and ensure the rejected state is preserved so callers of
request() receive the enhanced error.

In `@packages/start-plugin-core/src/rsbuild/start-compiler-host.ts`:
- Around line 112-117: The loadModule callback captures the outer variable
compiler with a non-null assertion (compiler!) creating a subtle temporal
dependency; change loadModule to read the latest compiler via a getter or
accessor at call time (e.g., create a getCompiler() helper that throws if
compiler is unset) and pass that result into loadModuleForRsbuildCompiler
instead of using compiler! directly; update the loadModule implementation and
reference loadModuleForRsbuildCompiler, loadModule, and the compiler variable
accordingly so the deferred access is explicit and safe.

In `@packages/start-plugin-core/src/rsbuild/virtual-modules.ts`:
- Around line 68-74: The template in generateManifestModuleDev interpolates
devClientEntryUrl directly which can break emitted JS if it contains special
characters; update generateManifestModuleDev to safely escape/serialize
devClientEntryUrl when embedding it (e.g. use JSON.stringify(devClientEntryUrl)
or an equivalent serializer) so the generated module assigns clientEntry to a
properly quoted/escaped string, keeping DEV_START_MANIFEST_GLOBAL usage
unchanged.

In `@packages/start-plugin-core/src/vite/prerender.ts`:
- Around line 41-43: The request method in prerender.ts currently constructs a
Request with new Request(url, options) but doesn't set redirect mode; update the
Request creation in request(path, options) to ensure redirect: 'manual' is
applied (e.g., merge options with redirect: 'manual' so existing options are
preserved) before calling fetch, to match the Rsbuild adapter behavior and let
the prerender logic handle redirects; reference the request function and the
baseUrl/Request construction when making the change.

In `@packages/start-plugin-core/tests/rsbuild-post-build.test.ts`:
- Around line 39-58: Add explicit mock/module cleanup to this test by resetting
Vitest's module cache and mocks after the test run: call vi.resetModules() (to
clear the module cache) and vi.resetAllMocks() or vi.restoreAllMocks() (to clear
spies/mocks such as prerenderSpy) either in the test's finally block after the
rm(...) cleanup or in an afterEach for the file; this ensures imports like
postBuildWithRsbuild and spies on prerenderSpy can't leak into other 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 288ebc7d-db5f-4392-8e02-3abfee4526a0

📥 Commits

Reviewing files that changed from the base of the PR and between 498f9fc and 7e33653.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (111)
  • .changeset/add-rsbuild-start-support.md
  • docs/start/framework/react/guide/import-protection.md
  • e2e/.gitignore
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • e2e/react-start/basic/.gitignore
  • e2e/react-start/basic/package.json
  • e2e/react-start/basic/postcss.config.mjs
  • e2e/react-start/basic/rsbuild.config.ts
  • e2e/react-start/basic/server.js
  • e2e/react-start/basic/src/routes/__root.tsx
  • e2e/react-start/basic/start-mode-config.ts
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/import-protection/package.json
  • e2e/react-start/import-protection/playwright.config.ts
  • e2e/react-start/import-protection/rsbuild.config.ts
  • e2e/react-start/import-protection/server.js
  • e2e/react-start/import-protection/start-mode-config.ts
  • e2e/react-start/import-protection/tests/error-mode.setup.ts
  • e2e/react-start/import-protection/tests/import-protection.spec.ts
  • e2e/react-start/import-protection/tests/violations.setup.ts
  • e2e/react-start/import-protection/tests/violations.utils.ts
  • e2e/react-start/import-protection/vite.config.ts
  • e2e/react-start/serialization-adapters/src/routes/server-function/late-raw-stream.tsx
  • e2e/solid-start/basic/package.json
  • e2e/solid-start/basic/postcss.config.mjs
  • e2e/solid-start/basic/rsbuild.config.ts
  • e2e/solid-start/basic/server.js
  • e2e/solid-start/basic/src/routes/__root.tsx
  • e2e/vue-start/basic-vue-query/src/utils/posts.tsx
  • e2e/vue-start/basic/package.json
  • e2e/vue-start/basic/postcss.config.mjs
  • e2e/vue-start/basic/rsbuild.config.ts
  • e2e/vue-start/basic/server.js
  • e2e/vue-start/basic/src/routes/__root.tsx
  • packages/react-router/src/Asset.tsx
  • packages/react-router/src/ClientOnly.tsx
  • packages/react-start-rsc/package.json
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/react-start-rsc/src/createCompositeComponent.ts
  • packages/react-start-rsc/src/global.d.ts
  • packages/react-start-rsc/src/react-server-dom-rspack.d.ts
  • packages/react-start-rsc/src/renderServerComponent.ts
  • packages/react-start-rsc/src/rsbuild/browser-decode.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • packages/react-start-rsc/vite.config.ts
  • packages/react-start/package.json
  • packages/react-start/src/plugin/rsbuild.ts
  • packages/react-start/vite.config.ts
  • packages/router-plugin/package.json
  • packages/router-utils/src/ast.ts
  • packages/router-utils/tests/stripTypeExports.test.ts
  • packages/solid-start/package.json
  • packages/solid-start/src/plugin/rsbuild.ts
  • packages/solid-start/vite.config.ts
  • packages/start-plugin-core/package.json
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts
  • packages/start-plugin-core/src/import-protection/INTERNALS.md
  • packages/start-plugin-core/src/import-protection/adapterUtils.ts
  • packages/start-plugin-core/src/import-protection/analysis.ts
  • packages/start-plugin-core/src/import-protection/ast.ts
  • packages/start-plugin-core/src/import-protection/constants.ts
  • packages/start-plugin-core/src/import-protection/defaults.ts
  • packages/start-plugin-core/src/import-protection/extensionlessAbsoluteIdResolver.ts
  • packages/start-plugin-core/src/import-protection/matchers.ts
  • packages/start-plugin-core/src/import-protection/rewrite.ts
  • packages/start-plugin-core/src/import-protection/sourceLocation.ts
  • packages/start-plugin-core/src/import-protection/trace.ts
  • packages/start-plugin-core/src/import-protection/utils.ts
  • packages/start-plugin-core/src/import-protection/virtualModules.ts
  • packages/start-plugin-core/src/index.ts
  • packages/start-plugin-core/src/post-build.ts
  • packages/start-plugin-core/src/prerender.ts
  • packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
  • packages/start-plugin-core/src/rsbuild/dev-server.ts
  • packages/start-plugin-core/src/rsbuild/import-protection.ts
  • packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
  • packages/start-plugin-core/src/rsbuild/planning.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/post-build.ts
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • packages/start-plugin-core/src/rsbuild/start-compiler-host.ts
  • packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
  • packages/start-plugin-core/src/rsbuild/types.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts
  • packages/start-plugin-core/src/start-compiler/load-module.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/types.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/virtualModules.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/src/vite/post-server-build.ts
  • packages/start-plugin-core/src/vite/prerender.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • packages/start-plugin-core/tests/importProtection/analysis.exports.test.ts
  • packages/start-plugin-core/tests/importProtection/analysis.usage.test.ts
  • packages/start-plugin-core/tests/importProtection/defaults.test.ts
  • packages/start-plugin-core/tests/importProtection/matchers.test.ts
  • packages/start-plugin-core/tests/importProtection/rewrite.test.ts
  • packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts
  • packages/start-plugin-core/tests/importProtection/trace.test.ts
  • packages/start-plugin-core/tests/importProtection/utils.test.ts
  • packages/start-plugin-core/tests/importProtection/virtualModules.test.ts
  • packages/start-plugin-core/tests/post-server-build.test.ts
  • packages/start-plugin-core/tests/prerender-ssrf.test.ts
  • packages/start-plugin-core/tests/rsbuild-output-directory.test.ts
  • packages/start-plugin-core/tests/rsbuild-post-build.test.ts
  • packages/vue-start/package.json
  • packages/vue-start/src/plugin/rsbuild.ts
  • packages/vue-start/vite.config.ts
💤 Files with no reviewable changes (7)
  • packages/react-start-rsc/src/renderServerComponent.ts
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • packages/start-plugin-core/src/import-protection/trace.ts
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts
✅ Files skipped from review due to trivial changes (36)
  • e2e/react-start/basic/.gitignore
  • packages/react-router/src/ClientOnly.tsx
  • .changeset/add-rsbuild-start-support.md
  • packages/react-start-rsc/src/createCompositeComponent.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/tests/importProtection/defaults.test.ts
  • packages/react-router/src/Asset.tsx
  • packages/vue-start/vite.config.ts
  • e2e/react-start/basic/src/routes/__root.tsx
  • e2e/vue-start/basic/postcss.config.mjs
  • e2e/solid-start/basic/postcss.config.mjs
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • packages/router-plugin/package.json
  • packages/react-start/vite.config.ts
  • e2e/solid-start/basic/rsbuild.config.ts
  • packages/start-plugin-core/tests/importProtection/virtualModules.test.ts
  • e2e/.gitignore
  • e2e/react-start/import-protection/start-mode-config.ts
  • packages/react-start-rsc/src/rsbuild/browser-decode.ts
  • packages/start-plugin-core/src/index.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • e2e/react-start/basic/postcss.config.mjs
  • packages/start-plugin-core/tests/prerender-ssrf.test.ts
  • packages/start-plugin-core/tests/importProtection/rewrite.test.ts
  • packages/start-plugin-core/tests/rsbuild-output-directory.test.ts
  • e2e/react-start/basic/start-mode-config.ts
  • packages/start-plugin-core/src/import-protection/constants.ts
  • e2e/react-start/import-protection/rsbuild.config.ts
  • packages/start-plugin-core/tests/importProtection/matchers.test.ts
  • e2e/react-start/serialization-adapters/src/routes/server-function/late-raw-stream.tsx
  • packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
  • packages/react-start-rsc/src/react-server-dom-rspack.d.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/import-protection.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/types.ts
🚧 Files skipped from review as they are similar to previous changes (40)
  • e2e/react-start/basic/server.js
  • packages/start-plugin-core/src/start-compiler/load-module.ts
  • e2e/react-start/basic/rsbuild.config.ts
  • e2e/solid-start/basic/server.js
  • packages/start-plugin-core/tests/post-server-build.test.ts
  • e2e/solid-start/basic/package.json
  • packages/vue-start/src/plugin/rsbuild.ts
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/import-protection/server.js
  • e2e/react-start/import-protection/tests/violations.utils.ts
  • packages/react-start-rsc/vite.config.ts
  • e2e/vue-start/basic/rsbuild.config.ts
  • packages/solid-start/vite.config.ts
  • e2e/vue-start/basic/src/routes/__root.tsx
  • e2e/vue-start/basic/package.json
  • packages/react-start/src/plugin/rsbuild.ts
  • docs/start/framework/react/guide/import-protection.md
  • packages/vue-start/package.json
  • e2e/react-start/import-protection/vite.config.ts
  • packages/react-start/package.json
  • packages/start-plugin-core/src/rsbuild/dev-server.ts
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • e2e/vue-start/basic-vue-query/src/utils/posts.tsx
  • packages/start-plugin-core/tests/importProtection/analysis.usage.test.ts
  • e2e/react-start/import-protection/package.json
  • packages/start-plugin-core/src/import-protection/utils.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/virtualModules.ts
  • packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
  • packages/react-start-rsc/src/global.d.ts
  • packages/start-plugin-core/src/post-build.ts
  • e2e/react-start/import-protection/tests/import-protection.spec.ts
  • e2e/solid-start/basic/src/routes/__root.tsx
  • packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
  • packages/start-plugin-core/src/rsbuild/types.ts
  • packages/start-plugin-core/tests/importProtection/trace.test.ts
  • e2e/react-start/import-protection/tests/violations.setup.ts
  • packages/solid-start/package.json
  • packages/react-start-rsc/package.json
  • packages/router-utils/tests/stripTypeExports.test.ts
  • packages/start-plugin-core/src/import-protection/virtualModules.ts

Comment on lines +44 to +50
const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
const routerBaseUrl = new URL(routerBasePath, 'http://localhost')

startConfig.pages = validateAndNormalizePrerenderPages(
startConfig.pages,
routerBaseUrl,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

validateAndNormalizePrerenderPages is called twice on the same pages.

The function is called at line 47-50 on startConfig.pages, then again at lines 93-96 inside prerenderPages. Since the first call already normalizes the pages and assigns back to startConfig.pages, the second call is redundant.

Suggested fix

Remove the duplicate validation inside prerenderPages:

   async function prerenderPages({ outputDir }: { outputDir: string }) {
     const seen = new Set<string>()
     const prerendered = new Set<string>()
     const retriesByPath = new Map<string, number>()
     const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length
     logger.info(`Concurrency: ${concurrency}`)
     const queue = new Queue({ concurrency })
     const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
-    const routerBaseUrl = new URL(routerBasePath, 'http://localhost')
-
-    startConfig.pages = validateAndNormalizePrerenderPages(
-      startConfig.pages,
-      routerBaseUrl,
-    )

     startConfig.pages.forEach((page) => addCrawlPageTask(page))

Also applies to: 93-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/prerender.ts` around lines 44 - 50, The call
to validateAndNormalizePrerenderPages is being performed twice: once when
assigning startConfig.pages and again inside the prerenderPages function; remove
the redundant second call inside prerenderPages (the
validateAndNormalizePrerenderPages invocation within prerenderPages) and rely on
the already-normalized startConfig.pages, ensuring any code in prerenderPages
uses startConfig.pages (or its local parameter) directly without re-validating.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Bundle Size Benchmarks

  • Commit: ea78f996468e
  • Measured at: 2026-04-24T01:13:55.955Z
  • Baseline source: history:cd91ceebb84b
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.35 KiB 0 B (0.00%) 274.60 KiB 75.97 KiB ▁▁▁████████
react-router.full 90.63 KiB 0 B (0.00%) 285.74 KiB 78.87 KiB ▁▁▁▂▂██████
solid-router.minimal 35.55 KiB 0 B (0.00%) 106.71 KiB 31.96 KiB ▁▁▁▂▂▂▂▆███
solid-router.full 40.02 KiB 0 B (0.00%) 120.20 KiB 35.94 KiB ▁▁▁▂▂▂▂▇███
vue-router.minimal 53.30 KiB 0 B (0.00%) 152.01 KiB 47.88 KiB ▁▁▁████████
vue-router.full 58.20 KiB 0 B (0.00%) 167.43 KiB 52.06 KiB ▁▁▁████████
react-start.minimal 101.77 KiB 0 B (0.00%) 322.39 KiB 88.05 KiB ▁▁▁▃▃██████
react-start.full 105.21 KiB 0 B (0.00%) 332.72 KiB 90.89 KiB ▁▁▁▃▃██████
solid-start.minimal 49.53 KiB 0 B (0.00%) 152.52 KiB 43.68 KiB ▁▁▁▄▄▄▄█▇▇▇
solid-start.full 55.07 KiB 0 B (0.00%) 168.73 KiB 48.43 KiB ▁▁▁▂▂▂▂▅███

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 19, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing rsb (d59b5be) with main (cd91cee)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on main (ea78f99) during the generation of this report, so cd91cee was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/start-plugin-core/src/rsbuild/import-protection.ts`:
- Around line 1180-1191: When you detect a self-denying module (fileMatch ||
markerViolation) and are about to return
generateSelfContainedMockModule(exportNames) in the build branch, also record a
self-violation first so it isn't lost; construct a deferred file-violation entry
describing this module (using the module context ctx and the reason from
fileMatch/markerViolation) and push it to the deferredFileViolations collection
(or call the existing defer/emitter used elsewhere) before returning the mock
from the config.command === 'build' branch.
- Around line 1-3: There are duplicate imports from 'node:path' (resolve aliased
as resolvePath and extname) causing import/no-duplicates; replace the two
separate imports with a single import statement that imports both symbols
(resolve as resolvePath and extname) from 'node:path' and remove the extra
import so only one import from 'node:path' remains (update the import that
currently defines resolvePath and extname).
- Around line 432-447: The function getModuleResource calls the optional
property nameForCondition without guarding it; update the call in
getModuleResource to use optional chaining (candidate.nameForCondition?.()) so
it only invokes the function when present and otherwise falls through to
candidate.resourceResolveData?.resource, candidate.resource,
candidate.userRequest, then candidate.request; this prevents a possible runtime
error and satisfies strict null checking for the candidate.nameForCondition
property.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b164dc2-79ed-4733-aacb-770d0c3aec24

📥 Commits

Reviewing files that changed from the base of the PR and between 7e33653 and b233b75.

📒 Files selected for processing (3)
  • .changeset/add-rsbuild-start-support.md
  • packages/start-plugin-core/src/rsbuild/import-protection.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
✅ Files skipped from review due to trivial changes (2)
  • .changeset/add-rsbuild-start-support.md
  • packages/start-plugin-core/src/rsbuild/plugin.ts

Comment thread packages/start-plugin-core/src/rsbuild/import-protection.ts Outdated
Comment on lines +432 to +447
function getModuleResource(module: RspackModule): string | undefined {
const candidate = module as RspackModule & {
nameForCondition?: () => string | undefined
resourceResolveData?: { resource?: string }
resource?: string
userRequest?: string
request?: string
}

return (
candidate.nameForCondition() ??
candidate.resourceResolveData?.resource ??
candidate.resource ??
candidate.userRequest ??
candidate.request
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the file
find . -name "import-protection.ts" -type f

Repository: TanStack/router

Length of output: 120


🏁 Script executed:

# Check the tsconfig.json to understand the TypeScript configuration
find . -name "tsconfig.json" -type f | head -5

Repository: TanStack/router

Length of output: 265


🏁 Script executed:

# Get the full context of the getModuleResource function
cat -n packages/start-plugin-core/src/rsbuild/import-protection.ts | sed -n '425,455p'

Repository: TanStack/router

Length of output: 1080


🏁 Script executed:

# Check the tsconfig for the packages/start-plugin-core directory
find packages/start-plugin-core -name "tsconfig.json" -o -name "tsconfig*.json" | xargs cat -n

Repository: TanStack/router

Length of output: 533


🏁 Script executed:

# Verify the RspackModule type definition
rg "type RspackModule|interface RspackModule" -A 5 packages/start-plugin-core/

Repository: TanStack/router

Length of output: 1241


🏁 Script executed:

# Check if nameForCondition is called elsewhere in this file with proper guards
rg "nameForCondition" packages/start-plugin-core/src/rsbuild/import-protection.ts

Repository: TanStack/router

Length of output: 142


🏁 Script executed:

# Check how getModuleResource is used in the codebase
rg "getModuleResource" packages/start-plugin-core/src/rsbuild/

Repository: TanStack/router

Length of output: 558


🏁 Script executed:

# Check the root tsconfig.json for strict mode settings
cat packages/start-plugin-core/tsconfig.json

Repository: TanStack/router

Length of output: 306


🏁 Script executed:

# Also check the repository root tsconfig.json
cat tsconfig.json | head -50

Repository: TanStack/router

Length of output: 846


Guard nameForCondition before calling it.

You typed this property as optional (line 434), but line 442 invokes it unconditionally. With strict: true enabled in tsconfig.json, TypeScript's strictNullChecks should catch this—calling an optional function without optional chaining violates type safety. Use candidate.nameForCondition?.() ?? ... to safely fall through to the next property in the chain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines 432
- 447, The function getModuleResource calls the optional property
nameForCondition without guarding it; update the call in getModuleResource to
use optional chaining (candidate.nameForCondition?.()) so it only invokes the
function when present and otherwise falls through to
candidate.resourceResolveData?.resource, candidate.resource,
candidate.userRequest, then candidate.request; this prevents a possible runtime
error and satisfies strict null checking for the candidate.nameForCondition
property.

Comment on lines +1180 to +1191
if (fileMatch || markerViolation) {
let exportNames: Array<string> = []

try {
exportNames = getNamedExports(ctx.code)
} catch {
exportNames = []
}

if (config.command === 'build') {
return generateSelfContainedMockModule(exportNames)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't drop diagnostics for self-denied modules in build mode.

This branch swaps the module for a self-contained mock, but it never records a violation before returning. The later reporting code only sees mock-edge modules, graph edges, and deferred file violations, so an entry/root module that violates its own file or marker rule can slip through without any warning/error. Please defer or emit a self-violation here before returning the mock.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines
1180 - 1191, When you detect a self-denying module (fileMatch ||
markerViolation) and are about to return
generateSelfContainedMockModule(exportNames) in the build branch, also record a
self-violation first so it isn't lost; construct a deferred file-violation entry
describing this module (using the module context ctx and the reason from
fileMatch/markerViolation) and push it to the deferredFileViolations collection
(or call the existing defer/emitter used elsewhere) before returning the mock
from the config.command === 'build' branch.

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/start-plugin-core/src/rsbuild/virtual-modules.ts (1)

76-86: Add type safety for the global routes manifest access.

globalThis.TSS_ROUTES_MANIFEST is accessed without type checking. If this global isn't set when buildStartManifestData is called, routeTreeRoutes will be undefined, which may cause downstream issues in buildStartManifest.

♻️ Proposed fix to add type safety
 function buildStartManifestData(
   clientBuild: NormalizedClientBuild,
   publicBase: string,
 ) {
-  const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST
+  const routeTreeRoutes = (globalThis as typeof globalThis & { TSS_ROUTES_MANIFEST?: unknown }).TSS_ROUTES_MANIFEST
   return buildStartManifest({
     clientBuild,
     routeTreeRoutes,
     basePath: publicBase,
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/virtual-modules.ts` around lines 76 -
86, buildStartManifestData reads globalThis.TSS_ROUTES_MANIFEST without any
type/existence checks, which can yield undefined and break buildStartManifest;
update buildStartManifestData to safely access and type the global: declare an
appropriate globalThis interface/type for TSS_ROUTES_MANIFEST (or import the
manifest type), then read globalThis.TSS_ROUTES_MANIFEST into routeTreeRoutes
with a runtime guard that either throws a clear error (e.g.,
"TSS_ROUTES_MANIFEST is missing or invalid") or supplies a typed default before
calling buildStartManifest; reference the symbols buildStartManifestData,
globalThis.TSS_ROUTES_MANIFEST, routeTreeRoutes, and buildStartManifest when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/start-plugin-core/src/rsbuild/plugin.ts`:
- Around line 239-250: registerVirtualModules is capturing root eagerly (using
resolvedStartConfig.root || process.cwd()) before modifyRsbuildConfig sets the
real root, causing virtual paths to be computed against the wrong directory;
change the call to pass root lazily (a function) like
registerStartCompilerTransforms does — e.g., replace the root property with
getRoot: () => resolvedStartConfig.root || process.cwd() (and update
registerVirtualModules/virtual-modules.ts to accept and call getRoot when
computing virtualPath()), so paths are generated after
applyResolvedBaseAndOutput updates resolvedStartConfig; keep
updateServerFnResolver assignment as-is.

---

Nitpick comments:
In `@packages/start-plugin-core/src/rsbuild/virtual-modules.ts`:
- Around line 76-86: buildStartManifestData reads globalThis.TSS_ROUTES_MANIFEST
without any type/existence checks, which can yield undefined and break
buildStartManifest; update buildStartManifestData to safely access and type the
global: declare an appropriate globalThis interface/type for TSS_ROUTES_MANIFEST
(or import the manifest type), then read globalThis.TSS_ROUTES_MANIFEST into
routeTreeRoutes with a runtime guard that either throws a clear error (e.g.,
"TSS_ROUTES_MANIFEST is missing or invalid") or supplies a typed default before
calling buildStartManifest; reference the symbols buildStartManifestData,
globalThis.TSS_ROUTES_MANIFEST, routeTreeRoutes, and buildStartManifest when
making the change.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77e2a05c-c23b-4bf1-bf5c-dadd7f11b401

📥 Commits

Reviewing files that changed from the base of the PR and between b233b75 and 4cf4ea3.

📒 Files selected for processing (3)
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/start-compiler-host.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts

Comment on lines +239 to +250
const virtualModuleState = registerVirtualModules(api, {
root: resolvedStartConfig.root || process.cwd(),
getConfig,
serverFnsById,
providerEnvName: serverFnProviderEnv,
ssrIsProvider,
serializationAdapters: corePluginOpts.serializationAdapters,
getDevClientEntryUrl: (publicBase: string) =>
joinURL(publicBase, 'static/js/index.js'),
rscEnabled,
})
updateServerFnResolver = virtualModuleState.updateServerFnResolver
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how createStartConfigContext initializes resolvedStartConfig.root
# and whether it has a sensible default before modifyRsbuildConfig runs

rg -n -A10 'createStartConfigContext' packages/start-plugin-core/src/
rg -n 'resolvedStartConfig.*root' packages/start-plugin-core/src/config-context.ts

Repository: TanStack/router

Length of output: 5101


🏁 Script executed:

# Get the full createStartConfigContext function to see when root is set
cat -n packages/start-plugin-core/src/config-context.ts | head -150

Repository: TanStack/router

Length of output: 4589


🏁 Script executed:

# Get the full context around registerVirtualModules call in plugin.ts
sed -n '50,260p' packages/start-plugin-core/src/rsbuild/plugin.ts | cat -n

Repository: TanStack/router

Length of output: 10092


🏁 Script executed:

# Check the registerVirtualModules function and its timing
rg -n -B5 -A15 'export.*function registerVirtualModules' packages/start-plugin-core/src/

Repository: TanStack/router

Length of output: 2229


🏁 Script executed:

# Check if virtual module paths can be updated after initial registration
rg -n -A20 'const paths = {' packages/start-plugin-core/src/rsbuild/virtual-modules.ts

Repository: TanStack/router

Length of output: 152


🏁 Script executed:

# Check how paths are used in virtual module registration
rg -n 'paths\.' packages/start-plugin-core/src/rsbuild/virtual-modules.ts | head -20

Repository: TanStack/router

Length of output: 1228


🏁 Script executed:

# Verify if modifyBundlerChain or other dynamic updates could recompute paths
rg -n 'modifyBundlerChain\|modifyRspackConfig' packages/start-plugin-core/src/rsbuild/virtual-modules.ts

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Search for modifyRspackConfig usage
rg -n 'modifyRspackConfig' packages/start-plugin-core/src/rsbuild/virtual-modules.ts

Repository: TanStack/router

Length of output: 430


🏁 Script executed:

# Get the full registerVirtualModules function to see how paths are used
sed -n '224,450p' packages/start-plugin-core/src/rsbuild/virtual-modules.ts | cat -n

Repository: TanStack/router

Length of output: 10250


🏁 Script executed:

# Check what virtualPath does and if it depends on root in a way that matters
rg -n -B5 -A10 'function virtualPath\|const virtualPath' packages/start-plugin-core/src/rsbuild/

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check if there's any mechanism to update paths after root is set
rg -n 'paths =' packages/start-plugin-core/src/rsbuild/virtual-modules.ts

Repository: TanStack/router

Length of output: 80


🏁 Script executed:

# Verify the exact timing: when does modifyRsbuildConfig run vs setup
sed -n '1,300p' packages/start-plugin-core/src/rsbuild/plugin.ts | grep -n 'modifyRsbuildConfig\|registerVirtualModules\|setup'

Repository: TanStack/router

Length of output: 615


🏁 Script executed:

# Find virtualPath implementation
rg -n -B2 -A10 'export.*virtualPath|function virtualPath' packages/start-plugin-core/src/

Repository: TanStack/router

Length of output: 1504


🏁 Script executed:

# Check VIRTUAL_MODULES constants to understand what virtualPath generates
rg -n 'VIRTUAL_MODULES|RSC_RUNTIME_VIRTUAL_ID|RSC_HMR_VIRTUAL_ID' packages/start-plugin-core/src/rsbuild/ | head -20

Repository: TanStack/router

Length of output: 2446


Root path captured before modifyRsbuildConfig sets it.

Unlike registerStartCompilerTransforms (line 163) which defers root access via a function, registerVirtualModules receives the root value directly at setup time. The virtualPath() calls (lines 233–237 in virtual-modules.ts) compute paths immediately as ${root}/node_modules/.virtual/... when registerVirtualModules is called during setup(), before the modifyRsbuildConfig callback runs.

When registerVirtualModules is called, resolvedStartConfig.root is still an empty string (initialized in config-context.ts:30), so the fallback process.cwd() is used. The paths object is created once and never recomputed, even after modifyRsbuildConfig later sets resolvedStartConfig.root to the actual rsbuildConfig.root (via applyResolvedBaseAndOutput at line 52–68). This causes all virtual module paths to be registered under the wrong root, which will break builds if rsbuildConfig.root differs from the current working directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/plugin.ts` around lines 239 - 250,
registerVirtualModules is capturing root eagerly (using resolvedStartConfig.root
|| process.cwd()) before modifyRsbuildConfig sets the real root, causing virtual
paths to be computed against the wrong directory; change the call to pass root
lazily (a function) like registerStartCompilerTransforms does — e.g., replace
the root property with getRoot: () => resolvedStartConfig.root || process.cwd()
(and update registerVirtualModules/virtual-modules.ts to accept and call getRoot
when computing virtualPath()), so paths are generated after
applyResolvedBaseAndOutput updates resolvedStartConfig; keep
updateServerFnResolver assignment as-is.

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

♻️ Duplicate comments (5)
packages/start-plugin-core/src/rsbuild/dev-server.ts (1)

93-109: ⚠️ Potential issue | 🟡 Minor

Escape the error message before embedding it in the HTML response.

The dev error page still injects e.message/String(e) directly into <pre>, so an error containing HTML characters can break the page or render unintended markup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/dev-server.ts` around lines 93 - 109,
The error HTML embeds the raw error text (e.message / String(e)) directly into
the Response body in sendNodeResponse, which can allow HTML injection; add an
HTML-escaping step before embedding the message (e.g., implement a small
escapeHtml utility that replaces &, <, >, ", ' and / with entities) and call it
when constructing the Response body in dev-server.ts (the block that creates new
Response for 500 errors) so the <pre> contains the escaped error string instead
of the raw message.
packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts (1)

2-11: ⚠️ Potential issue | 🟡 Minor

Sort the named imports to satisfy ESLint.

ImportLocCache is still out of order here, so sort-imports will keep failing on this test file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts`
around lines 2 - 11, The named imports in the import statement (including
ImportLocCache) are not alphabetically sorted, which triggers the sort-imports
ESLint rule; reorder the imported identifiers (e.g.,
createImportSpecifierLocationIndex, getOrCreateOriginalTransformResult,
ImportLocCache, etc.) into ascending alphabetical order so ImportLocCache is
placed where it belongs and ensure the whole list in the import from
'../../src/import-protection/sourceLocation' is consistently sorted.
packages/start-plugin-core/src/rsbuild/import-protection.ts (2)

1191-1202: ⚠️ Potential issue | 🟠 Major

Record build-mode self-denials before returning the mock.

This branch swaps a self-denying module for a self-contained mock, but it never defers or emits the corresponding violation first. An entry/root module that violates its own file or marker rule can therefore pass the build with no diagnostic, because the report stage only sees mock-edge payloads, graph edges, and deferredFileViolations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines
1191 - 1202, The build branch returns a self-contained mock
(generateSelfContainedMockModule) when fileMatch or markerViolation is true but
fails to record the corresponding self-denial, so the violation is never seen
later; before returning the mock in the if (config.command === 'build') branch,
add logic to record/defer the violation (e.g., push a suitable entry into
deferredFileViolations or call the existing defer/report helper used elsewhere)
using the same identifying data you use for fileMatch/markerViolation and
include exportNames from getNamedExports so the deferred record matches the
real-module signature.

435-450: ⚠️ Potential issue | 🟠 Major

Guard nameForCondition before invoking it.

candidate.nameForCondition is typed optional here, but Line 445 calls it unconditionally. Under strict null checks that is both a type-safety hole and a possible runtime throw when Rspack returns a module without that method.

Suggested fix
   return (
-    candidate.nameForCondition() ??
+    candidate.nameForCondition?.() ??
     candidate.resourceResolveData?.resource ??
     candidate.resource ??
     candidate.userRequest ??
     candidate.request
   )
#!/bin/bash
set -e
echo "Verifying the optional call site:"
sed -n '435,450p' packages/start-plugin-core/src/rsbuild/import-protection.ts

echo
echo "Verifying strict TypeScript settings for this package:"
sed -n '1,120p' packages/start-plugin-core/tsconfig.json

As per coding guidelines, **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts` around lines 435
- 450, getModuleResource currently calls candidate.nameForCondition() unguarded
which can throw when that method is absent; change the call to be guarded (e.g.
use optional chaining or a typeof/function check) so you invoke nameForCondition
only when it exists (refer to getModuleResource and the
candidate.nameForCondition property) and return its result in the existing
fallback chain (preserve the other fallbacks: resourceResolveData?.resource,
resource, userRequest, request).
packages/start-plugin-core/src/rsbuild/plugin.ts (1)

255-257: ⚠️ Potential issue | 🔴 Critical

Defer the root lookup for virtual modules.

This still snapshots resolvedStartConfig.root during setup(). For apps with a custom rsbuildConfig.root, the virtual file paths are computed under process.cwd() before modifyRsbuildConfig() populates the real root, and they never get recomputed. registerVirtualModules needs the same lazy-root pattern you already use for registerStartCompilerTransforms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/plugin.ts` around lines 255 - 257, The
virtual module registration currently captures resolvedStartConfig.root eagerly,
causing wrong paths; change the registerVirtualModules call to accept a
lazy/root getter (the same pattern used by registerStartCompilerTransforms)
instead of passing resolvedStartConfig.root directly so the real root set in
modifyRsbuildConfig() is used when computing virtual paths; update the call site
in plugin.ts where registerVirtualModules(api, { root: resolvedStartConfig.root
|| process.cwd(), getConfig }) is invoked and ensure registerVirtualModules
consumes a function (e.g., () => resolvedStartConfig.root || process.cwd()) so
paths are computed lazily.
🧹 Nitpick comments (7)
packages/start-plugin-core/src/import-protection/INTERNALS.md (1)

257-262: Optional wording cleanup in maintainer guidance

Line 257 and Line 260 both use “it probably belongs…”. Consider varying one phrase to improve readability in this short numbered section.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/import-protection/INTERNALS.md` around lines
257 - 262, The two list items in the numbered guidance both repeat the phrase
"it probably belongs...", which is redundant; update one of the items (either
the first item referencing parsing/source extraction/export discovery/usage
lookup/source locations/rewrite mechanics/mock codegen or the second item
referencing hook timing/deferral/compilation truth/pending
queues/environment-specific virtual-module transport) to use a different wording
such as "it likely belongs in..." or "it should belong in..." to improve
readability while preserving meaning.
e2e/vue-start/basic/rsbuild.config.ts (1)

8-8: Harden E2E_DIST_DIR fallback for empty-string env values.

Line 8 currently handles undefined/null, but not ''. Optional improvement to avoid writing to an unintended root-relative path when env is set but empty.

Optional hardening diff
-const outDir = process.env.E2E_DIST_DIR ?? 'dist'
+const outDir = process.env.E2E_DIST_DIR?.trim() || 'dist'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/vue-start/basic/rsbuild.config.ts` at line 8, The current outDir
assignment uses nullish coalescing and treats an empty E2E_DIST_DIR as valid;
change the logic that sets outDir so an empty string (or whitespace-only) falls
back to 'dist' — e.g., check process.env.E2E_DIST_DIR (or its trimmed value) for
truthiness before using it; update the const outDir declaration to use that
check so outDir only uses E2E_DIST_DIR when it is a non-empty value.
e2e/react-start/import-protection/tests/error-mode.setup.ts (1)

128-134: Remove redundant toolchain redeclaration.

The toolchain variable is already declared at module scope (line 22) with the same logic. This local redeclaration shadows the module-level variable unnecessarily.

Proposed fix
   const out = fs.createWriteStream(logFile)
-  const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'
   const command =
     toolchain === 'rsbuild'
       ? ['exec', 'rsbuild', 'dev', '--port', String(port)]
       : ['exec', 'vite', 'dev', '--port', String(port)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/tests/error-mode.setup.ts` around lines 128
- 134, Remove the redundant local redeclaration of the toolchain variable in the
spawn block: use the module-level toolchain (declared earlier) instead of
re-declaring const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite'; update the
command computation (the const command = ... ternary) to reference the existing
toolchain variable and keep the spawn('pnpm', command, { ... }) invocation
unchanged so there is no shadowing of the module-level toolchain.
e2e/react-start/import-protection/tests/import-protection.spec.ts (1)

732-756: Good toolchain-conditional test structure.

The tests correctly document and verify the behavioral difference between Vite and Rsbuild for barrel re-export patterns in dev mode. The early return pattern keeps the test logic clear.

Consider adding a brief inline comment explaining why Rsbuild behaves differently (e.g., different module resolution/tree-shaking timing) to help future maintainers understand the expected divergence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection/tests/import-protection.spec.ts` around
lines 732 - 756, Add a concise inline comment inside the for-loop tests (the
block iterating modes and the two tests named "barrel-reexport .server dev
behavior matches..." and "barrel-reexport marker dev behavior matches...")
explaining why the Rsbuild toolchain is expected to produce no hits (e.g.,
Rsbuild performs different module resolution/tree-shaking timing than Vite),
placed near the conditional that checks toolchain and uses
readViolations/findBarrelReexportHits and findBarrelMarkerHits so future
maintainers understand the expected divergence.
packages/start-plugin-core/src/prerender.ts (1)

114-116: Mutating startConfig.pages while iterating may cause missed pages.

addCrawlPageTask is called via startConfig.pages.forEach() at line 98, and inside the function, new pages are pushed to startConfig.pages when page.fromCrawl is true. While the initial forEach iteration won't see these new items, this mutation pattern is confusing and could lead to subtle bugs if the iteration logic changes.

Consider collecting crawled pages in a separate array and merging them after the prerender completes, or documenting that this mutation is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/prerender.ts` around lines 114 - 116, The
current addCrawlPageTask mutates startConfig.pages during the
startConfig.pages.forEach iteration by pushing pages when page.fromCrawl is
true, which can hide newly added pages from the ongoing loop; to fix, stop
mutating startConfig.pages inside addCrawlPageTask—collect crawled pages into a
local array (e.g., crawledPages) or push to a temporary buffer inside
addCrawlPageTask, and after the forEach completes merge that buffer into
startConfig.pages (or iterate over a snapshot like startConfig.pages.slice());
update references in addCrawlPageTask and the caller to use the temporary
collection and ensure page.fromCrawl logic still sets the correct flag before
merging.
packages/start-plugin-core/src/rsbuild/start-router-plugin.ts (1)

35-61: Consider more specific error handling for dynamic imports.

The empty catch block swallows all errors, not just "module not found". If the import succeeds but plugin initialization fails (e.g., configuration error), the error would be silently ignored, making debugging difficult.

♻️ Suggested improvement
       try {
         const { TanStackRouterGeneratorRspack } =
           await import('@tanstack/router-plugin/rspack')
         // ... plugin registration
         utils.appendPlugins(generatorPlugin as any)
-      } catch {
-        // router-plugin/rspack not available — skip
+      } catch (e: unknown) {
+        // Skip if module not available, re-throw other errors
+        if (
+          e instanceof Error &&
+          'code' in e &&
+          (e as NodeJS.ErrnoException).code === 'ERR_MODULE_NOT_FOUND'
+        ) {
+          return
+        }
+        throw e
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/rsbuild/start-router-plugin.ts` around lines
35 - 61, The catch block around the dynamic import of
'@tanstack/router-plugin/rspack' currently swallows all errors; change it to
only ignore "module not found" errors and surface other failures by rethrowing
or logging them: wrap the import in the existing try but in the catch inspect
the error (e.g., error.code === 'ERR_MODULE_NOT_FOUND' or error.message includes
'Cannot find module' / 'ERR_MODULE_NOT_FOUND') and only skip when that condition
is true; otherwise call opts.corePluginOpts.logger.error(...) or rethrow the
error so failures during TanStackRouterGeneratorRspack initialization (and
subsequent calls to TanStackRouterGeneratorRspack, routesManifestPlugin,
prerenderRoutesPlugin, buildRouteTreeFileFooterFromConfig, utils.appendPlugins)
are not silently ignored.
packages/router-plugin/src/core/hmr/webpack-adapter.ts (1)

109-111: The syntacticPlaceholders option has no effect here.

The template uses JavaScript template literal interpolation (${handleRouteUpdateCode}, ${staticRouteIdLiteral}, etc.) which resolves before the string is passed to template.statement(). The syntacticPlaceholders: true option enables %%placeholder%% syntax, which isn't used. Consider removing it for clarity.

♻️ Suggested change
-      {
-        syntacticPlaceholders: true,
-      },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-plugin/src/core/hmr/webpack-adapter.ts` around lines 109 -
111, Remove the unused syntacticPlaceholders option because the code passes
JS-interpolated template literals (e.g. `${handleRouteUpdateCode}`,
`${staticRouteIdLiteral}`) to template.statement() rather than %%placeholder%%
syntax; locate the options object where syntacticPlaceholders: true is set (in
the webpack-adapter file near the template.statement usage) and delete that
property to avoid confusion and clarify intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/react-start/basic/server.js`:
- Around line 16-28: resolveDistServerEntryPath currently returns serverJsPath
even when neither server/server.js nor server/index.js exist; change it to fail
fast by throwing an error when both fs.existsSync checks are false (include the
attempted paths serverJsPath and indexJsPath in the thrown message for
debugging). Update the function (symbol: resolveDistServerEntryPath) to throw
with a clear message referencing distDir, serverJsPath and indexJsPath instead
of returning serverJsPath, and mirror the same guard/throw behavior as used in
e2e/react-start/rsc/server.js so both entry-resolution functions behave
consistently.

In `@e2e/react-start/hmr/src/hmr/server-fn-hmr-factory.ts`:
- Line 1: The import line currently includes an unused symbol createClientOnlyFn
which triggers unused-imports/no-unused-imports; either remove
createClientOnlyFn from the import statement (leaving import {
createServerOnlyFn } from '@tanstack/react-start') or, if the unused import is
intentional for test-time file mutation, add a targeted ESLint disable comment
directly above the import (e.g. /* eslint-disable-next-line
unused-imports/no-unused-imports -- kept for test-time file mutation */) and
include the brief rationale; reference the import line that contains
createClientOnlyFn and createServerOnlyFn to locate the change.

In `@packages/router-plugin/src/core/hmr/handle-route-update.ts`:
- Around line 134-139: The current branch that handles
removedKeys.has('beforeLoad') leaves stale keys from the old beforeLoad in
next.context because it spreads next.context first; instead rebuild next.context
by starting from route-owned data and then copying only those keys from the
previous context that are not from the cleared beforeLoad. Concretely: when
removedKeys.has('beforeLoad'), capture the set of keys in
next.__beforeLoadContext, create a fresh base from next.__routeContext (or {}),
then iterate next.context (if present) and copy into the new context only keys
not present in that beforeLoad key-set, assign that new object to next.context,
and finally clear next.__beforeLoadContext.

In `@packages/router-plugin/src/core/hmr/select-adapter.ts`:
- Around line 21-27: The Vite branch drops opts.routeId causing brittle HMR;
change the call site in select-adapter (the branch where opts.hmrStyle !==
'webpack') to pass opts.routeId into createViteHmrStatement (along with
stableRouteOptionKeys), and then update createViteHmrStatement to accept a
routeId parameter and use that routeId preferentially instead of reading
Route.id at runtime (falling back to Route.id only if routeId is not provided).
Ensure the symbol names involved are createViteHmrStatement,
createWebpackHmrStatement, stableRouteOptionKeys, and opts.routeId.

In `@packages/router-plugin/src/rspack.ts`:
- Around line 15-28: withWebpackHmrStyle currently assumes options is a plain
object and spreads it, which drops callback-style configs like () => Config;
change withWebpackHmrStyle to detect if options is a function and, if so, return
a new function that calls the original to obtain the config and then merges in
the hmr.style = 'webpack' block (otherwise merge into the plain object as
before), ensuring you preserve lazy/callback options used by
TanStackRouterCodeSplitterRspack and similar callers and keep typing to accept
Partial<Config> | (() => Partial<Config>).

In `@packages/start-plugin-core/src/prerender.ts`:
- Around line 214-217: The retry delay can be undefined causing setTimeout to
receive NaN; update the retry logic around the logger.warn and the await new
Promise(...) that calls setTimeout to coerce and validate
prerenderOptions.retryDelay (e.g., use a fallback constant like
DEFAULT_RETRY_DELAY or Number(prerenderOptions.retryDelay) ||
DEFAULT_RETRY_DELAY and ensure it's a non-negative integer) before passing it to
setTimeout, and use the validated value wherever retryDelay is referenced in the
retry path.

In `@packages/start-plugin-core/src/rsbuild/start-compiler-host.ts`:
- Around line 71-77: The environments array only contains
RSBUILD_ENVIRONMENT_NAMES.client and RSBUILD_ENVIRONMENT_NAMES.server, so when a
third "provider" environment (providerEnvName) is created it doesn't get
StartCompiler transforms; update the code that builds environments (the
environments array near start-compiler.ts) to include the provider environment
as a server-like entry (push or include { name: providerEnvName, type: 'server'
} when providerEnvName is present and different from the SSR/server constant),
so that the loop which uses createStartCompiler and registers Start transforms
runs for that provider environment as well.

In `@packages/start-plugin-core/src/rsbuild/start-router-plugin.ts`:
- Line 57: The cast to any on generatorPlugin and codegenPlugin when calling
utils.appendPlugins(generatorPlugin as any) (and the similar call for
codegenPlugin) suppresses TypeScript type incompatibility caused by dynamic
import of `@tanstack/router-plugin/rspack`; add an inline comment directly above
each cast explaining that the dynamic import loses precise type information and
that the cast is intentional to bridge rspack plugin types to Rsbuild's
appendPlugins, and optionally note a follow-up to request/export
Rsbuild-compatible wrappers from `@tanstack/router-plugin` to remove the cast in
future.

In `@packages/start-plugin-core/src/rsbuild/swc-rsc.ts`:
- Around line 142-146: The route-split clone currently overwrites
originalRule.resolve with only conditionNames, dropping any aliases or per-rule
resolution settings; instead merge the existing originalRule.resolve into
routeSplitRule.resolve and only override/ensure conditionNames includes '...'
(e.g. compute routeSplitRule.resolve by copying originalRule.resolve and
setting/merging conditionNames), preserving other resolve fields from
originalRule; locate the clone creation using cloneRspackRule, routeSplitRule
and originalRule to implement the merge.

In `@packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts`:
- Around line 304-316: The code is invalidating direct importer modules (calling
compiler.invalidateModule(importer.id)) while still building the transitive
importer seed, which breaks getTransitiveImporters traversal; instead, only
collect importer IDs and modules into idsToInvalidate and
importerModulesToInvalidate (and add cleanId(importer.id) to
transitiveCompilerImportersToInvalidate) inside the importer loop without
calling compiler.invalidateModule there, then after you finish seeding and run
getTransitiveImporters(...) perform the actual compiler.invalidateModule calls
for every collected importer id (and any transitive ids) using
compiler.invalidateModule; apply the same deferred-invalidation change to the
other identical block (lines ~322-347) so invalidation happens only after
transitive discovery.
- Around line 248-253: this.load() can return null, a string, or an object with
a code property, so normalize its result before calling compiler!.ingestModule
to avoid TypeError or ingesting an empty module: call this.load({ id }), check
for null and bail/return early if no module was found, if the result is a string
use it as the code, if it’s an object extract the code property (fallback to
empty string only if intentionally desired), then pass that normalized code and
id into compiler.ingestModule; update the block around the this.load call in
plugin.ts to perform this normalization and early return on null instead of
directly using loaded.code.

In `@packages/start-plugin-core/tests/importProtection/utils.test.ts`:
- Around line 4-21: The named import list from utils has members out of sort
order for the linter; move dedupePatterns so it appears before
dedupeViolationKey in the import statement that imports
buildResolutionCandidates, buildSourceCandidates, ..., withoutKnownExtension
from '../../src/import-protection/utils' (i.e., reorder the identifiers so
dedupePatterns precedes dedupeViolationKey).

In `@packages/start-plugin-core/tests/vite-start-compiler-plugin.test.ts`:
- Around line 3-20: Move the vi.mock call so all import statements come first
(fixing the import/first violation) and remove/merge the duplicate imports from
'vite' into a single import; specifically, place the
vi.mock('@tanstack/start-server-core', ...) after the import block that includes
SERVER_FN_LOOKUP and startCompilerPlugin from
'../src/vite/start-compiler-plugin/plugin' and the module-specifier and
hot-update imports, and combine the two separate type imports for Plugin and
EnvironmentModuleNode into one import statement from 'vite' to eliminate the
duplicate source.

---

Duplicate comments:
In `@packages/start-plugin-core/src/rsbuild/dev-server.ts`:
- Around line 93-109: The error HTML embeds the raw error text (e.message /
String(e)) directly into the Response body in sendNodeResponse, which can allow
HTML injection; add an HTML-escaping step before embedding the message (e.g.,
implement a small escapeHtml utility that replaces &, <, >, ", ' and / with
entities) and call it when constructing the Response body in dev-server.ts (the
block that creates new Response for 500 errors) so the <pre> contains the
escaped error string instead of the raw message.

In `@packages/start-plugin-core/src/rsbuild/import-protection.ts`:
- Around line 1191-1202: The build branch returns a self-contained mock
(generateSelfContainedMockModule) when fileMatch or markerViolation is true but
fails to record the corresponding self-denial, so the violation is never seen
later; before returning the mock in the if (config.command === 'build') branch,
add logic to record/defer the violation (e.g., push a suitable entry into
deferredFileViolations or call the existing defer/report helper used elsewhere)
using the same identifying data you use for fileMatch/markerViolation and
include exportNames from getNamedExports so the deferred record matches the
real-module signature.
- Around line 435-450: getModuleResource currently calls
candidate.nameForCondition() unguarded which can throw when that method is
absent; change the call to be guarded (e.g. use optional chaining or a
typeof/function check) so you invoke nameForCondition only when it exists (refer
to getModuleResource and the candidate.nameForCondition property) and return its
result in the existing fallback chain (preserve the other fallbacks:
resourceResolveData?.resource, resource, userRequest, request).

In `@packages/start-plugin-core/src/rsbuild/plugin.ts`:
- Around line 255-257: The virtual module registration currently captures
resolvedStartConfig.root eagerly, causing wrong paths; change the
registerVirtualModules call to accept a lazy/root getter (the same pattern used
by registerStartCompilerTransforms) instead of passing resolvedStartConfig.root
directly so the real root set in modifyRsbuildConfig() is used when computing
virtual paths; update the call site in plugin.ts where
registerVirtualModules(api, { root: resolvedStartConfig.root || process.cwd(),
getConfig }) is invoked and ensure registerVirtualModules consumes a function
(e.g., () => resolvedStartConfig.root || process.cwd()) so paths are computed
lazily.

In `@packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts`:
- Around line 2-11: The named imports in the import statement (including
ImportLocCache) are not alphabetically sorted, which triggers the sort-imports
ESLint rule; reorder the imported identifiers (e.g.,
createImportSpecifierLocationIndex, getOrCreateOriginalTransformResult,
ImportLocCache, etc.) into ascending alphabetical order so ImportLocCache is
placed where it belongs and ensure the whole list in the import from
'../../src/import-protection/sourceLocation' is consistently sorted.

---

Nitpick comments:
In `@e2e/react-start/import-protection/tests/error-mode.setup.ts`:
- Around line 128-134: Remove the redundant local redeclaration of the toolchain
variable in the spawn block: use the module-level toolchain (declared earlier)
instead of re-declaring const toolchain = process.env.E2E_TOOLCHAIN ?? 'vite';
update the command computation (the const command = ... ternary) to reference
the existing toolchain variable and keep the spawn('pnpm', command, { ... })
invocation unchanged so there is no shadowing of the module-level toolchain.

In `@e2e/react-start/import-protection/tests/import-protection.spec.ts`:
- Around line 732-756: Add a concise inline comment inside the for-loop tests
(the block iterating modes and the two tests named "barrel-reexport .server dev
behavior matches..." and "barrel-reexport marker dev behavior matches...")
explaining why the Rsbuild toolchain is expected to produce no hits (e.g.,
Rsbuild performs different module resolution/tree-shaking timing than Vite),
placed near the conditional that checks toolchain and uses
readViolations/findBarrelReexportHits and findBarrelMarkerHits so future
maintainers understand the expected divergence.

In `@e2e/vue-start/basic/rsbuild.config.ts`:
- Line 8: The current outDir assignment uses nullish coalescing and treats an
empty E2E_DIST_DIR as valid; change the logic that sets outDir so an empty
string (or whitespace-only) falls back to 'dist' — e.g., check
process.env.E2E_DIST_DIR (or its trimmed value) for truthiness before using it;
update the const outDir declaration to use that check so outDir only uses
E2E_DIST_DIR when it is a non-empty value.

In `@packages/router-plugin/src/core/hmr/webpack-adapter.ts`:
- Around line 109-111: Remove the unused syntacticPlaceholders option because
the code passes JS-interpolated template literals (e.g.
`${handleRouteUpdateCode}`, `${staticRouteIdLiteral}`) to template.statement()
rather than %%placeholder%% syntax; locate the options object where
syntacticPlaceholders: true is set (in the webpack-adapter file near the
template.statement usage) and delete that property to avoid confusion and
clarify intent.

In `@packages/start-plugin-core/src/import-protection/INTERNALS.md`:
- Around line 257-262: The two list items in the numbered guidance both repeat
the phrase "it probably belongs...", which is redundant; update one of the items
(either the first item referencing parsing/source extraction/export
discovery/usage lookup/source locations/rewrite mechanics/mock codegen or the
second item referencing hook timing/deferral/compilation truth/pending
queues/environment-specific virtual-module transport) to use a different wording
such as "it likely belongs in..." or "it should belong in..." to improve
readability while preserving meaning.

In `@packages/start-plugin-core/src/prerender.ts`:
- Around line 114-116: The current addCrawlPageTask mutates startConfig.pages
during the startConfig.pages.forEach iteration by pushing pages when
page.fromCrawl is true, which can hide newly added pages from the ongoing loop;
to fix, stop mutating startConfig.pages inside addCrawlPageTask—collect crawled
pages into a local array (e.g., crawledPages) or push to a temporary buffer
inside addCrawlPageTask, and after the forEach completes merge that buffer into
startConfig.pages (or iterate over a snapshot like startConfig.pages.slice());
update references in addCrawlPageTask and the caller to use the temporary
collection and ensure page.fromCrawl logic still sets the correct flag before
merging.

In `@packages/start-plugin-core/src/rsbuild/start-router-plugin.ts`:
- Around line 35-61: The catch block around the dynamic import of
'@tanstack/router-plugin/rspack' currently swallows all errors; change it to
only ignore "module not found" errors and surface other failures by rethrowing
or logging them: wrap the import in the existing try but in the catch inspect
the error (e.g., error.code === 'ERR_MODULE_NOT_FOUND' or error.message includes
'Cannot find module' / 'ERR_MODULE_NOT_FOUND') and only skip when that condition
is true; otherwise call opts.corePluginOpts.logger.error(...) or rethrow the
error so failures during TanStackRouterGeneratorRspack initialization (and
subsequent calls to TanStackRouterGeneratorRspack, routesManifestPlugin,
prerenderRoutesPlugin, buildRouteTreeFileFooterFromConfig, utils.appendPlugins)
are not silently ignored.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48f7c47e-4109-4219-87bc-4a97fbea8efc

📥 Commits

Reviewing files that changed from the base of the PR and between 4cf4ea3 and f81e803.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (184)
  • .changeset/add-rsbuild-start-support.md
  • docs/start/framework/react/guide/import-protection.md
  • e2e/.gitignore
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • e2e/react-start/basic/.gitignore
  • e2e/react-start/basic/package.json
  • e2e/react-start/basic/postcss.config.mjs
  • e2e/react-start/basic/rsbuild.config.ts
  • e2e/react-start/basic/server.js
  • e2e/react-start/basic/src/routes/__root.tsx
  • e2e/react-start/basic/start-mode-config.ts
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/hmr/.gitignore
  • e2e/react-start/hmr/package.json
  • e2e/react-start/hmr/playwright.config.ts
  • e2e/react-start/hmr/postcss.config.mjs
  • e2e/react-start/hmr/rsbuild.config.ts
  • e2e/react-start/hmr/src/hmr/server-fn-hmr-entry.ts
  • e2e/react-start/hmr/src/hmr/server-fn-hmr-factory.ts
  • e2e/react-start/hmr/src/routeTree.gen.ts
  • e2e/react-start/hmr/src/routes/__root.tsx
  • e2e/react-start/hmr/src/routes/server-fn-hmr.tsx
  • e2e/react-start/hmr/tests/app.spec.ts
  • e2e/react-start/hmr/tests/setup/global.setup.ts
  • e2e/react-start/hmr/tests/setup/global.teardown.ts
  • e2e/react-start/hmr/vite.config.ts
  • e2e/react-start/import-protection/package.json
  • e2e/react-start/import-protection/playwright.config.ts
  • e2e/react-start/import-protection/rsbuild.config.ts
  • e2e/react-start/import-protection/server.js
  • e2e/react-start/import-protection/start-mode-config.ts
  • e2e/react-start/import-protection/tests/error-mode.setup.ts
  • e2e/react-start/import-protection/tests/import-protection.spec.ts
  • e2e/react-start/import-protection/tests/violations.setup.ts
  • e2e/react-start/import-protection/tests/violations.utils.ts
  • e2e/react-start/import-protection/vite.config.ts
  • e2e/react-start/rsc/package.json
  • e2e/react-start/rsc/playwright.config.ts
  • e2e/react-start/rsc/rsbuild.config.ts
  • e2e/react-start/rsc/server.js
  • e2e/react-start/rsc/src/utils/conditionalCssServerComponent.tsx
  • e2e/react-start/rsc/src/utils/cssModulesServerComponent.tsx
  • e2e/react-start/rsc/tests/rsc-client-preload.spec.ts
  • e2e/react-start/rsc/tests/rsc-css-preload-complex.spec.ts
  • e2e/react-start/serialization-adapters/src/routes/server-function/late-raw-stream.tsx
  • e2e/react-start/server-functions/package.json
  • e2e/react-start/server-functions/playwright.config.ts
  • e2e/react-start/server-functions/postcss.config.mjs
  • e2e/react-start/server-functions/rsbuild.config.ts
  • e2e/react-start/server-functions/server.js
  • e2e/react-start/server-functions/src/routes/__root.tsx
  • e2e/react-start/server-functions/start-mode-config.ts
  • e2e/react-start/server-functions/tests/server-functions.spec.ts
  • e2e/react-start/server-functions/vite.config.ts
  • e2e/solid-start/basic/package.json
  • e2e/solid-start/basic/postcss.config.mjs
  • e2e/solid-start/basic/rsbuild.config.ts
  • e2e/solid-start/basic/server.js
  • e2e/solid-start/basic/src/routes/__root.tsx
  • e2e/vue-start/basic-vue-query/src/utils/posts.tsx
  • e2e/vue-start/basic/package.json
  • e2e/vue-start/basic/postcss.config.mjs
  • e2e/vue-start/basic/rsbuild.config.ts
  • e2e/vue-start/basic/server.js
  • e2e/vue-start/basic/src/routes/__root.tsx
  • packages/react-router-devtools/src/TanStackRouterDevtools.tsx
  • packages/react-router-devtools/src/TanStackRouterDevtoolsPanel.tsx
  • packages/react-router-devtools/src/index.ts
  • packages/react-router/src/Asset.tsx
  • packages/react-router/src/ClientOnly.tsx
  • packages/react-router/src/useLocation.tsx
  • packages/react-start-rsc/package.json
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/react-start-rsc/src/createCompositeComponent.ts
  • packages/react-start-rsc/src/global.d.ts
  • packages/react-start-rsc/src/react-server-dom-rspack.d.ts
  • packages/react-start-rsc/src/renderServerComponent.ts
  • packages/react-start-rsc/src/rsbuild/browser-decode.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • packages/react-start-rsc/vite.config.ts
  • packages/react-start/package.json
  • packages/react-start/src/plugin/rsbuild.ts
  • packages/react-start/src/rsbuild/browser-decode.ts
  • packages/react-start/src/rsbuild/ssr-decode.ts
  • packages/react-start/vite.config.ts
  • packages/router-generator/src/generator.ts
  • packages/router-generator/src/types.ts
  • packages/router-plugin/package.json
  • packages/router-plugin/src/core/code-splitter/compilers.ts
  • packages/router-plugin/src/core/code-splitter/plugins.ts
  • packages/router-plugin/src/core/code-splitter/plugins/framework-plugins.ts
  • packages/router-plugin/src/core/code-splitter/plugins/react-refresh-ignored-route-exports.ts
  • packages/router-plugin/src/core/code-splitter/plugins/react-stable-hmr-split-route-components.ts
  • packages/router-plugin/src/core/config.ts
  • packages/router-plugin/src/core/hmr-hot-expression.ts
  • packages/router-plugin/src/core/hmr/handle-route-update.ts
  • packages/router-plugin/src/core/hmr/index.ts
  • packages/router-plugin/src/core/hmr/select-adapter.ts
  • packages/router-plugin/src/core/hmr/vite-adapter.ts
  • packages/router-plugin/src/core/hmr/webpack-adapter.ts
  • packages/router-plugin/src/core/router-code-splitter-plugin.ts
  • packages/router-plugin/src/core/router-hmr-plugin.ts
  • packages/router-plugin/src/rspack.ts
  • packages/router-plugin/src/webpack.ts
  • packages/router-plugin/tests/add-hmr.test.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@webpack-hot.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx
  • packages/router-utils/src/ast.ts
  • packages/router-utils/tests/stripTypeExports.test.ts
  • packages/solid-start/package.json
  • packages/solid-start/src/plugin/rsbuild.ts
  • packages/solid-start/vite.config.ts
  • packages/start-plugin-core/package.json
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts
  • packages/start-plugin-core/src/import-protection/INTERNALS.md
  • packages/start-plugin-core/src/import-protection/adapterUtils.ts
  • packages/start-plugin-core/src/import-protection/analysis.ts
  • packages/start-plugin-core/src/import-protection/ast.ts
  • packages/start-plugin-core/src/import-protection/constants.ts
  • packages/start-plugin-core/src/import-protection/defaults.ts
  • packages/start-plugin-core/src/import-protection/extensionlessAbsoluteIdResolver.ts
  • packages/start-plugin-core/src/import-protection/matchers.ts
  • packages/start-plugin-core/src/import-protection/rewrite.ts
  • packages/start-plugin-core/src/import-protection/sourceLocation.ts
  • packages/start-plugin-core/src/import-protection/trace.ts
  • packages/start-plugin-core/src/import-protection/utils.ts
  • packages/start-plugin-core/src/import-protection/virtualModules.ts
  • packages/start-plugin-core/src/index.ts
  • packages/start-plugin-core/src/post-build.ts
  • packages/start-plugin-core/src/prerender.ts
  • packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
  • packages/start-plugin-core/src/rsbuild/dev-server.ts
  • packages/start-plugin-core/src/rsbuild/import-protection.ts
  • packages/start-plugin-core/src/rsbuild/normalized-client-build.ts
  • packages/start-plugin-core/src/rsbuild/planning.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/post-build.ts
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • packages/start-plugin-core/src/rsbuild/start-compiler-host.ts
  • packages/start-plugin-core/src/rsbuild/start-router-plugin.ts
  • packages/start-plugin-core/src/rsbuild/swc-rsc.ts
  • packages/start-plugin-core/src/rsbuild/types.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts
  • packages/start-plugin-core/src/start-compiler/compiler.ts
  • packages/start-plugin-core/src/start-compiler/handleCreateServerFn.ts
  • packages/start-plugin-core/src/start-compiler/load-module.ts
  • packages/start-plugin-core/src/start-compiler/types.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/types.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/virtualModules.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/src/vite/post-server-build.ts
  • packages/start-plugin-core/src/vite/prerender.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/hot-update.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/module-specifier.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • packages/start-plugin-core/tests/compiler.test.ts
  • packages/start-plugin-core/tests/importProtection/analysis.exports.test.ts
  • packages/start-plugin-core/tests/importProtection/analysis.usage.test.ts
  • packages/start-plugin-core/tests/importProtection/defaults.test.ts
  • packages/start-plugin-core/tests/importProtection/matchers.test.ts
  • packages/start-plugin-core/tests/importProtection/rewrite.test.ts
  • packages/start-plugin-core/tests/importProtection/sourceLocation.test.ts
  • packages/start-plugin-core/tests/importProtection/trace.test.ts
  • packages/start-plugin-core/tests/importProtection/utils.test.ts
  • packages/start-plugin-core/tests/importProtection/virtualModules.test.ts
  • packages/start-plugin-core/tests/post-server-build.test.ts
  • packages/start-plugin-core/tests/prerender-ssrf.test.ts
  • packages/start-plugin-core/tests/rsbuild-output-directory.test.ts
  • packages/start-plugin-core/tests/rsbuild-plugin.test.ts
  • packages/start-plugin-core/tests/rsbuild-post-build.test.ts
  • packages/start-plugin-core/tests/vite-start-compiler-plugin.test.ts
  • packages/vue-start/package.json
  • packages/vue-start/src/plugin/rsbuild.ts
  • packages/vue-start/vite.config.ts
💤 Files with no reviewable changes (11)
  • e2e/react-start/basic-rsc/.devcontainer/devcontainer.json
  • packages/router-plugin/src/core/hmr-hot-expression.ts
  • packages/start-plugin-core/src/import-protection/trace.ts
  • e2e/react-start/rsc/src/utils/conditionalCssServerComponent.tsx
  • e2e/react-start/rsc/src/utils/cssModulesServerComponent.tsx
  • packages/react-start-rsc/src/ServerComponentTypes.ts
  • packages/react-start-rsc/src/renderServerComponent.ts
  • packages/start-plugin-core/src/import-protection-plugin/postCompileUsage.ts
  • packages/start-plugin-core/src/start-compiler/load-module.ts
  • packages/start-plugin-core/src/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/import-protection-plugin/rewriteDeniedImports.ts
✅ Files skipped from review due to trivial changes (51)
  • packages/react-router-devtools/src/TanStackRouterDevtools.tsx
  • packages/react-router-devtools/src/TanStackRouterDevtoolsPanel.tsx
  • e2e/react-start/basic/postcss.config.mjs
  • e2e/react-start/hmr/postcss.config.mjs
  • e2e/solid-start/basic/postcss.config.mjs
  • e2e/react-start/hmr/.gitignore
  • packages/vue-start/vite.config.ts
  • packages/react-router-devtools/src/index.ts
  • e2e/react-start/basic/.gitignore
  • packages/react-router/src/ClientOnly.tsx
  • packages/start-plugin-core/tests/importProtection/matchers.test.ts
  • packages/react-router/src/Asset.tsx
  • e2e/vue-start/basic/src/routes/__root.tsx
  • e2e/.gitignore
  • .changeset/add-rsbuild-start-support.md
  • e2e/solid-start/basic/src/routes/__root.tsx
  • packages/react-router/src/useLocation.tsx
  • e2e/vue-start/basic/postcss.config.mjs
  • packages/start-plugin-core/src/vite/import-protection-plugin/types.ts
  • e2e/react-start/basic/src/routes/__root.tsx
  • packages/react-start-rsc/src/createCompositeComponent.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • e2e/vue-start/basic/package.json
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/tests/importProtection/defaults.test.ts
  • packages/router-utils/src/ast.ts
  • packages/react-start/src/rsbuild/ssr-decode.ts
  • packages/react-start/src/rsbuild/browser-decode.ts
  • e2e/react-start/serialization-adapters/src/routes/server-function/late-raw-stream.tsx
  • packages/start-plugin-core/src/import-protection/constants.ts
  • packages/start-plugin-core/src/start-compiler/types.ts
  • packages/router-plugin/package.json
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • e2e/react-start/basic/start-mode-config.ts
  • e2e/solid-start/basic/package.json
  • e2e/react-start/rsc/rsbuild.config.ts
  • packages/react-start-rsc/src/rsbuild/browser-decode.ts
  • e2e/vue-start/basic-vue-query/src/utils/posts.tsx
  • packages/router-plugin/src/core/code-splitter/plugins.ts
  • packages/start-plugin-core/src/index.ts
  • packages/vue-start/src/plugin/rsbuild.ts
  • packages/start-plugin-core/tests/rsbuild-output-directory.test.ts
  • packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
  • e2e/solid-start/basic/rsbuild.config.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/INTERNALS.md
  • packages/start-plugin-core/src/rsbuild/types.ts
  • e2e/react-start/hmr/src/routeTree.gen.ts
  • packages/start-plugin-core/src/post-build.ts
  • packages/react-start-rsc/src/react-server-dom-rspack.d.ts
  • packages/start-plugin-core/src/import-protection/analysis.ts
  • packages/start-plugin-core/src/vite/import-protection-plugin/plugin.ts
🚧 Files skipped from review as they are similar to previous changes (23)
  • packages/react-start/vite.config.ts
  • e2e/solid-start/basic/server.js
  • e2e/react-start/basic/rsbuild.config.ts
  • e2e/react-start/import-protection/start-mode-config.ts
  • packages/solid-start/vite.config.ts
  • e2e/vue-start/basic/server.js
  • packages/react-start-rsc/src/global.d.ts
  • e2e/react-start/import-protection/tests/violations.setup.ts
  • packages/start-plugin-core/tests/importProtection/virtualModules.test.ts
  • packages/start-plugin-core/src/rsbuild/post-build.ts
  • packages/start-plugin-core/src/vite/prerender.ts
  • packages/start-plugin-core/tests/importProtection/trace.test.ts
  • packages/start-plugin-core/src/vite/post-server-build.ts
  • docs/start/framework/react/guide/import-protection.md
  • e2e/react-start/import-protection/playwright.config.ts
  • packages/start-plugin-core/tests/importProtection/rewrite.test.ts
  • e2e/react-start/import-protection/rsbuild.config.ts
  • e2e/react-start/import-protection/server.js
  • packages/vue-start/package.json
  • packages/router-utils/tests/stripTypeExports.test.ts
  • packages/react-start/package.json
  • packages/start-plugin-core/src/import-protection/adapterUtils.ts
  • packages/start-plugin-core/src/import-protection/sourceLocation.ts

Comment on lines +16 to +28
function resolveDistServerEntryPath() {
const serverJsPath = path.resolve(distDir, 'server', 'server.js')
if (fs.existsSync(serverJsPath)) {
return serverJsPath
}

const indexJsPath = path.resolve(distDir, 'server', 'index.js')
if (fs.existsSync(indexJsPath)) {
return indexJsPath
}

return serverJsPath
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fail fast when neither server entry exists.

On Line 27 this returns serverJsPath even after both existence checks failed, so a broken dist layout gets reported later as a generic import failure instead of at the point where we already know resolution failed. Throw here with both checked paths, and mirror the same guard in e2e/react-start/rsc/server.js.

Suggested fix
 function resolveDistServerEntryPath() {
   const serverJsPath = path.resolve(distDir, 'server', 'server.js')
   if (fs.existsSync(serverJsPath)) {
     return serverJsPath
   }

   const indexJsPath = path.resolve(distDir, 'server', 'index.js')
   if (fs.existsSync(indexJsPath)) {
     return indexJsPath
   }

-  return serverJsPath
+  throw new Error(
+    `Could not find a server entry. Checked: ${serverJsPath} and ${indexJsPath}`,
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/basic/server.js` around lines 16 - 28,
resolveDistServerEntryPath currently returns serverJsPath even when neither
server/server.js nor server/index.js exist; change it to fail fast by throwing
an error when both fs.existsSync checks are false (include the attempted paths
serverJsPath and indexJsPath in the thrown message for debugging). Update the
function (symbol: resolveDistServerEntryPath) to throw with a clear message
referencing distDir, serverJsPath and indexJsPath instead of returning
serverJsPath, and mirror the same guard/throw behavior as used in
e2e/react-start/rsc/server.js so both entry-resolution functions behave
consistently.

@@ -0,0 +1,4 @@
import { createClientOnlyFn, createServerOnlyFn } from '@tanstack/react-start'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove or justify the unused import to keep lint green.

Line 1 triggers unused-imports/no-unused-imports for createClientOnlyFn.

🧹 Minimal fix
-import { createClientOnlyFn, createServerOnlyFn } from '@tanstack/react-start'
+import { createServerOnlyFn } from '@tanstack/react-start'

If this import is intentionally kept for test-time file mutation, add a targeted ESLint disable comment with that rationale instead.

📝 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.

Suggested change
import { createClientOnlyFn, createServerOnlyFn } from '@tanstack/react-start'
import { createServerOnlyFn } from '@tanstack/react-start'
🧰 Tools
🪛 ESLint

[error] 1-1: 'createClientOnlyFn' is defined but never used.

(unused-imports/no-unused-imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/hmr/src/hmr/server-fn-hmr-factory.ts` at line 1, The import
line currently includes an unused symbol createClientOnlyFn which triggers
unused-imports/no-unused-imports; either remove createClientOnlyFn from the
import statement (leaving import { createServerOnlyFn } from
'@tanstack/react-start') or, if the unused import is intentional for test-time
file mutation, add a targeted ESLint disable comment directly above the import
(e.g. /* eslint-disable-next-line unused-imports/no-unused-imports -- kept for
test-time file mutation */) and include the brief rationale; reference the
import line that contains createClientOnlyFn and createServerOnlyFn to locate
the change.

Comment thread packages/router-plugin/src/core/hmr/handle-route-update.ts Outdated
Comment thread packages/router-plugin/src/core/hmr/select-adapter.ts Outdated
Comment thread packages/router-plugin/src/rspack.ts
Comment thread packages/start-plugin-core/src/rsbuild/swc-rsc.ts
Comment thread packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
Comment thread packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
Comment on lines 4 to +21
import {
buildResolutionCandidates,
buildSourceCandidates,
canonicalizeResolvedId,
checkFileDenial,
dedupeViolationKey,
dedupePatterns,
escapeRegExp,
extractImportSources,
getOrCreate,
isInsideDirectory,
isFileExcluded,
normalizeFilePath,
relativizePath,
shouldDeferViolation,
stripQuery,
stripQueryAndHash,
withoutKnownExtension,
} from '../../src/import-protection-plugin/utils'
} from '../../src/import-protection/utils'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reorder import members to satisfy sort-imports.

dedupePatterns should come before dedupeViolationKey in this named import list.

✅ Suggested fix
   canonicalizeResolvedId,
   checkFileDenial,
-  dedupeViolationKey,
   dedupePatterns,
+  dedupeViolationKey,
   escapeRegExp,
📝 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.

Suggested change
import {
buildResolutionCandidates,
buildSourceCandidates,
canonicalizeResolvedId,
checkFileDenial,
dedupeViolationKey,
dedupePatterns,
escapeRegExp,
extractImportSources,
getOrCreate,
isInsideDirectory,
isFileExcluded,
normalizeFilePath,
relativizePath,
shouldDeferViolation,
stripQuery,
stripQueryAndHash,
withoutKnownExtension,
} from '../../src/import-protection-plugin/utils'
} from '../../src/import-protection/utils'
import {
buildResolutionCandidates,
buildSourceCandidates,
canonicalizeResolvedId,
checkFileDenial,
dedupePatterns,
dedupeViolationKey,
escapeRegExp,
getOrCreate,
isInsideDirectory,
isFileExcluded,
normalizeFilePath,
relativizePath,
shouldDeferViolation,
stripQuery,
stripQueryAndHash,
withoutKnownExtension,
} from '../../src/import-protection/utils'
🧰 Tools
🪛 ESLint

[error] 10-10: Member 'dedupePatterns' of the import declaration should be sorted alphabetically.

(sort-imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/importProtection/utils.test.ts` around lines
4 - 21, The named import list from utils has members out of sort order for the
linter; move dedupePatterns so it appears before dedupeViolationKey in the
import statement that imports buildResolutionCandidates, buildSourceCandidates,
..., withoutKnownExtension from '../../src/import-protection/utils' (i.e.,
reorder the identifiers so dedupePatterns precedes dedupeViolationKey).

Comment on lines +3 to +20
vi.mock('@tanstack/start-server-core', () => ({
VIRTUAL_MODULES: {
serverFnResolver: 'virtual:tanstack-start-server-fn-resolver',
},
}))

import {
SERVER_FN_LOOKUP,
startCompilerPlugin,
} from '../src/vite/start-compiler-plugin/plugin'
import {
createViteDevServerFnModuleSpecifierEncoder,
decodeViteDevServerModuleSpecifier,
} from '../src/vite/start-compiler-plugin/module-specifier'
import { mergeHotUpdateModules } from '../src/vite/start-compiler-plugin/hot-update'
import type { Plugin } from 'vite'
import type { EnvironmentModuleNode } from 'vite'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix top-level import order and duplicate vite imports.

Lines 9-20 currently violate import/first, and Lines 18-19 duplicate the same vite import source.

✅ Suggested fix
 import { describe, expect, test, vi } from 'vitest'
+import {
+  SERVER_FN_LOOKUP,
+  startCompilerPlugin,
+} from '../src/vite/start-compiler-plugin/plugin'
+import {
+  createViteDevServerFnModuleSpecifierEncoder,
+  decodeViteDevServerModuleSpecifier,
+} from '../src/vite/start-compiler-plugin/module-specifier'
+import { mergeHotUpdateModules } from '../src/vite/start-compiler-plugin/hot-update'
+import type { EnvironmentModuleNode, Plugin } from 'vite'
 
 vi.mock('@tanstack/start-server-core', () => ({
   VIRTUAL_MODULES: {
     serverFnResolver: 'virtual:tanstack-start-server-fn-resolver',
   },
 }))
-
-import {
-  SERVER_FN_LOOKUP,
-  startCompilerPlugin,
-} from '../src/vite/start-compiler-plugin/plugin'
-import {
-  createViteDevServerFnModuleSpecifierEncoder,
-  decodeViteDevServerModuleSpecifier,
-} from '../src/vite/start-compiler-plugin/module-specifier'
-import { mergeHotUpdateModules } from '../src/vite/start-compiler-plugin/hot-update'
-import type { Plugin } from 'vite'
-import type { EnvironmentModuleNode } from 'vite'
📝 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.

Suggested change
vi.mock('@tanstack/start-server-core', () => ({
VIRTUAL_MODULES: {
serverFnResolver: 'virtual:tanstack-start-server-fn-resolver',
},
}))
import {
SERVER_FN_LOOKUP,
startCompilerPlugin,
} from '../src/vite/start-compiler-plugin/plugin'
import {
createViteDevServerFnModuleSpecifierEncoder,
decodeViteDevServerModuleSpecifier,
} from '../src/vite/start-compiler-plugin/module-specifier'
import { mergeHotUpdateModules } from '../src/vite/start-compiler-plugin/hot-update'
import type { Plugin } from 'vite'
import type { EnvironmentModuleNode } from 'vite'
import { describe, expect, test, vi } from 'vitest'
import {
SERVER_FN_LOOKUP,
startCompilerPlugin,
} from '../src/vite/start-compiler-plugin/plugin'
import {
createViteDevServerFnModuleSpecifierEncoder,
decodeViteDevServerModuleSpecifier,
} from '../src/vite/start-compiler-plugin/module-specifier'
import { mergeHotUpdateModules } from '../src/vite/start-compiler-plugin/hot-update'
import type { EnvironmentModuleNode, Plugin } from 'vite'
vi.mock('@tanstack/start-server-core', () => ({
VIRTUAL_MODULES: {
serverFnResolver: 'virtual:tanstack-start-server-fn-resolver',
},
}))
🧰 Tools
🪛 ESLint

[error] 9-12: Import in body of module; reorder to top.

(import/first)


[error] 13-16: Import in body of module; reorder to top.

(import/first)


[error] 17-17: Import in body of module; reorder to top.

(import/first)


[error] 18-18: Import in body of module; reorder to top.

(import/first)


[error] 18-18: '/home/jailuser/git/node_modules/vite/dist/node/index.js' imported multiple times.

(import/no-duplicates)


[error] 19-19: Import in body of module; reorder to top.

(import/first)


[error] 19-19: '/home/jailuser/git/node_modules/vite/dist/node/index.js' imported multiple times.

(import/no-duplicates)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/vite-start-compiler-plugin.test.ts` around
lines 3 - 20, Move the vi.mock call so all import statements come first (fixing
the import/first violation) and remove/merge the duplicate imports from 'vite'
into a single import; specifically, place the
vi.mock('@tanstack/start-server-core', ...) after the import block that includes
SERVER_FN_LOOKUP and startCompilerPlugin from
'../src/vite/start-compiler-plugin/plugin' and the module-specifier and
hot-update imports, and combine the two separate type imports for Plugin and
EnvironmentModuleNode into one import statement from 'vite' to eliminate the
duplicate source.

Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We removed a stray duplicate } introduced in the loadModule callback of packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts, which prematurely closed the enclosing function scope and broke the file's syntax. This fix resolves the ESLint parsing error (',' expected at line 266) and unblocks the test:eslint, build, and test:types tasks for @tanstack/start-plugin-core.

Warning

We could not verify this fix.

diff --git a/packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts b/packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
index c3ce45fa29..07efa2a9bb 100644
--- a/packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
+++ b/packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
@@ -261,7 +261,6 @@ export function startCompilerPlugin(
                   compiler!.ingestModule({ code, id })
                   return
                 }
-                }
 
                 if (this.environment.mode !== 'dev') {
                   this.error(

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally SUyi-1IEB

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

@schiller-manuel schiller-manuel merged commit 91a7089 into main Apr 24, 2026
16 of 17 checks passed
@schiller-manuel schiller-manuel deleted the rsb branch April 24, 2026 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants