diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 157990b25f..adfdf69301 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -7,12 +7,10 @@ import { getLoggerWithContext, } from './utilities/log'; import {getErrorMarkup} from './utilities/error'; -import {defer} from './utilities/defer'; import type { - RendererOptions, - StreamerOptions, - HydratorOptions, - ImportGlobEagerOutput, + AssembleHtmlParams, + RunSsrParams, + RunRscParams, ResolvedHydrogenConfig, ResolvedHydrogenRoutes, } from './types'; @@ -83,13 +81,13 @@ export interface RequestHandler { export const renderHydrogen = (App: any) => { const handleRequest: RequestHandler = async function (rawRequest, options) { const { - indexTemplate, - streamableResponse, dev, + nonce, cache, context, - nonce, buyerIpHeader, + indexTemplate, + streamableResponse: nodeResponse, } = options; const request = new ServerComponentRequest(rawRequest); @@ -115,18 +113,13 @@ export const renderHydrogen = (App: any) => { request.ctx.hydrogenConfig = hydrogenConfig; request.ctx.buyerIpHeader = buyerIpHeader; + const response = new ServerComponentResponse(); const log = getLoggerWithContext(request); const sessionApi = hydrogenConfig.session ? hydrogenConfig.session(log) : undefined; - const componentResponse = new ServerComponentResponse(); - request.ctx.session = getSyncSessionApi( - request, - componentResponse, - log, - sessionApi - ); + request.ctx.session = getSyncSessionApi(request, response, log, sessionApi); /** * Inject the cache & context into the module loader so we can pull it out for subrequests. @@ -145,28 +138,40 @@ export const renderHydrogen = (App: any) => { ); } - const isReactHydrationRequest = url.pathname === RSC_PATHNAME; - - if (!isReactHydrationRequest) { - const apiRoute = getApiRoute(url, hydrogenConfig.routes); - - // The API Route might have a default export, making it also a server component - // If it does, only render the API route if the request method is GET - if ( - apiRoute && - (!apiRoute.hasServerComponent || request.method !== 'GET') - ) { - const apiResponse = await renderApiRoute( - request, - apiRoute, - hydrogenConfig.shopify, - sessionApi - ); + const isRSCRequest = url.pathname === RSC_PATHNAME; + const apiRoute = !isRSCRequest && getApiRoute(url, hydrogenConfig.routes); - return apiResponse instanceof Request - ? handleRequest(apiResponse, options) - : apiResponse; - } + // The API Route might have a default export, making it also a server component + // If it does, only render the API route if the request method is GET + if ( + apiRoute && + (!apiRoute.hasServerComponent || request.method !== 'GET') + ) { + const apiResponse = await renderApiRoute( + request, + apiRoute, + hydrogenConfig.shopify, + sessionApi + ); + + return apiResponse instanceof Request + ? handleRequest(apiResponse, options) + : apiResponse; + } + + const state: Record = isRSCRequest + ? parseJSON(url.searchParams.get('state') || '{}') + : {pathname: url.pathname, search: url.search}; + + const rsc = runRSC({App, state, log, request, response}); + + if (isRSCRequest) { + const buffered = await bufferReadableStream(rsc.readable.getReader()); + postRequestTasks('rsc', 200, request, response); + + return new Response(buffered, { + headers: {'cache-control': response.cacheControlHeader}, + }); } const isStreamable = @@ -174,43 +179,21 @@ export const renderHydrogen = (App: any) => { ? hydrogenConfig.enableStreaming(request) : true) && !isBotUA(url, request.headers.get('user-agent')) && - (!!streamableResponse || (await isStreamingSupported())); + (!!nodeResponse || (await isStreamingSupported())); - let template = - typeof indexTemplate === 'function' - ? await indexTemplate(url.toString()) - : indexTemplate; + if (!isStreamable) response.doNotStream(); - if (template && typeof template !== 'string') { - template = template.default; - } - - const params = { - App, + return runSSR({ log, dev, + rsc, nonce, + state, request, - template, - isStreamable, - componentResponse, - response: streamableResponse, - }; - - if (isReactHydrationRequest) { - return hydrate(url, params); - } - - /** - * Stream back real-user responses, but for bots/etc, - * use `render` instead. This is because we need to inject - * things for SEO reasons. - */ - if (isStreamable) { - return stream(url, params); - } - - return render(url, params); + response, + nodeResponse, + template: await getTemplate(indexTemplate, url), + }); }; if (__WORKER__) return handleRequest; @@ -222,123 +205,113 @@ export const renderHydrogen = (App: any) => { )) as RequestHandler; }; -function getApiRoute(url: URL, routes: ResolvedHydrogenRoutes) { - const apiRoutes = getApiRoutes(routes); - return getApiRouteFromURL(url, apiRoutes); -} - -/** - * The render function is responsible for turning the provided `App` into an HTML string, - * and returning any initial state that needs to be hydrated into the client version of the app. - * NOTE: This is currently only used for SEO bots or Worker runtime (where Stream is not yet supported). - */ -async function render( - url: URL, - {App, request, template, componentResponse, nonce, log}: RendererOptions +async function getTemplate( + indexTemplate: + | string + | ((url: string) => Promise), + url: URL ) { - const state = {pathname: url.pathname, search: url.search}; - - const {AppSSR, rscReadable} = buildAppSSR( - { - App, - log, - state, - request, - response: componentResponse, - }, - {template} - ); + let template = + typeof indexTemplate === 'function' + ? await indexTemplate(url.toString()) + : indexTemplate; - function onErrorShell(error: Error) { - log.error(error); - componentResponse.writeHead({status: 500}); - return template; + if (template && typeof template !== 'string') { + template = template.default; } - let [html, flight] = await Promise.all([ - renderToBufferedString(AppSSR, {log, nonce}).catch(onErrorShell), - bufferReadableStream(rscReadable.getReader()).catch(() => null), - ]); - - const {headers, status, statusText} = getResponseOptions(componentResponse); + return template; +} - /** - * TODO: Also add `Vary` headers for `accept-language` and any other keys - * we want to shard our full-page cache for all Hydrogen storefronts. - */ - headers.set('cache-control', componentResponse.cacheControlHeader); - headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE); +function getApiRoute(url: URL, routes: ResolvedHydrogenRoutes) { + const apiRoutes = getApiRoutes(routes); + return getApiRouteFromURL(url, apiRoutes); +} - html = applyHtmlHead(html, request.ctx.head, template); +function assembleHtml({ + ssrHtml, + rscPayload, + request, + template, +}: AssembleHtmlParams) { + let html = applyHtmlHead(ssrHtml, request.ctx.head, template); - if (flight) { + if (rscPayload) { html = html.replace( '', - () => flightContainer(flight as string) + '' + // This must be a function to avoid replacing + // special patterns like `$1` in `String.replace`. + () => flightContainer(rscPayload) + '' ); } - postRequestTasks('ssr', status, request, componentResponse); - - return new Response(html, { - status, - statusText, - headers, - }); + return html; } /** - * Stream a response to the client. NOTE: This omits custom `` - * information, so this method should not be used by crawlers. + * Run the SSR/Fizz part of the App. If streaming is disabled, + * this buffers the output and applies SEO enhancements. */ -async function stream( - url: URL, - { - App, - request, - response, - componentResponse, - template, - nonce, - dev, - log, - }: StreamerOptions -) { - const state = {pathname: url.pathname, search: url.search}; - log.trace('start stream'); +async function runSSR({ + rsc, + state, + request, + response, + nodeResponse, + template, + nonce, + dev, + log, +}: RunSsrParams) { + let ssrDidError: Error | undefined; + const didError = () => rsc.didError() ?? ssrDidError; + + const [rscReadableForFizz, rscReadableForFlight] = rsc.readable.tee(); + const rscResponse = createFromReadableStream(rscReadableForFizz); + const RscConsumer = () => rscResponse.readRoot(); const {noScriptTemplate, bootstrapScripts, bootstrapModules} = stripScriptsFromTemplate(template); - const {AppSSR, rscReadable, rscDidError} = buildAppSSR( - { - App, - log, - state, - request, - response: componentResponse, - }, - {template: noScriptTemplate} + const AppSSR = ( + + + {}} + > + + + + + + + + + + + ); - const rscToScriptTagReadable = new ReadableStream({ - start(controller) { - log.trace('rsc start chunks'); - const encoder = new TextEncoder(); - bufferReadableStream(rscReadable.getReader(), (chunk) => { - const metaTag = flightContainer(chunk); - controller.enqueue(encoder.encode(metaTag)); - }).then(() => { - log.trace('rsc finish chunks'); - return controller.close(); - }); - }, - }); - - let ssrDidError: Error | undefined; + log.trace('start ssr'); + + const rscReadable = response.canStream() + ? new ReadableStream({ + start(controller) { + log.trace('rsc start chunks'); + const encoder = new TextEncoder(); + bufferReadableStream(rscReadableForFlight.getReader(), (chunk) => { + const metaTag = flightContainer(chunk); + controller.enqueue(encoder.encode(metaTag)); + }).then(() => { + log.trace('rsc finish chunks'); + return controller.close(); + }); + }, + }) + : rscReadableForFlight; if (__WORKER__) { - const onCompleteAll = defer(); const encoder = new TextEncoder(); const transform = new TransformStream(); const writable = transform.writable.getWriter(); @@ -373,106 +346,94 @@ async function stream( ); } - log.trace('worker ready to stream'); - - ssrReadable.allReady.then(() => { - log.trace('worker complete stream'); - onCompleteAll.resolve(true); - }); + if (response.canStream()) log.trace('worker ready to stream'); + ssrReadable.allReady.then(() => log.trace('worker complete ssr')); - /* eslint-disable no-inner-declarations */ - function prepareForStreaming(flush: boolean) { - Object.assign( - responseOptions, - getResponseOptions(componentResponse, rscDidError ?? ssrDidError) - ); + const prepareForStreaming = () => { + Object.assign(responseOptions, getResponseOptions(response, didError())); /** * TODO: This assumes `response.cache()` has been called _before_ any * queries which might be caught behind Suspense. Clarify this or add * additional checks downstream? */ - responseOptions.headers.set( - 'cache-control', - componentResponse.cacheControlHeader - ); + /** + * TODO: Also add `Vary` headers for `accept-language` and any other keys + * we want to shard our full-page cache for all Hydrogen storefronts. + */ + responseOptions.headers.set('cache-control', response.cacheControlHeader); if (isRedirect(responseOptions)) { return false; } - if (flush) { - responseOptions.headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE); - writable.write(encoder.encode(DOCTYPE)); - - if (rscDidError ?? ssrDidError) { - // This error was delayed until the headers were properly sent. - writable.write( - encoder.encode( - getErrorMarkup((rscDidError ?? ssrDidError) as Error) - ) - ); - } + responseOptions.headers.set(CONTENT_TYPE, HTML_CONTENT_TYPE); + writable.write(encoder.encode(DOCTYPE)); - return true; + const error = didError(); + if (error) { + // This error was delayed until the headers were properly sent. + writable.write(encoder.encode(dev ? getErrorMarkup(error) : template)); } - } - /* eslint-enable no-inner-declarations */ - const shouldReturnApp = - prepareForStreaming(componentResponse.canStream()) ?? - (await onCompleteAll.promise.then(prepareForStreaming)); + return true; + }; + + const shouldFlushBody = response.canStream() + ? prepareForStreaming() + : await ssrReadable.allReady.then(prepareForStreaming); - if (shouldReturnApp) { + if (shouldFlushBody) { let bufferedSsr = ''; let isPendingSsrWrite = false; + const writingSSR = bufferReadableStream( ssrReadable.getReader(), - (chunk) => { - bufferedSsr += chunk; - - if (!isPendingSsrWrite) { - isPendingSsrWrite = true; - setTimeout(() => { - isPendingSsrWrite = false; - // React can write fractional chunks synchronously. - // This timeout ensures we only write full HTML tags - // in order to allow RSC writing concurrently. - if (bufferedSsr) { - writable.write(encoder.encode(bufferedSsr)); - bufferedSsr = ''; + response.canStream() + ? (chunk) => { + bufferedSsr += chunk; + + if (!isPendingSsrWrite) { + isPendingSsrWrite = true; + setTimeout(() => { + isPendingSsrWrite = false; + // React can write fractional chunks synchronously. + // This timeout ensures we only write full HTML tags + // in order to allow RSC writing concurrently. + if (bufferedSsr) { + writable.write(encoder.encode(bufferedSsr)); + bufferedSsr = ''; + } + }, 0); } - }, 0); - } - } + } + : undefined ); const writingRSC = bufferReadableStream( - rscToScriptTagReadable.getReader(), - (scriptTag) => writable.write(encoder.encode(scriptTag)) + rscReadable.getReader(), + response.canStream() + ? (scriptTag) => writable.write(encoder.encode(scriptTag)) + : undefined ); - Promise.all([writingSSR, writingRSC]).then(() => { + Promise.all([writingSSR, writingRSC]).then(([ssrHtml, rscPayload]) => { + if (!response.canStream()) { + const html = assembleHtml({ssrHtml, rscPayload, request, template}); + writable.write(encoder.encode(html)); + } + // Last SSR write might be pending, delay closing the writable one tick setTimeout(() => writable.close(), 0); - postRequestTasks( - 'str', - responseOptions.status, - request, - componentResponse - ); + postRequestTasks('str', responseOptions.status, request, response); }); } else { + // Redirects do not write body writable.close(); - postRequestTasks( - 'str', - responseOptions.status, - request, - componentResponse - ); + postRequestTasks('str', responseOptions.status, request, response); } - if (await isStreamingSupported()) { + if (response.canStream()) { return new Response(transform.readable, responseOptions); } @@ -481,116 +442,101 @@ async function stream( ); return new Response(bufferedBody, responseOptions); - } else if (response) { + } else if (nodeResponse) { const {pipe} = ssrRenderToPipeableStream(AppSSR, { nonce, bootstrapScripts, bootstrapModules, onShellReady() { log.trace('node ready to stream'); + /** * TODO: This assumes `response.cache()` has been called _before_ any * queries which might be caught behind Suspense. Clarify this or add * additional checks downstream? */ - response.setHeader( - 'cache-control', - componentResponse.cacheControlHeader - ); - - writeHeadToServerResponse( - response, - componentResponse, - log, - rscDidError ?? ssrDidError - ); + writeHeadToNodeResponse(nodeResponse, response, log, didError()); - if (isRedirect(response)) { + if (isRedirect(nodeResponse)) { // Return redirects early without further rendering/streaming - return response.end(); + return nodeResponse.end(); } - if (!componentResponse.canStream()) return; + if (!response.canStream()) return; - startWritingHtmlToServerResponse( - response, - dev ? rscDidError ?? ssrDidError : undefined - ); + startWritingToNodeResponse(nodeResponse, dev ? didError() : undefined); setTimeout(() => { log.trace('node pipe response'); - pipe(response); + pipe(nodeResponse); }, 0); - bufferReadableStream(rscToScriptTagReadable.getReader(), (chunk) => { + bufferReadableStream(rscReadable.getReader(), (chunk) => { log.trace('rsc chunk'); - return response.write(chunk); + return nodeResponse.write(chunk); }); }, - onAllReady() { - log.trace('node complete stream'); - - if (componentResponse.canStream() || response.writableEnded) { - postRequestTasks( - 'str', - response.statusCode, - request, - componentResponse - ); + async onAllReady() { + log.trace('node complete ssr'); + + if (response.canStream() || nodeResponse.writableEnded) { + postRequestTasks('str', nodeResponse.statusCode, request, response); + return; } - writeHeadToServerResponse( - response, - componentResponse, - log, - rscDidError ?? ssrDidError - ); + writeHeadToNodeResponse(nodeResponse, response, log, didError()); - postRequestTasks( - 'str', - response.statusCode, - request, - componentResponse - ); - - if (isRedirect(response)) { + if (isRedirect(nodeResponse)) { // Redirects found after any async code - return response.end(); + return nodeResponse.end(); } - startWritingHtmlToServerResponse( - response, - dev ? rscDidError ?? ssrDidError : undefined + const bufferedResponse = await createNodeWriter(); + const bufferedRscPromise = bufferReadableStream( + rscReadable.getReader() ); - bufferReadableStream(rscToScriptTagReadable.getReader()).then( - (scriptTags) => { - // Piping ends the response so script tags - // must be written before that. - response.write(scriptTags); - pipe(response); + let ssrHtml = ''; + bufferedResponse.on('data', (chunk) => (ssrHtml += chunk.toString())); + bufferedResponse.once('error', (error) => (ssrDidError = error)); + bufferedResponse.once('end', async () => { + const rscPayload = await bufferedRscPromise; + + const error = didError(); + startWritingToNodeResponse(nodeResponse, dev ? error : undefined); + + let html = template; + + if (!error) { + html = assembleHtml({ssrHtml, rscPayload, request, template}); + postRequestTasks('ssr', nodeResponse.statusCode, request, response); } - ); + + nodeResponse.write(html); + nodeResponse.end(); + }); + + pipe(bufferedResponse); }, onShellError(error: any) { log.error(error); - if (!response.writableEnded) { - writeHeadToServerResponse(response, componentResponse, log, error); - startWritingHtmlToServerResponse(response, dev ? error : undefined); + if (!nodeResponse.writableEnded) { + writeHeadToNodeResponse(nodeResponse, response, log, error); + startWritingToNodeResponse(nodeResponse, dev ? error : undefined); - response.write(template); - response.end(); + nodeResponse.write(template); + nodeResponse.end(); } }, onError(error: any) { ssrDidError = error; - if (dev && response.headersSent) { + if (dev && nodeResponse.headersSent) { // Calling write would flush headers automatically. // Delay this error until headers are properly sent. - response.write(getErrorMarkup(error)); + nodeResponse.write(getErrorMarkup(error)); } log.error(error); @@ -600,68 +546,10 @@ async function stream( } /** - * Stream a hydration response to the client. + * Run the RSC/Flight part of the App */ -async function hydrate( - url: URL, - { - App, - log, - request, - response, - isStreamable, - componentResponse, - }: HydratorOptions -) { - const state = parseJSON(url.searchParams.get('state') || '{}'); - - const {AppRSC} = buildAppRSC({ - App, - log, - state, - request, - response: componentResponse, - }); - - const rscReadable = rscRenderToReadableStream(AppRSC, { - onError(e) { - log.error(e); - }, - }); - - const bufferedBody = await bufferReadableStream(rscReadable.getReader()); - - postRequestTasks('rsc', 200, request, componentResponse); - - return new Response(bufferedBody, { - headers: { - 'cache-control': componentResponse.cacheControlHeader, - }, - }); -} - -type SharedServerProps = { - state?: object | null; - request: ServerComponentRequest; - response: ServerComponentResponse; - log: Logger; -}; - -type BuildAppOptions = { - App: React.JSXElementConstructor; -} & SharedServerProps; - -export type AppProps = SharedServerProps & { - routes?: ImportGlobEagerOutput; -}; - -function buildAppRSC({App, log, state, request, response}: BuildAppOptions) { - const hydrogenServerProps = {request, response, log}; - const serverProps = { - ...state, - ...hydrogenServerProps, - }; - +function runRSC({App, state, log, request, response}: RunRscParams) { + const serverProps = {...state, request, response, log}; request.ctx.router.serverProps = serverProps; const AppRSC = ( @@ -675,57 +563,15 @@ function buildAppRSC({App, log, state, request, response}: BuildAppOptions) { ); - return {AppRSC}; -} - -function buildAppSSR( - {App, state, request, response, log}: BuildAppOptions, - htmlOptions: Omit[0], 'children'> & {} -) { - const {AppRSC} = buildAppRSC({ - App, - log, - state, - request, - response, + let rscDidError: Error; + const rscReadable = rscRenderToReadableStream(AppRSC, { + onError(e) { + rscDidError = e; + log.error(e); + }, }); - let rscDidError; - - const [rscReadableForFizz, rscReadableForFlight] = rscRenderToReadableStream( - AppRSC, - { - onError(e) { - rscDidError = e; - log.error(e); - }, - } - ).tee(); - - const rscResponse = createFromReadableStream(rscReadableForFizz); - const RscConsumer = () => rscResponse.readRoot(); - - const AppSSR = ( - - - {}} - > - - - - - - - - - - - - ); - - return {AppSSR, rscReadable: rscReadableForFlight, rscDidError}; + return {readable: rscReadable, didError: () => rscDidError}; } function PreloadQueries({ @@ -741,64 +587,20 @@ function PreloadQueries({ return <>{children}; } -async function renderToBufferedString( - ReactApp: JSX.Element, - {log, nonce}: {log: Logger; nonce?: string} -): Promise { - if (__WORKER__) { - const ssrReadable = await ssrRenderToReadableStream(ReactApp, { - nonce, - onError: (error) => log.error(error), - }); - - /** - * We want to wait until `allReady` resolves before fetching the - * stream body. Otherwise, React 18's streaming JS script/template tags - * will be included in the output and cause issues when loading - * the Client Components in the browser. - */ - await ssrReadable.allReady; - - return bufferReadableStream(ssrReadable.getReader()); - } else { - const writer = await createNodeWriter(); - - return new Promise((resolve, reject) => { - const {pipe} = ssrRenderToPipeableStream(ReactApp, { - nonce, - /** - * When hydrating, we have to wait until `onCompleteAll` to avoid having - * `template` and `script` tags inserted and rendered as part of the hydration response. - */ - onAllReady() { - let data = ''; - writer.on('data', (chunk) => (data += chunk.toString())); - writer.once('error', reject); - writer.once('end', () => resolve(data)); - // Tell React to start writing to the writer - pipe(writer); - }, - onShellError: reject, - onError: (error) => log.error(error), - }); - }); - } -} - export default renderHydrogen; -function startWritingHtmlToServerResponse( - response: ServerResponse, +function startWritingToNodeResponse( + nodeResponse: ServerResponse, error?: Error ) { - if (!response.headersSent) { - response.setHeader(CONTENT_TYPE, HTML_CONTENT_TYPE); - response.write(DOCTYPE); + if (!nodeResponse.headersSent) { + nodeResponse.setHeader(CONTENT_TYPE, HTML_CONTENT_TYPE); + nodeResponse.write(DOCTYPE); } if (error) { // This error was delayed until the headers were properly sent. - response.write(getErrorMarkup(error)); + nodeResponse.write(getErrorMarkup(error)); } } @@ -829,26 +631,33 @@ function getResponseOptions( return responseInit; } -function writeHeadToServerResponse( - response: ServerResponse, - serverComponentResponse: ServerComponentResponse, +function writeHeadToNodeResponse( + nodeResponse: ServerResponse, + componentResponse: ServerComponentResponse, log: Logger, error?: Error ) { - if (response.headersSent) return; - log.trace('writeHeadToServerResponse'); + if (nodeResponse.headersSent) return; + log.trace('writeHeadToNodeResponse'); + + /** + * TODO: Also add `Vary` headers for `accept-language` and any other keys + * we want to shard our full-page cache for all Hydrogen storefronts. + */ + nodeResponse.setHeader('cache-control', componentResponse.cacheControlHeader); const {headers, status, statusText} = getResponseOptions( - serverComponentResponse, + componentResponse, error ); - response.statusCode = status; + + nodeResponse.statusCode = status; if (statusText) { - response.statusMessage = statusText; + nodeResponse.statusMessage = statusText; } - setServerHeaders(headers, response); + setNodeHeaders(headers, nodeResponse); } function isRedirect(response: {status?: number; statusCode?: number}) { @@ -874,10 +683,10 @@ function postRequestTasks( type: RenderType, status: number, request: ServerComponentRequest, - componentResponse: ServerComponentResponse + response: ServerComponentResponse ) { logServerResponse(type, request, status); - logCacheControlHeaders(type, request, componentResponse); + logCacheControlHeaders(type, request, response); logQueryTimings(type, request); request.savePreloadQueries(); } @@ -893,7 +702,7 @@ function handleFetchResponseInNode( fetchResponsePromise.then((response) => { if (!response) return; - setServerHeaders(response.headers, nodeResponse); + setNodeHeaders(response.headers, nodeResponse); nodeResponse.statusCode = response.status; @@ -909,7 +718,7 @@ function handleFetchResponseInNode( } // From fetch Headers to Node Response -function setServerHeaders(headers: Headers, nodeResponse: ServerResponse) { +function setNodeHeaders(headers: Headers, nodeResponse: ServerResponse) { // Headers.raw is only implemented in node-fetch, which is used by Hydrogen in dev and prod. // It is the only way for now to access `set-cookie` header as an array. // https://github.com/Shopify/hydrogen/issues/1228 diff --git a/packages/hydrogen/src/types.ts b/packages/hydrogen/src/types.ts index c0286178b8..427d23587a 100644 --- a/packages/hydrogen/src/types.ts +++ b/packages/hydrogen/src/types.ts @@ -10,29 +10,33 @@ import type { } from './storefront-api-types'; import type {SessionStorageAdapter} from './foundation/session/session'; -type CommonOptions = { - App: any; +export type AssembleHtmlParams = { + ssrHtml: string; + rscPayload?: string; routes?: ImportGlobEagerOutput; request: ServerComponentRequest; - componentResponse: ServerComponentResponse; - log: Logger; - dev?: boolean; -}; - -export type RendererOptions = CommonOptions & { template: string; - nonce?: string; }; -export type StreamerOptions = CommonOptions & { - response?: ServerResponse; +export type RunSsrParams = { + state: Record; + rsc: {readable: ReadableStream; didError: () => Error | undefined}; + routes?: ImportGlobEagerOutput; + request: ServerComponentRequest; + response: ServerComponentResponse; + log: Logger; + dev?: boolean; template: string; nonce?: string; + nodeResponse?: ServerResponse; }; -export type HydratorOptions = CommonOptions & { - response?: ServerResponse; - isStreamable: boolean; +export type RunRscParams = { + App: any; + state: Record; + log: Logger; + request: ServerComponentRequest; + response: ServerComponentResponse; }; export type ShopifyConfig = {