From 51ad849e977def2d60d7424b764316bd9e0ca175 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Fri, 6 Jun 2025 14:30:26 +1000 Subject: [PATCH] Support RSC lazy `default` export as `Component` fallback --- .../helpers/rsc-parcel/src/routes/home.tsx | 2 +- integration/rsc/rsc-test.ts | 22 ++++---- packages/react-router/lib/rsc/server.rsc.ts | 51 +++++++++++++------ .../src/routes/about/about.client.tsx | 2 +- .../rsc-parcel/src/routes/about/about.tsx | 2 +- .../src/routes/fetcher/fetcher.client.tsx | 2 +- .../rsc-parcel/src/routes/fetcher/fetcher.ts | 2 +- .../src/routes/home/home.client.tsx | 2 +- .../rsc-parcel/src/routes/home/home.tsx | 2 +- .../src/routes/root/root.client.tsx | 2 +- .../rsc-parcel/src/routes/root/root.tsx | 2 +- .../src/routes/about/about.client.tsx | 2 +- playground/rsc-vite/src/routes/home/home.tsx | 2 +- .../src/routes/parent-index/parent-index.tsx | 2 +- .../rsc-vite/src/routes/parent/parent.tsx | 2 +- playground/rsc-vite/src/routes/root/root.tsx | 2 +- 16 files changed, 60 insertions(+), 41 deletions(-) diff --git a/integration/helpers/rsc-parcel/src/routes/home.tsx b/integration/helpers/rsc-parcel/src/routes/home.tsx index 9dc86aec4a..b5e522802e 100644 --- a/integration/helpers/rsc-parcel/src/routes/home.tsx +++ b/integration/helpers/rsc-parcel/src/routes/home.tsx @@ -1,3 +1,3 @@ -export function Component() { +export default function HomeRoute() { return

Home

; } diff --git a/integration/rsc/rsc-test.ts b/integration/rsc/rsc-test.ts index d8ee2d8790..7c535a1349 100644 --- a/integration/rsc/rsc-test.ts +++ b/integration/rsc/rsc-test.ts @@ -109,7 +109,7 @@ implementations.forEach((implementation) => { export function loader() { return { message: "Loader Data" }; } - export function Component({ loaderData }) { + export default function HomeRoute({ loaderData }) { return

Home: {loaderData.message}

; } `, @@ -141,7 +141,7 @@ implementations.forEach((implementation) => { return { message: "Loader Data" }; } - export function Component({ loaderData }) { + export default function HomeRoute({ loaderData }) { return (

Home: {loaderData.message}

@@ -236,7 +236,7 @@ implementations.forEach((implementation) => { return { message: "Home Page Data" }; } - export function Component({ loaderData }) { + export default function HomeRoute({ loaderData }) { return (

Home Page

@@ -251,7 +251,7 @@ implementations.forEach((implementation) => { return { count: 1 }; } - export { Component } from "./dashboard.client"; + export { default } from "./dashboard.client"; `, "src/routes/dashboard.client.tsx": js` "use client"; @@ -260,7 +260,7 @@ implementations.forEach((implementation) => { import { Link } from "react-router"; // Export the entire route as a client component - export function Component({ loaderData }) { + export default function DashboardRoute({ loaderData }) { const [count, setCount] = useState(loaderData.count); return ( @@ -367,7 +367,7 @@ implementations.forEach((implementation) => { return { message: "Home Page Data" }; } - export function Component({ loaderData }) { + export default function HomeRoute({ loaderData }) { return (

Home Page

@@ -477,7 +477,7 @@ implementations.forEach((implementation) => { } `, "src/routes/home.tsx": js` - export { Component } from "./home.client"; + export { default } from "./home.client"; `, "src/routes/home.client.tsx": js` "use client"; @@ -486,7 +486,7 @@ implementations.forEach((implementation) => { import { incrementCounter } from "./home.actions"; - export function Component() { + export default function HomeRoute() { const [count, incrementCounterAction, incrementing] = useActionState(incrementCounter, 0); return ( @@ -547,7 +547,7 @@ implementations.forEach((implementation) => { return { name, count }; } - export function Component({ loaderData }) { + export default function HomeRoute({ loaderData }) { const updateCounter = async (formData: FormData) => { "use server"; name = formData.get("name"); @@ -624,7 +624,7 @@ implementations.forEach((implementation) => { import { redirectAction } from "./home.actions"; import { Counter } from "./home.client"; - export function Component(props) { + export default function HomeRoute(props) { console.log({props}); return (
@@ -697,7 +697,7 @@ implementations.forEach((implementation) => { throw new Error("Intentional error from loader"); } - export function Component() { + export default function HomeRoute() { return

This shouldn't render

; } diff --git a/packages/react-router/lib/rsc/server.rsc.ts b/packages/react-router/lib/rsc/server.rsc.ts index 211fbb94e1..a3756293f7 100644 --- a/packages/react-router/lib/rsc/server.rsc.ts +++ b/packages/react-router/lib/rsc/server.rsc.ts @@ -33,7 +33,6 @@ type ServerRouteObjectBase = { action?: ActionFunction; clientAction?: ClientActionFunction; clientLoader?: ClientLoaderFunction; - Component?: React.ComponentType; ErrorBoundary?: React.ComponentType; handle?: any; headers?: HeadersFunction; @@ -48,7 +47,20 @@ type ServerRouteObjectBase = { export type ServerRouteObject = ServerRouteObjectBase & { id: string; path?: string; - lazy?: () => Promise; + Component?: React.ComponentType; + lazy?: () => Promise< + ServerRouteObjectBase & + ( + | { + default?: React.ComponentType; + Component?: never; + } + | { + default?: never; + Component?: React.ComponentType; + } + ) + >; } & ( | { index: true; @@ -578,17 +590,8 @@ async function getServerRouteMatch( shouldRenderComponent: boolean, parentId: string | undefined ) { - if ("lazy" in match.route && match.route.lazy) { - Object.assign(match.route, { - // @ts-expect-error - FIXME: Fix the types here - ...((await match.route.lazy()) as any), - path: match.route.path, - index: (match.route as any).index, - id: match.route.id, - }); - match.route.lazy = undefined; - } - + // @ts-expect-error - FIXME: Fix the types here + await explodeLazyRoute(match.route); const Layout = (match.route as any).Layout || React.Fragment; const Component = (match.route as any).Component; const ErrorBoundary = (match.route as any).ErrorBoundary; @@ -712,9 +715,25 @@ async function getManifestRoute( async function explodeLazyRoute(route: ServerRouteObject) { if ("lazy" in route && route.lazy) { - let impl = (await route.lazy()) as any; - for (let [k, v] of Object.entries(impl)) { - route[k as keyof ServerRouteObject] = v; + let { + default: lazyDefaultExport, + Component: lazyComponentExport, + ...lazyProperties + } = (await route.lazy()) as any; + let Component = lazyComponentExport || lazyDefaultExport; + if (Component && !route.Component) { + route.Component = Component; + } + for (let [k, v] of Object.entries(lazyProperties)) { + if ( + k !== "id" && + k !== "path" && + k !== "index" && + k !== "children" && + route[k as keyof ServerRouteObject] == null + ) { + route[k as keyof ServerRouteObject] = v; + } } route.lazy = undefined; } diff --git a/playground/rsc-parcel/src/routes/about/about.client.tsx b/playground/rsc-parcel/src/routes/about/about.client.tsx index a60e964447..e6d57616b3 100644 --- a/playground/rsc-parcel/src/routes/about/about.client.tsx +++ b/playground/rsc-parcel/src/routes/about/about.client.tsx @@ -15,7 +15,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) { }; } -export function Component() { +export default function AboutRoute() { const { client, message } = useLoaderData(); return ( diff --git a/playground/rsc-parcel/src/routes/about/about.tsx b/playground/rsc-parcel/src/routes/about/about.tsx index b1222a2056..815c50903d 100644 --- a/playground/rsc-parcel/src/routes/about/about.tsx +++ b/playground/rsc-parcel/src/routes/about/about.tsx @@ -1,4 +1,4 @@ -export { clientLoader, Component } from "./about.client"; +export { clientLoader, default } from "./about.client"; export function loader() { return { diff --git a/playground/rsc-parcel/src/routes/fetcher/fetcher.client.tsx b/playground/rsc-parcel/src/routes/fetcher/fetcher.client.tsx index d1b72782f0..2c48ea8963 100644 --- a/playground/rsc-parcel/src/routes/fetcher/fetcher.client.tsx +++ b/playground/rsc-parcel/src/routes/fetcher/fetcher.client.tsx @@ -3,7 +3,7 @@ import { useFetcher } from "react-router"; import type { loader } from "../resource/resource"; -export function Component() { +export default function FetcherRoute() { const fetcher = useFetcher(); return ( diff --git a/playground/rsc-parcel/src/routes/fetcher/fetcher.ts b/playground/rsc-parcel/src/routes/fetcher/fetcher.ts index 8887fa87ad..e7dadeb8d8 100644 --- a/playground/rsc-parcel/src/routes/fetcher/fetcher.ts +++ b/playground/rsc-parcel/src/routes/fetcher/fetcher.ts @@ -1 +1 @@ -export { Component } from "./fetcher.client"; +export { default } from "./fetcher.client"; diff --git a/playground/rsc-parcel/src/routes/home/home.client.tsx b/playground/rsc-parcel/src/routes/home/home.client.tsx index 70b50d001b..11277d9199 100644 --- a/playground/rsc-parcel/src/routes/home/home.client.tsx +++ b/playground/rsc-parcel/src/routes/home/home.client.tsx @@ -15,7 +15,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) { }; } -export function Component() { +export default function HomeRoute() { const { client, message } = useLoaderData(); return ( diff --git a/playground/rsc-parcel/src/routes/home/home.tsx b/playground/rsc-parcel/src/routes/home/home.tsx index a8678017c4..22fffe84be 100644 --- a/playground/rsc-parcel/src/routes/home/home.tsx +++ b/playground/rsc-parcel/src/routes/home/home.tsx @@ -1,4 +1,4 @@ -export { clientLoader, Component } from "./home.client"; +export { clientLoader, default } from "./home.client"; export function loader() { return { diff --git a/playground/rsc-parcel/src/routes/root/root.client.tsx b/playground/rsc-parcel/src/routes/root/root.client.tsx index bdf8e2fbd3..03ccf799f8 100644 --- a/playground/rsc-parcel/src/routes/root/root.client.tsx +++ b/playground/rsc-parcel/src/routes/root/root.client.tsx @@ -27,7 +27,7 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } -export function Component() { +export default function RootRoute() { const { counter, message } = useLoaderData(); return ( <> diff --git a/playground/rsc-parcel/src/routes/root/root.tsx b/playground/rsc-parcel/src/routes/root/root.tsx index f4ffa957d9..347dbd2248 100644 --- a/playground/rsc-parcel/src/routes/root/root.tsx +++ b/playground/rsc-parcel/src/routes/root/root.tsx @@ -1,4 +1,4 @@ -export { Component, ErrorBoundary, Layout } from "./root.client"; +export { default, ErrorBoundary, Layout } from "./root.client"; import { Counter } from "../../counter"; diff --git a/playground/rsc-vite/src/routes/about/about.client.tsx b/playground/rsc-vite/src/routes/about/about.client.tsx index 47a3738b01..e0c7a4079d 100644 --- a/playground/rsc-vite/src/routes/about/about.client.tsx +++ b/playground/rsc-vite/src/routes/about/about.client.tsx @@ -31,7 +31,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) { clientLoader.hydrate = true; -export function Component() { +export default function AboutRoute() { const loaderData = useLoaderData(); const actionData = useActionData(); diff --git a/playground/rsc-vite/src/routes/home/home.tsx b/playground/rsc-vite/src/routes/home/home.tsx index b08e936428..89e1234b8a 100644 --- a/playground/rsc-vite/src/routes/home/home.tsx +++ b/playground/rsc-vite/src/routes/home/home.tsx @@ -14,7 +14,7 @@ export async function loader({ request }: LoaderFunctionArgs) { }; } -export function Component({ +export default function HomeRoute({ loaderData: { message, wasRedirected }, }: { loaderData: Awaited>; diff --git a/playground/rsc-vite/src/routes/parent-index/parent-index.tsx b/playground/rsc-vite/src/routes/parent-index/parent-index.tsx index 971b5dc7b3..ed3e26fad7 100644 --- a/playground/rsc-vite/src/routes/parent-index/parent-index.tsx +++ b/playground/rsc-vite/src/routes/parent-index/parent-index.tsx @@ -5,7 +5,7 @@ export async function loader() { }; } -export function Component({ +export default function ParentIndexRoute({ loaderData, }: { loaderData: Awaited>; diff --git a/playground/rsc-vite/src/routes/parent/parent.tsx b/playground/rsc-vite/src/routes/parent/parent.tsx index 9a306e3ef8..7c932b3507 100644 --- a/playground/rsc-vite/src/routes/parent/parent.tsx +++ b/playground/rsc-vite/src/routes/parent/parent.tsx @@ -6,7 +6,7 @@ export function loader() { }; } -export function Component({ +export default function ParentRoute({ loaderData, }: { loaderData: Awaited>; diff --git a/playground/rsc-vite/src/routes/root/root.tsx b/playground/rsc-vite/src/routes/root/root.tsx index 008c6f8ab0..cfe865d0fe 100644 --- a/playground/rsc-vite/src/routes/root/root.tsx +++ b/playground/rsc-vite/src/routes/root/root.tsx @@ -27,7 +27,7 @@ export async function loader() { }; } -export function Component({ +export default function RootRoute({ loaderData, }: { loaderData: Awaited>;