From dcedb1c6c08b6aa48ab8a6995786f65b0ab55d74 Mon Sep 17 00:00:00 2001 From: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com> Date: Sat, 25 Jan 2025 15:56:10 +0100 Subject: [PATCH] refactor: create `TuonoContext` (#413) --- crates/tuono_lib/src/payload.rs | 16 +++--- packages/tuono-router/src/components/Link.tsx | 2 +- .../tuono-router/src/components/Matches.tsx | 12 +++-- .../tuono-router/src/components/NotFound.tsx | 2 +- .../src/components/RouteMatch.spec.tsx | 7 +-- .../src/components/RouteMatch.tsx | 10 ++-- .../src/components/RouterContext.tsx | 42 ++++++++-------- .../src/components/RouterProvider.tsx | 18 ++++--- .../src/{globals.ts => globals.d.ts} | 2 - .../{useRoute.spec.tsx => useRoute.spec.ts} | 2 +- .../src/hooks/{useRoute.tsx => useRoute.ts} | 4 +- .../src/hooks/{useRouter.tsx => useRouter.ts} | 0 ...rSideProps.tsx => useServerPayloadData.ts} | 26 +++++----- packages/tuono-router/src/index.ts | 6 +-- packages/tuono-router/src/router.ts | 3 +- packages/tuono-router/src/types.ts | 19 +++---- packages/tuono/src/constants.ts | 1 + packages/tuono/src/globals.d.ts | 8 +++ packages/tuono/src/shared/DevResources.tsx | 8 +-- packages/tuono/src/shared/ProdResources.tsx | 9 ++-- .../shared/RouterContextProviderWrapper.tsx | 30 +++++++++++ packages/tuono/src/shared/TuonoContext.tsx | 50 +++++++++++++++++++ packages/tuono/src/shared/TuonoEntryPoint.tsx | 20 +++++--- packages/tuono/src/shared/TuonoScripts.tsx | 11 ++-- packages/tuono/src/ssr/server.tsx | 7 +-- packages/tuono/src/types.ts | 48 ++++++++++++++++++ 26 files changed, 253 insertions(+), 110 deletions(-) rename packages/tuono-router/src/{globals.ts => globals.d.ts} (58%) rename packages/tuono-router/src/hooks/{useRoute.spec.tsx => useRoute.spec.ts} (97%) rename packages/tuono-router/src/hooks/{useRoute.tsx => useRoute.ts} (94%) rename packages/tuono-router/src/hooks/{useRouter.tsx => useRouter.ts} (100%) rename packages/tuono-router/src/hooks/{useServerSideProps.tsx => useServerPayloadData.ts} (85%) create mode 100644 packages/tuono/src/constants.ts create mode 100644 packages/tuono/src/globals.d.ts create mode 100644 packages/tuono/src/shared/RouterContextProviderWrapper.tsx create mode 100644 packages/tuono/src/shared/TuonoContext.tsx diff --git a/crates/tuono_lib/src/payload.rs b/crates/tuono_lib/src/payload.rs index 3e04e5cd..80d963e7 100644 --- a/crates/tuono_lib/src/payload.rs +++ b/crates/tuono_lib/src/payload.rs @@ -16,8 +16,8 @@ fn has_dynamic_path(route: &str) -> bool { #[derive(SerdeSerialize)] /// This is the payload sent to the client for hydration pub struct Payload<'a> { - router: Location, - props: &'a dyn Serialize, + location: Location, + data: &'a dyn Serialize, mode: Mode, #[serde(rename(serialize = "jsBundles"))] js_bundles: Option>, @@ -28,7 +28,7 @@ pub struct Payload<'a> { } impl<'a> Payload<'a> { - pub fn new(req: &'a Request, props: &'a dyn Serialize) -> Payload<'a> { + pub fn new(req: &'a Request, data: &'a dyn Serialize) -> Payload<'a> { let config = GLOBAL_CONFIG .get() .expect("Failed to load the current config"); @@ -42,8 +42,8 @@ impl<'a> Payload<'a> { }; Payload { - router: req.location(), - props, + location: req.location(), + data, mode, js_bundles: None, css_bundles: None, @@ -79,7 +79,7 @@ impl<'a> Payload<'a> { let mut js_bundles_sources = vec![&main_bundle.file]; let mut css_bundles_sources = main_bundle.css.iter().collect::>(); - let pathname = &self.router.pathname(); + let pathname = &self.location.pathname(); let bundle_data = manifest.get(*pathname); @@ -233,8 +233,8 @@ mod tests { let location = Location::from(uri); Payload { - router: location, - props: &None::>, + location, + data: &None::>, mode, js_bundles: None, css_bundles: None, diff --git a/packages/tuono-router/src/components/Link.tsx b/packages/tuono-router/src/components/Link.tsx index dd837628..a6e62c03 100644 --- a/packages/tuono-router/src/components/Link.tsx +++ b/packages/tuono-router/src/components/Link.tsx @@ -2,7 +2,7 @@ import type * as React from 'react' import { useInView } from 'react-intersection-observer' import { useRouter } from '../hooks/useRouter' -import useRoute from '../hooks/useRoute' +import { useRoute } from '../hooks/useRoute' interface TuonoLinkProps extends React.AnchorHTMLAttributes { /** diff --git a/packages/tuono-router/src/components/Matches.tsx b/packages/tuono-router/src/components/Matches.tsx index 350fc5ce..7bd5f7e7 100644 --- a/packages/tuono-router/src/components/Matches.tsx +++ b/packages/tuono-router/src/components/Matches.tsx @@ -1,17 +1,19 @@ import type * as React from 'react' -import useRoute from '../hooks/useRoute' +import { useRoute } from '../hooks/useRoute' import { RouteMatch } from './RouteMatch' import NotFound from './NotFound' import { useRouterContext } from './RouterContext' -interface MatchesProps { +interface MatchesProps { // user defined props - serverSideProps: TServerSideProps + serverInitialData: TServerPayloadData } -export function Matches({ serverSideProps }: MatchesProps): React.JSX.Element { +export function Matches({ + serverInitialData, +}: MatchesProps): React.JSX.Element { const { location } = useRouterContext() const route = useRoute(location.pathname) @@ -20,5 +22,5 @@ export function Matches({ serverSideProps }: MatchesProps): React.JSX.Element { return } - return + return } diff --git a/packages/tuono-router/src/components/NotFound.tsx b/packages/tuono-router/src/components/NotFound.tsx index c1ab19b7..9064bcba 100644 --- a/packages/tuono-router/src/components/NotFound.tsx +++ b/packages/tuono-router/src/components/NotFound.tsx @@ -12,7 +12,7 @@ export default function NotFound(): React.JSX.Element { // Check if exists a custom 404 error page if (custom404Route) { - return + return } return ( diff --git a/packages/tuono-router/src/components/RouteMatch.spec.tsx b/packages/tuono-router/src/components/RouteMatch.spec.tsx index c29f4a48..640dbb8b 100644 --- a/packages/tuono-router/src/components/RouteMatch.spec.tsx +++ b/packages/tuono-router/src/components/RouteMatch.spec.tsx @@ -6,6 +6,7 @@ import { cleanup, render, screen } from '@testing-library/react' import type { Route } from '../route' import { RouteMatch } from './RouteMatch' + import '@testing-library/jest-dom' interface Props { @@ -41,8 +42,8 @@ describe('Test RouteMatch component', () => { }) test('It should correctly render nested routes', () => { - vi.mock('../hooks/useServerSideProps.tsx', () => ({ - useServerSideProps: (): { data: unknown; isLoading: boolean } => { + vi.mock('../hooks/useServerPayloadData.ts', () => ({ + useServerPayloadData: (): { data: unknown; isLoading: boolean } => { return { data: undefined, isLoading: false, @@ -50,7 +51,7 @@ describe('Test RouteMatch component', () => { }, })) - render() + render() expect(screen.getByTestId('root')).toHaveTextContent( 'root route parent route current route', ) diff --git a/packages/tuono-router/src/components/RouteMatch.tsx b/packages/tuono-router/src/components/RouteMatch.tsx index 0d9c832b..ac9ad7bd 100644 --- a/packages/tuono-router/src/components/RouteMatch.tsx +++ b/packages/tuono-router/src/components/RouteMatch.tsx @@ -1,12 +1,12 @@ import * as React from 'react' import type { Route } from '../route' -import { useServerSideProps } from '../hooks/useServerSideProps' +import { useServerPayloadData } from '../hooks/useServerPayloadData' -interface RouteMatchProps { +interface RouteMatchProps { route: Route // User defined server side props - serverSideProps: TServerSideProps + serverInitialData: TServerPayloadData } /** @@ -16,9 +16,9 @@ interface RouteMatchProps { */ export const RouteMatch = ({ route, - serverSideProps, + serverInitialData: serverInitialData, }: RouteMatchProps): React.JSX.Element => { - const { data, isLoading } = useServerSideProps(route, serverSideProps) + const { data, isLoading } = useServerPayloadData(route, serverInitialData) // eslint-disable-next-line react-hooks/exhaustive-deps const routes = React.useMemo(() => loadParentComponents(route), [route.id]) diff --git a/packages/tuono-router/src/components/RouterContext.tsx b/packages/tuono-router/src/components/RouterContext.tsx index b4bdfaf4..75cfcb94 100644 --- a/packages/tuono-router/src/components/RouterContext.tsx +++ b/packages/tuono-router/src/components/RouterContext.tsx @@ -2,7 +2,7 @@ import { createContext, useState, useEffect, useContext, useMemo } from 'react' import type { ReactNode } from 'react' import type { Router } from '../router' -import type { ServerRouterInfo, ServerProps } from '../types' +import type { ServerInitialLocation } from '../types' const isServerSide = typeof window === 'undefined' @@ -17,53 +17,51 @@ export interface ParsedLocation { interface RouterContextValue { router: Router location: ParsedLocation - serverSideProps?: ServerProps updateLocation: (loc: ParsedLocation) => void } -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const RouterContext = createContext(null!) +const RouterContext = createContext({} as RouterContextValue) function getInitialLocation( - serverSideProps?: ServerRouterInfo, + serverPayloadLocation: ServerInitialLocation, ): ParsedLocation { - if (typeof document === 'undefined') { + if (isServerSide) { return { - pathname: serverSideProps?.pathname || '', + pathname: serverPayloadLocation.pathname || '', hash: '', - href: serverSideProps?.href || '', - searchStr: serverSideProps?.searchStr || '', + href: serverPayloadLocation.href || '', + searchStr: serverPayloadLocation.searchStr || '', // TODO: Polyfill URLSearchParams search: {}, } } - const { location } = window + const { pathname, hash, href, search } = window.location return { - pathname: location.pathname, - hash: location.hash, - href: location.href, - searchStr: location.search, - search: Object.fromEntries(new URLSearchParams(location.search)), + pathname, + hash, + href, + searchStr: search, + search: Object.fromEntries(new URLSearchParams(search)), } } interface RouterContextProviderProps { router: Router + serverInitialLocation: ServerInitialLocation children: ReactNode - serverSideProps?: ServerProps } export function RouterContextProvider({ router, + serverInitialLocation, children, - serverSideProps, }: RouterContextProviderProps): ReactNode { // Allow the router to update options on the router instance router.update({ ...router.options } as Parameters[0]) const [location, setLocation] = useState(() => - getInitialLocation(serverSideProps?.router), + getInitialLocation(serverInitialLocation), ) /** @@ -94,14 +92,11 @@ export function RouterContextProvider({ const contextValue: RouterContextValue = useMemo( () => ({ - serverSideProps: isServerSide - ? serverSideProps - : window.__TUONO_SSR_PROPS__, router, location, updateLocation: setLocation, }), - [location, router, serverSideProps], + [location, router], ) return ( @@ -111,6 +106,9 @@ export function RouterContextProvider({ ) } +/** + * @warning THIS SHOULD NOT BE EXPOSED TO USERLAND + */ export function useRouterContext(): RouterContextValue { return useContext(RouterContext) } diff --git a/packages/tuono-router/src/components/RouterProvider.tsx b/packages/tuono-router/src/components/RouterProvider.tsx index 5ae11d65..9bf6ce77 100644 --- a/packages/tuono-router/src/components/RouterProvider.tsx +++ b/packages/tuono-router/src/components/RouterProvider.tsx @@ -1,6 +1,6 @@ import type { JSX } from 'react' -import type { ServerProps } from '../types' +import type { ServerInitialLocation } from '../types' import type { Router } from '../router' import { RouterContextProvider } from './RouterContext' @@ -8,19 +8,21 @@ import { Matches } from './Matches' interface RouterProviderProps { router: Router - serverProps?: ServerProps + serverInitialLocation: ServerInitialLocation + serverInitialData: unknown } -/** - * This component is used in the tuono app entry point - */ export function RouterProvider({ router, - serverProps, + serverInitialLocation, + serverInitialData, }: RouterProviderProps): JSX.Element { return ( - - + + ) } diff --git a/packages/tuono-router/src/globals.ts b/packages/tuono-router/src/globals.d.ts similarity index 58% rename from packages/tuono-router/src/globals.ts rename to packages/tuono-router/src/globals.d.ts index cb66ce24..f790a226 100644 --- a/packages/tuono-router/src/globals.ts +++ b/packages/tuono-router/src/globals.d.ts @@ -1,9 +1,7 @@ import type { Router } from './router' -import type { ServerProps } from './types' declare global { interface Window { __TUONO__ROUTER__: Router - __TUONO_SSR_PROPS__?: ServerProps } } diff --git a/packages/tuono-router/src/hooks/useRoute.spec.tsx b/packages/tuono-router/src/hooks/useRoute.spec.ts similarity index 97% rename from packages/tuono-router/src/hooks/useRoute.spec.tsx rename to packages/tuono-router/src/hooks/useRoute.spec.ts index 11868738..2272582d 100644 --- a/packages/tuono-router/src/hooks/useRoute.spec.tsx +++ b/packages/tuono-router/src/hooks/useRoute.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest' import { cleanup } from '@testing-library/react' -import useRoute from './useRoute' +import { useRoute } from './useRoute' describe('useRoute', () => { afterEach(cleanup) diff --git a/packages/tuono-router/src/hooks/useRoute.tsx b/packages/tuono-router/src/hooks/useRoute.ts similarity index 94% rename from packages/tuono-router/src/hooks/useRoute.tsx rename to packages/tuono-router/src/hooks/useRoute.ts index 226c3b0c..b7a65485 100644 --- a/packages/tuono-router/src/hooks/useRoute.tsx +++ b/packages/tuono-router/src/hooks/useRoute.ts @@ -22,9 +22,9 @@ export function sanitizePathname(pathname: string): string { * * File: crates/tuono_lib/src/payload.rs * - * Optimizations should occour on both + * Optimizations should occur on both */ -export default function useRoute(pathname?: string): Route | undefined { +export function useRoute(pathname?: string): Route | undefined { const { router: { routesById }, } = useRouterContext() diff --git a/packages/tuono-router/src/hooks/useRouter.tsx b/packages/tuono-router/src/hooks/useRouter.ts similarity index 100% rename from packages/tuono-router/src/hooks/useRouter.tsx rename to packages/tuono-router/src/hooks/useRouter.ts diff --git a/packages/tuono-router/src/hooks/useServerSideProps.tsx b/packages/tuono-router/src/hooks/useServerPayloadData.ts similarity index 85% rename from packages/tuono-router/src/hooks/useServerSideProps.tsx rename to packages/tuono-router/src/hooks/useServerPayloadData.ts index d5acab64..74aa50e4 100644 --- a/packages/tuono-router/src/hooks/useServerSideProps.tsx +++ b/packages/tuono-router/src/hooks/useServerPayloadData.ts @@ -7,11 +7,6 @@ import { useRouterContext } from '../components/RouterContext' const isServer = typeof document === 'undefined' -interface UseServerSidePropsReturn { - data: TData - isLoading: boolean -} - interface TuonoApi { data?: unknown info: { @@ -25,17 +20,22 @@ const fetchClientSideData = async (): Promise => { return data } +interface UseServerPayloadDataResult { + data: TData + isLoading: boolean +} + /* * Use the props provided by the SSR and dehydrate the * props for client side usage. * * In case is a client fetch the remote data API */ -export function useServerSideProps( +export function useServerPayloadData( route: Route, - // User defined props - serverSideProps: T, -): UseServerSidePropsReturn { + // User defined data + serverInitialData: TServerPayloadData, +): UseServerPayloadDataResult { const isFirstRendering = useRef(true) const { location, updateLocation } = useRouterContext() const [isLoading, setIsLoading] = useState( @@ -47,8 +47,8 @@ export function useServerSideProps( !isFirstRendering.current, ) - const [data, setData] = useState( - (serverSideProps ?? window.__TUONO_SSR_PROPS__?.props) as T, + const [data, setData] = useState( + serverInitialData, ) useEffect(() => { @@ -80,7 +80,7 @@ export function useServerSideProps( updateLocation(parsedLocation) return } - setData(response.data as T) + setData(response.data as TServerPayloadData) } catch (error) { throw Error('Failed loading Server Side Data', { cause: error }) } finally { @@ -95,5 +95,5 @@ export function useServerSideProps( } }, [location.pathname, route.options.hasHandler, updateLocation]) - return { isLoading, data: data as T } + return { isLoading, data: data as TServerPayloadData } } diff --git a/packages/tuono-router/src/index.ts b/packages/tuono-router/src/index.ts index d71d3c9c..08fca9d0 100644 --- a/packages/tuono-router/src/index.ts +++ b/packages/tuono-router/src/index.ts @@ -1,7 +1,7 @@ export { RouterProvider } from './components/RouterProvider' -export { useRouterContext } from './components/RouterContext' export { default as Link } from './components/Link' -export { createRouter, type RouterType } from './router' +export { createRouter } from './router' +export type { RouterInstanceType } from './router' export { createRoute, createRootRoute } from './route' export { useRouter } from './hooks/useRouter' -export type { RouteProps, RouteComponent, ServerProps } from './types' +export type { RouteProps, RouteComponent } from './types' diff --git a/packages/tuono-router/src/router.ts b/packages/tuono-router/src/router.ts index 1033622f..f4b7e5e0 100644 --- a/packages/tuono-router/src/router.ts +++ b/packages/tuono-router/src/router.ts @@ -21,7 +21,8 @@ export function createRouter(options: CreateRouterOptions): Router { return new Router(options) } -export type RouterType = InstanceType +export type RouterInstanceType = InstanceType + export class Router { options?: RouterOptions basePath = '/' diff --git a/packages/tuono-router/src/types.ts b/packages/tuono-router/src/types.ts index 593790f3..94d8b510 100644 --- a/packages/tuono-router/src/types.ts +++ b/packages/tuono-router/src/types.ts @@ -5,26 +5,19 @@ export interface Segment { value: string } -export interface ServerRouterInfo { +/** + * Provided by the rust server and used in the ssr env + * @see tuono {@link ServerPayloadLocation} + */ +export interface ServerInitialLocation { href: string pathname: string searchStr: string } -export interface ServerProps { - router: ServerRouterInfo - props: TProps - jsBundles: Array - cssBundles: Array - mode: 'Dev' | 'Prod' - devServerConfig: { - port: number - host: string - } -} - export interface RouteProps { data: TData + isLoading: boolean children?: ReactNode diff --git a/packages/tuono/src/constants.ts b/packages/tuono/src/constants.ts new file mode 100644 index 00000000..f4999f6b --- /dev/null +++ b/packages/tuono/src/constants.ts @@ -0,0 +1 @@ +export const SERVER_PAYLOAD_VARIABLE_NAME = '__TUONO_SERVER_PAYLOAD__' diff --git a/packages/tuono/src/globals.d.ts b/packages/tuono/src/globals.d.ts new file mode 100644 index 00000000..7811ff93 --- /dev/null +++ b/packages/tuono/src/globals.d.ts @@ -0,0 +1,8 @@ +import type { ServerPayload } from './types' +import type { SERVER_PAYLOAD_VARIABLE_NAME } from './constants' + +declare global { + interface Window { + [SERVER_PAYLOAD_VARIABLE_NAME]?: ServerPayload + } +} diff --git a/packages/tuono/src/shared/DevResources.tsx b/packages/tuono/src/shared/DevResources.tsx index 595954ca..5a80dfe4 100644 --- a/packages/tuono/src/shared/DevResources.tsx +++ b/packages/tuono/src/shared/DevResources.tsx @@ -1,13 +1,13 @@ import type { JSX } from 'react' -import { useRouterContext } from 'tuono-router' + +import { useTuonoContextServerPayload } from './TuonoContext' const VITE_PROXY_PATH = '/vite-server' const DEFAULT_SERVER_CONFIG = { host: 'localhost', port: 3000 } export const DevResources = (): JSX.Element => { - const { serverSideProps } = useRouterContext() - const { host, port } = - serverSideProps?.devServerConfig ?? DEFAULT_SERVER_CONFIG + const { devServerConfig } = useTuonoContextServerPayload() + const { host, port } = devServerConfig ?? DEFAULT_SERVER_CONFIG const viteBaseUrl = `http://${host}:${port}${VITE_PROXY_PATH}` diff --git a/packages/tuono/src/shared/ProdResources.tsx b/packages/tuono/src/shared/ProdResources.tsx index 11328f7c..c28e2976 100644 --- a/packages/tuono/src/shared/ProdResources.tsx +++ b/packages/tuono/src/shared/ProdResources.tsx @@ -1,12 +1,13 @@ import type { JSX } from 'react' -import { useRouterContext } from 'tuono-router' + +import { useTuonoContextServerPayload } from './TuonoContext' export const ProdResources = (): JSX.Element => { - const { serverSideProps } = useRouterContext() + const { cssBundles, jsBundles } = useTuonoContextServerPayload() return ( <> - {serverSideProps?.cssBundles.map((cssHref) => ( + {cssBundles?.map((cssHref) => ( { /> ))} - {serverSideProps?.jsBundles.map((scriptSrc) => ( + {jsBundles?.map((scriptSrc) => ( ))} diff --git a/packages/tuono/src/shared/RouterContextProviderWrapper.tsx b/packages/tuono/src/shared/RouterContextProviderWrapper.tsx new file mode 100644 index 00000000..05071541 --- /dev/null +++ b/packages/tuono/src/shared/RouterContextProviderWrapper.tsx @@ -0,0 +1,30 @@ +import type { JSX } from 'react' +import { RouterProvider } from 'tuono-router' +import type { RouterInstanceType } from 'tuono-router' + +import { useTuonoContextServerPayload } from './TuonoContext' + +interface RouterContextProviderWrapperProps { + router: RouterInstanceType +} + +/** + * This component is needed to get the data from {@link TuonoContext} + * since the provider is also located in {@link TuonoEntryPoint} + * hence the context cannot be accessed directly there + * + * @see https://github.com/tuono-labs/tuono/issues/410 + */ +export function RouterContextProviderWrapper({ + router, +}: RouterContextProviderWrapperProps): JSX.Element { + const serverPayload = useTuonoContextServerPayload() + + return ( + + ) +} diff --git a/packages/tuono/src/shared/TuonoContext.tsx b/packages/tuono/src/shared/TuonoContext.tsx new file mode 100644 index 00000000..118a2cbd --- /dev/null +++ b/packages/tuono/src/shared/TuonoContext.tsx @@ -0,0 +1,50 @@ +import type { JSX, ReactNode } from 'react' +import { createContext, useContext, useMemo } from 'react' + +import type { ServerPayload } from '../types' +import { SERVER_PAYLOAD_VARIABLE_NAME } from '../constants' + +const isServerSide = typeof window === 'undefined' + +interface TuonoContextValue { + serverPayload: ServerPayload +} + +const TuonoContext = createContext({} as TuonoContextValue) + +interface TuonoContextProviderProps { + serverPayload?: ServerPayload + + children: ReactNode +} + +/** + * @warning THIS SHOULD NOT BE EXPOSED TO USERLAND + * + * @see https://github.com/tuono-labs/tuono/issues/410 + */ +export function TuonoContextProvider({ + serverPayload, + children, +}: TuonoContextProviderProps): JSX.Element { + const contextValue: TuonoContextValue = useMemo(() => { + // At least one of these two should be defined + const _serverPayload = ( + isServerSide ? serverPayload : window[SERVER_PAYLOAD_VARIABLE_NAME] + ) as ServerPayload + + return { + // Maybe this logic should be integrated using defaults + serverPayload: _serverPayload, + } + }, [serverPayload]) + + return {children} +} + +/** + * @warning THIS SHOULD NOT BE EXPOSED TO USERLAND + */ +export function useTuonoContextServerPayload(): TuonoContextValue['serverPayload'] { + return useContext(TuonoContext).serverPayload +} diff --git a/packages/tuono/src/shared/TuonoEntryPoint.tsx b/packages/tuono/src/shared/TuonoEntryPoint.tsx index 401f8050..0fd142d7 100644 --- a/packages/tuono/src/shared/TuonoEntryPoint.tsx +++ b/packages/tuono/src/shared/TuonoEntryPoint.tsx @@ -1,18 +1,26 @@ -import { type JSX, StrictMode } from 'react' -import { type RouterType, type ServerProps, RouterProvider } from 'tuono-router' +import { StrictMode } from 'react' +import type { JSX } from 'react' +import type { RouterInstanceType } from 'tuono-router' + +import type { ServerPayload } from '../types' + +import { TuonoContextProvider } from './TuonoContext' +import { RouterContextProviderWrapper } from './RouterContextProviderWrapper' interface TuonoEntryPointProps { - router: RouterType - serverProps?: ServerProps + router: RouterInstanceType + serverPayload?: ServerPayload } export function TuonoEntryPoint({ router, - serverProps, + serverPayload, }: TuonoEntryPointProps): JSX.Element { return ( - + + + ) } diff --git a/packages/tuono/src/shared/TuonoScripts.tsx b/packages/tuono/src/shared/TuonoScripts.tsx index e5109e55..0e10c571 100644 --- a/packages/tuono/src/shared/TuonoScripts.tsx +++ b/packages/tuono/src/shared/TuonoScripts.tsx @@ -1,18 +1,19 @@ import type { JSX } from 'react' -import { useRouterContext } from 'tuono-router' +import { SERVER_PAYLOAD_VARIABLE_NAME } from '../constants' import { DevResources } from './DevResources' import { ProdResources } from './ProdResources' +import { useTuonoContextServerPayload } from './TuonoContext' export function TuonoScripts(): JSX.Element { - const { serverSideProps } = useRouterContext() + const serverPayload = useTuonoContextServerPayload() return ( <> - - {serverSideProps?.mode === 'Dev' && } - {serverSideProps?.mode === 'Prod' && } + + {serverPayload.mode === 'Dev' && } + {serverPayload.mode === 'Prod' && } ) } diff --git a/packages/tuono/src/ssr/server.tsx b/packages/tuono/src/ssr/server.tsx index e771d66f..54b6f961 100644 --- a/packages/tuono/src/ssr/server.tsx +++ b/packages/tuono/src/ssr/server.tsx @@ -42,9 +42,10 @@ import type { ReadableStream } from 'node:stream/web' import { renderToReadableStream } from 'react-dom/server' import { createRouter } from 'tuono-router' -import type { createRoute, ServerProps } from 'tuono-router' +import type { createRoute } from 'tuono-router' import { TuonoEntryPoint } from '../shared/TuonoEntryPoint' +import type { ServerPayload } from '../types' import { streamToString } from './utils' @@ -52,12 +53,12 @@ type RouteTree = ReturnType export function serverSideRendering(routeTree: RouteTree) { return async function render(payload: string | undefined): Promise { - const serverProps = (payload ? JSON.parse(payload) : {}) as ServerProps + const serverPayload = (payload ? JSON.parse(payload) : {}) as ServerPayload const router = createRouter({ routeTree }) // Render the app const stream = await renderToReadableStream( - , + , ) await stream.allReady diff --git a/packages/tuono/src/types.ts b/packages/tuono/src/types.ts index 2d2b5b83..a221d1bf 100644 --- a/packages/tuono/src/types.ts +++ b/packages/tuono/src/types.ts @@ -1,3 +1,51 @@ +/** + * Provided by the rust server and used in the ssr env + * @see tuono-router {@link ServerInitialLocation} + */ +export interface ServerPayloadLocation { + href: string + pathname: string + searchStr: string +} + +/** + * @see crates/tuono_lib/src/payload.rs + */ +export interface ServerPayload { + mode: 'Prod' | 'Dev' + + location: ServerPayloadLocation + + data: TData + + /** Available only on 'Prod' mode */ + jsBundles: Array | null + cssBundles: Array | null + + /** Available only on 'Dev' mode */ + devServerConfig?: { + port: number + host: string + } +} + +/* the above type could be refined with an union like this +( + | { + mode: 'Prod' + jsBundles: Array + cssBundles: Array + } + | { + mode: 'Dev' + devServerConfig: { + port: number + host: string + } + } +) +*/ + export interface TuonoProps { data?: T isLoading: boolean