From 4f4dbdfef802c1a4b1741677bd9f541ce52a079c Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Wed, 10 Sep 2025 15:58:01 +1000 Subject: [PATCH 1/4] Fix root route pre-bundle warnings in RSC Framework Mode --- .../vite/rsc/virtual-route-config.ts | 2 +- .../vite/rsc/virtual-route-modules.ts | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/react-router-dev/vite/rsc/virtual-route-config.ts b/packages/react-router-dev/vite/rsc/virtual-route-config.ts index a9a4352b4a..1edddcb042 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-config.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-config.ts @@ -28,7 +28,7 @@ export function createVirtualRouteConfig({ const routeId = route.id || createRouteId(route.file, appDirectory); routeIdByFile.set(routeFile, routeId); code += `lazy: () => import(${JSON.stringify( - `${routeFile}?route-module${routeId === "root" ? "&root-route=true" : ""}`, + `${routeFile}?route-module`, )}),`; code += `id: ${JSON.stringify(routeId)},`; diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index 0dc93141ff..0f327b8edd 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -183,7 +183,7 @@ async function createVirtualRouteModuleCode({ } } - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if (id === "root" && !staticExports.includes("ErrorBoundary")) { code += `export { ErrorBoundary } from "${clientModuleId}";\n`; } @@ -256,7 +256,7 @@ function createVirtualClientRouteModuleCode({ const generatorResult = babel.generate(clientRouteModuleAst); generatorResult.code = '"use client";' + generatorResult.code; - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if (id === "root" && !staticExports.includes("ErrorBoundary")) { const hasRootLayout = staticExports.includes("Layout"); generatorResult.code += `\nimport { createElement as __rr_createElement } from "react";\n`; generatorResult.code += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`; @@ -288,15 +288,11 @@ export function parseRouteExports(code: string) { } function getVirtualClientModuleId(id: string): string { - return `${id.split("?")[0]}?client-route-module${isRootRouteId(id) ? "&root-route=true" : ""}`; + return `${id.split("?")[0]}?client-route-module`; } function getVirtualServerModuleId(id: string): string { - return `${id.split("?")[0]}?server-route-module${isRootRouteId(id) ? "&root-route=true" : ""}`; -} - -function isRootRouteId(id: string): boolean { - return /(\?|&)root-route=true(&|$)/.test(id); + return `${id.split("?")[0]}?server-route-module`; } function isVirtualRouteModuleId(id: string): boolean { From 6643a1c81436876a3c96230eb59be74cb0871c8c Mon Sep 17 00:00:00 2001 From: markdalgleish Date: Wed, 10 Sep 2025 17:02:23 +1000 Subject: [PATCH 2/4] Fix root route ID check --- .../vite/rsc/virtual-route-modules.ts | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index 0f327b8edd..f2779069a2 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -109,28 +109,40 @@ export function transformVirtualRouteModules({ return createVirtualRouteModuleCode({ id, code, + routeIdByFile, viteCommand, viteEnvironment, }); } if (isVirtualServerRouteModuleId(id)) { - return createVirtualServerRouteModuleCode({ id, code, viteEnvironment }); + return createVirtualServerRouteModuleCode({ + id, + code, + viteEnvironment, + }); } if (isVirtualClientRouteModuleId(id)) { - return createVirtualClientRouteModuleCode({ id, code, viteCommand }); + return createVirtualClientRouteModuleCode({ + id, + code, + routeIdByFile, + viteCommand, + }); } } async function createVirtualRouteModuleCode({ id, code: routeSource, + routeIdByFile, viteCommand, viteEnvironment, }: { id: string; code: string; + routeIdByFile: Map; viteCommand: ViteCommand; viteEnvironment: Vite.Environment; }) { @@ -183,7 +195,10 @@ async function createVirtualRouteModuleCode({ } } - if (id === "root" && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteId({ id, routeIdByFile }) && + !staticExports.includes("ErrorBoundary") + ) { code += `export { ErrorBoundary } from "${clientModuleId}";\n`; } @@ -236,10 +251,12 @@ function createVirtualServerRouteModuleCode({ function createVirtualClientRouteModuleCode({ id, code: routeSource, + routeIdByFile, viteCommand, }: { id: string; code: string; + routeIdByFile: Map; viteCommand: ViteCommand; }) { const { staticExports, isServerFirstRoute, hasClientExports } = @@ -256,7 +273,10 @@ function createVirtualClientRouteModuleCode({ const generatorResult = babel.generate(clientRouteModuleAst); generatorResult.code = '"use client";' + generatorResult.code; - if (id === "root" && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteId({ id, routeIdByFile }) && + !staticExports.includes("ErrorBoundary") + ) { const hasRootLayout = staticExports.includes("Layout"); generatorResult.code += `\nimport { createElement as __rr_createElement } from "react";\n`; generatorResult.code += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`; @@ -306,3 +326,13 @@ export function isVirtualClientRouteModuleId(id: string): boolean { function isVirtualServerRouteModuleId(id: string): boolean { return /(\?|&)server-route-module(&|$)/.test(id); } + +function isRootRouteId({ + id, + routeIdByFile, +}: { + id: string; + routeIdByFile: Map; +}) { + return routeIdByFile.has(id.split("?")[0]); +} From 28600316d38b20a93b9fc3326de6d8ff5087686a Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 11 Sep 2025 12:09:30 +1000 Subject: [PATCH 3/4] Check module ID against resolved root route path --- integration/vite-presets-test.ts | 1 + packages/react-router-dev/config/config.ts | 17 +++++++--- packages/react-router-dev/vite/rsc/plugin.ts | 3 +- .../vite/rsc/virtual-route-modules.ts | 33 ++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/integration/vite-presets-test.ts b/integration/vite-presets-test.ts index 34a2c3933f..3c88154853 100644 --- a/integration/vite-presets-test.ts +++ b/integration/vite-presets-test.ts @@ -226,6 +226,7 @@ test.describe("Vite / presets", async () => { "serverBundles", "serverModuleFormat", "ssr", + "unstable_rootRouteFile", "unstable_routeConfig", ]); diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts index 68ef25cb12..98353026b8 100644 --- a/packages/react-router-dev/config/config.ts +++ b/packages/react-router-dev/config/config.ts @@ -264,6 +264,10 @@ export type ResolvedReactRouterConfig = Readonly<{ * SPA without server-rendering. Default's to `true`. */ ssr: boolean; + /** + * The absolute path to the root route file. + */ + unstable_rootRouteFile: string; /** * The resolved array of route config entries exported from `routes.ts` */ @@ -345,6 +349,8 @@ type Result = error: string; }; +type ConfigResult = Result; + function ok(value: T): Result { return { ok: true, value }; } @@ -365,7 +371,7 @@ async function resolveConfig({ reactRouterConfigFile?: string; skipRoutes?: boolean; validateConfig?: ValidateConfigFunction; -}): Promise> { +}): Promise { let reactRouterUserConfig: ReactRouterConfig = {}; if (reactRouterConfigFile) { @@ -506,7 +512,7 @@ async function resolveConfig({ let appDirectory = Path.resolve(root, userAppDirectory || "app"); let buildDirectory = Path.resolve(root, userBuildDirectory); - let rootRouteFile = findEntry(appDirectory, "root"); + let rootRouteFile = findEntry(appDirectory, "root", { absolute: true }); if (!rootRouteFile) { let rootRouteDisplayPath = Path.relative( root, @@ -556,7 +562,7 @@ async function resolveConfig({ { id: "root", path: "", - file: rootRouteFile, + file: Path.relative(appDirectory, rootRouteFile), children: result.routeConfig, }, ]; @@ -609,6 +615,7 @@ async function resolveConfig({ serverBundles, serverModuleFormat, ssr, + unstable_rootRouteFile: rootRouteFile, unstable_routeConfig: routeConfig, } satisfies ResolvedReactRouterConfig); @@ -622,7 +629,7 @@ async function resolveConfig({ type ChokidarEventName = ChokidarEmitArgs[0]; type ChangeHandler = (args: { - result: Result; + result: ConfigResult; configCodeChanged: boolean; routeConfigCodeChanged: boolean; configChanged: boolean; @@ -632,7 +639,7 @@ type ChangeHandler = (args: { }) => void; export type ConfigLoader = { - getConfig: () => Promise>; + getConfig: () => Promise; onChange: (handler: ChangeHandler) => () => void; close: () => Promise; }; diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 3c2b15efa4..154311bbea 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -309,12 +309,11 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { { name: "react-router/rsc/virtual-route-modules", transform(code, id) { - if (!routeIdByFile) return; return transformVirtualRouteModules({ code, id, viteCommand, - routeIdByFile, + rootRouteFile: config.unstable_rootRouteFile, viteEnvironment: this.environment, }); }, diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index f2779069a2..8825fde7d3 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -96,20 +96,20 @@ export function transformVirtualRouteModules({ id, code, viteCommand, - routeIdByFile, + rootRouteFile, viteEnvironment, }: { id: string; code: string; viteCommand: ViteCommand; - routeIdByFile: Map; + rootRouteFile: string; viteEnvironment: Vite.Environment; }) { - if (isVirtualRouteModuleId(id) || routeIdByFile.has(id)) { + if (isVirtualRouteModuleId(id)) { return createVirtualRouteModuleCode({ id, code, - routeIdByFile, + rootRouteFile, viteCommand, viteEnvironment, }); @@ -127,7 +127,7 @@ export function transformVirtualRouteModules({ return createVirtualClientRouteModuleCode({ id, code, - routeIdByFile, + rootRouteFile, viteCommand, }); } @@ -136,13 +136,13 @@ export function transformVirtualRouteModules({ async function createVirtualRouteModuleCode({ id, code: routeSource, - routeIdByFile, + rootRouteFile, viteCommand, viteEnvironment, }: { id: string; code: string; - routeIdByFile: Map; + rootRouteFile: string; viteCommand: ViteCommand; viteEnvironment: Vite.Environment; }) { @@ -196,7 +196,7 @@ async function createVirtualRouteModuleCode({ } if ( - isRootRouteId({ id, routeIdByFile }) && + isRootRouteFile({ id, rootRouteFile }) && !staticExports.includes("ErrorBoundary") ) { code += `export { ErrorBoundary } from "${clientModuleId}";\n`; @@ -251,12 +251,12 @@ function createVirtualServerRouteModuleCode({ function createVirtualClientRouteModuleCode({ id, code: routeSource, - routeIdByFile, + rootRouteFile, viteCommand, }: { id: string; code: string; - routeIdByFile: Map; + rootRouteFile: string; viteCommand: ViteCommand; }) { const { staticExports, isServerFirstRoute, hasClientExports } = @@ -274,7 +274,7 @@ function createVirtualClientRouteModuleCode({ generatorResult.code = '"use client";' + generatorResult.code; if ( - isRootRouteId({ id, routeIdByFile }) && + isRootRouteFile({ id, rootRouteFile }) && !staticExports.includes("ErrorBoundary") ) { const hasRootLayout = staticExports.includes("Layout"); @@ -327,12 +327,13 @@ function isVirtualServerRouteModuleId(id: string): boolean { return /(\?|&)server-route-module(&|$)/.test(id); } -function isRootRouteId({ +function isRootRouteFile({ id, - routeIdByFile, + rootRouteFile, }: { id: string; - routeIdByFile: Map; -}) { - return routeIdByFile.has(id.split("?")[0]); + rootRouteFile: string; +}): boolean { + const filePath = id.split("?")[0]; + return filePath === rootRouteFile; } From 6d80e40754a376854238c9fecdd159231041281b Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 11 Sep 2025 14:12:21 +1000 Subject: [PATCH 4/4] Fix duplicated route module code --- packages/react-router-dev/vite/rsc/plugin.ts | 2 ++ packages/react-router-dev/vite/rsc/virtual-route-modules.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 154311bbea..202cfe4984 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -309,10 +309,12 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { { name: "react-router/rsc/virtual-route-modules", transform(code, id) { + if (!routeIdByFile) return; return transformVirtualRouteModules({ code, id, viteCommand, + routeIdByFile, rootRouteFile: config.unstable_rootRouteFile, viteEnvironment: this.environment, }); diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index 8825fde7d3..5c5cdea04e 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -96,16 +96,18 @@ export function transformVirtualRouteModules({ id, code, viteCommand, + routeIdByFile, rootRouteFile, viteEnvironment, }: { id: string; code: string; viteCommand: ViteCommand; + routeIdByFile: Map; rootRouteFile: string; viteEnvironment: Vite.Environment; }) { - if (isVirtualRouteModuleId(id)) { + if (isVirtualRouteModuleId(id) || routeIdByFile.has(id)) { return createVirtualRouteModuleCode({ id, code,