packages/runtime/src/renderDocument.tsx (116 lines of code) (raw):

import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import getAppConfig from './appConfig.js'; import { AppContextProvider } from './AppContext.js'; import { DocumentContextProvider } from './Document.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; import getRequestContext from './requestContext.js'; import matchRoutes from './matchRoutes.js'; import getDocumentData from './server/getDocumentData.js'; import getCurrentRoutePath from './utils/getCurrentRoutePath.js'; import { sendResponse, getLocation } from './server/response.js'; import type { AppContext, RouteItem, RouteMatch, RenderOptions, Response, ServerContext, } from './types.js'; interface RenderDocumentOptions { matches: RouteMatch[]; renderOptions: RenderOptions; routes: RouteItem[]; documentData: any; routePath?: string; downgrade?: boolean; } export function renderDocument(options: RenderDocumentOptions): Response { const { matches, renderOptions, routePath = '', downgrade, routes, documentData, }: RenderDocumentOptions = options; const { assetsManifest, app, Document, basename, routesConfig = {}, serverData, } = renderOptions; const appData = null; const appConfig = getAppConfig(app); const loaderData = {}; matches.forEach(async (match) => { const { id } = match.route; const pageConfig = routesConfig[id]; loaderData[id] = { pageConfig: pageConfig ? pageConfig({}) : {}, }; }); const appContext: AppContext = { assetsManifest, appConfig, appData, loaderData, matches, routes, documentOnly: true, renderMode: 'CSR', routePath, basename, downgrade, serverData, documentData, }; const documentContext = { main: null, }; const htmlStr = ReactDOMServer.renderToString( <AppContextProvider value={appContext}> <DocumentContextProvider value={documentContext}> { Document && <Document pagePath={routePath} /> } </DocumentContextProvider> </AppContextProvider>, ); return { value: `<!DOCTYPE html>${htmlStr}`, headers: { 'Content-Type': 'text/html; charset=utf-8', }, statusCode: 200, }; } export async function getDocumentResponse( serverContext: ServerContext, renderOptions: RenderOptions, ): Promise<Response> { const { req } = serverContext; const { app, basename, serverOnlyBasename, createRoutes, documentOnly, renderMode, } = renderOptions; const finalBasename = addLeadingSlash(serverOnlyBasename || basename); const location = getLocation(req.url); const requestContext = getRequestContext(location, serverContext); const appConfig = getAppConfig(app); const routes = createRoutes({ requestContext, renderMode, }); const documentData = await getDocumentData({ loaderConfig: renderOptions.documentDataLoader, requestContext, documentOnly, }); const matches = appConfig?.router?.type === 'hash' ? [] : matchRoutes(routes, location, finalBasename); const routePath = getCurrentRoutePath(matches); return renderDocument({ matches, routePath, routes, renderOptions, documentData }); } export async function renderToResponse( requestContext: ServerContext, renderOptions: RenderOptions, ) { const { req, res } = requestContext; const documentResoponse = await getDocumentResponse(requestContext, renderOptions); sendResponse(req, res, documentResoponse); }