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..202cfe4984 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -315,6 +315,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { id, viteCommand, routeIdByFile, + rootRouteFile: config.unstable_rootRouteFile, viteEnvironment: this.environment, }); }, 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..5c5cdea04e 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -97,40 +97,54 @@ export function transformVirtualRouteModules({ code, viteCommand, routeIdByFile, + rootRouteFile, viteEnvironment, }: { id: string; code: string; viteCommand: ViteCommand; routeIdByFile: Map; + rootRouteFile: string; viteEnvironment: Vite.Environment; }) { if (isVirtualRouteModuleId(id) || routeIdByFile.has(id)) { return createVirtualRouteModuleCode({ id, code, + rootRouteFile, 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, + rootRouteFile, + viteCommand, + }); } } async function createVirtualRouteModuleCode({ id, code: routeSource, + rootRouteFile, viteCommand, viteEnvironment, }: { id: string; code: string; + rootRouteFile: string; viteCommand: ViteCommand; viteEnvironment: Vite.Environment; }) { @@ -183,7 +197,10 @@ async function createVirtualRouteModuleCode({ } } - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteFile({ id, rootRouteFile }) && + !staticExports.includes("ErrorBoundary") + ) { code += `export { ErrorBoundary } from "${clientModuleId}";\n`; } @@ -236,10 +253,12 @@ function createVirtualServerRouteModuleCode({ function createVirtualClientRouteModuleCode({ id, code: routeSource, + rootRouteFile, viteCommand, }: { id: string; code: string; + rootRouteFile: string; viteCommand: ViteCommand; }) { const { staticExports, isServerFirstRoute, hasClientExports } = @@ -256,7 +275,10 @@ function createVirtualClientRouteModuleCode({ const generatorResult = babel.generate(clientRouteModuleAst); generatorResult.code = '"use client";' + generatorResult.code; - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteFile({ id, rootRouteFile }) && + !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 +310,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 { @@ -310,3 +328,14 @@ export function isVirtualClientRouteModuleId(id: string): boolean { function isVirtualServerRouteModuleId(id: string): boolean { return /(\?|&)server-route-module(&|$)/.test(id); } + +function isRootRouteFile({ + id, + rootRouteFile, +}: { + id: string; + rootRouteFile: string; +}): boolean { + const filePath = id.split("?")[0]; + return filePath === rootRouteFile; +}