import * as React from 'react' ; import * as ReactDOMServer from 'react-dom/server' ; import { Helmet } from 'react-helmet' ; import { matchPath , StaticRouter , RouteProps } from 'react-router-dom' ; import { Document as DefaultDoc , __AfterContext } from './Document' ; import { After } from './After' ; import { loadInitialProps } from './loadInitialProps' ; import * as utils from './utils' ; import * as url from 'url' ; import { AfterRenderAppOptions , AfterClientData , CtxBase , AsyncRouteProps , RedirectWithStatuCode , ServerAppState , RenderResult , DocumentProps , RenderPageResult , } from './types' ; import { StaticRouterContext } from 'react-router' ; import { getAssets } from './getAssets' ; const modPageFn = function < Props > ( Page ) { return function RenderAfter ( props ) { return < Page { ... props } / > ; } ; } ; /**
 * The customRenderer parameter is a (potentially async) function that can be set to return
 * more than just a rendered string.
 * If present, it will be used instead of the default ReactDOMServer renderToString function.
 * It has to return an object of shape { html, ... }, in which html will be used as the rendered string
 * Other props will be also pass to the Document component
 */ export async function renderApp < T > ( options ) { const { req , res , routes : pureRoutes , assets , document : Document , customRenderer , chunks , scrollToTop = true , ssg = false , ... rest } = options ; const autoScrollRef = { current : scrollToTop } ; const routes = utils . getAllRoutes ( pureRoutes ) ; const ctx = { req , res , scrollToTop : autoScrollRef , ... rest } ; const pathname = url . parse ( req . url ) . pathname as string ; const { match , data : initialData } = await loadInitialProps ( pathname , routes , ctx ) ; const context = { } ; if ( initialData ) { const { redirectTo , statusCode } = initialData as RedirectWithStatuCode ; if ( statusCode ) { context . statusCode = statusCode ; } if ( redirectTo ) { return { html : '' , data : initialData , redirect : redirectTo , statusCode : statusCode || 302 } ; } } const afterData = { scrollToTop : autoScrollRef } ; if ( ssg ) { afterData . ssg = true ; } const data = { initialData , afterData } ; const renderPage = async ( fn = modPageFn ) => { const defaultRenderer = ( element ) => ( { html : ReactDOMServer . renderToString ( element ) } ) ; const renderer = customRenderer || defaultRenderer ; const asyncOrSyncRender = renderer ( < StaticRouter location = { req . url } context = { context } > { fn ( After ) ( { routes , data , transitionBehavior : 'blocking' } ) } < /StaticRouter>); const renderedContent = await asyncOrSyncRender ; const helmet = Helmet . renderStatic ( ) ; return { helmet , ... renderedContent } ; } ; const Doc = Document || DefaultDoc ; const { scripts , styles } = getAssets ( { route : match , chunks } ) ; const reactRouterMatch = matchPath ( req . url , match as RouteProps ) ; const { html , ... docProps } = await Doc . getInitialProps ( { req , res , assets , renderPage , data , helmet : Helmet . renderStatic ( ) , match : reactRouterMatch , scripts , styles , scrollToTop : autoScrollRef , ... rest } ) ; if ( context . url ) { return { html : '' , data : initialData , redirect : context . url , statusCode : context . statusCode || 302 } ; } const props = { assets , data , scripts , styles , match : reactRouterMatch , ... rest , ... docProps , html } ; const doc = ReactDOMServer . renderToStaticMarkup ( < __AfterContext . Provider value = { props } > < Doc { ... props } / > < /__AfterContext.Provider>); const page = `<!doctype html>${ doc }` ; return { html : page , data : initialData , redirect : '' , statusCode : context . statusCode || 200 } ; }