2022-05-03 12:37:23 +02:00
|
|
|
import type { IncomingMessage, ServerResponse } from 'http'
|
|
|
|
import type { LoadComponentsReturnType } from './load-components'
|
|
|
|
|
|
|
|
import React from 'react'
|
|
|
|
import { ParsedUrlQuery, stringify as stringifyQuery } from 'querystring'
|
|
|
|
import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'
|
|
|
|
import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server'
|
|
|
|
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
|
|
|
|
import { NextParsedUrlQuery } from './request-meta'
|
|
|
|
import RenderResult from './render-result'
|
|
|
|
import {
|
|
|
|
readableStreamTee,
|
|
|
|
encodeText,
|
|
|
|
decodeText,
|
|
|
|
renderToInitialStream,
|
|
|
|
createBufferedTransformStream,
|
|
|
|
continueFromInitialStream,
|
|
|
|
} from './node-web-streams-helper'
|
|
|
|
import { isDynamicRoute } from '../shared/lib/router/utils'
|
|
|
|
import { tryGetPreviewData } from './api-utils/node'
|
2022-05-18 13:18:28 +02:00
|
|
|
import { htmlEscapeJsonString } from './htmlescape'
|
2022-05-03 12:37:23 +02:00
|
|
|
|
2022-05-07 20:45:40 +02:00
|
|
|
const ReactDOMServer = process.env.__NEXT_REACT_ROOT
|
|
|
|
? require('react-dom/server.browser')
|
|
|
|
: require('react-dom/server')
|
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
export type RenderOptsPartial = {
|
|
|
|
err?: Error | null
|
|
|
|
dev?: boolean
|
|
|
|
serverComponentManifest?: any
|
|
|
|
supportsDynamicHTML?: boolean
|
|
|
|
runtime?: 'nodejs' | 'edge'
|
|
|
|
serverComponents?: boolean
|
|
|
|
reactRoot: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
|
|
|
|
|
2022-06-02 12:02:05 +02:00
|
|
|
function interopDefault(mod: any) {
|
|
|
|
return mod.default || mod
|
|
|
|
}
|
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
const rscCache = new Map()
|
|
|
|
|
|
|
|
// Shadowing check does not work with TypeScript enums
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
|
|
const enum RecordStatus {
|
|
|
|
Pending,
|
|
|
|
Resolved,
|
|
|
|
Rejected,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Record = {
|
|
|
|
status: RecordStatus
|
|
|
|
value: any
|
|
|
|
}
|
|
|
|
|
|
|
|
function createRecordFromThenable(thenable: Promise<any>) {
|
|
|
|
const record: Record = {
|
|
|
|
status: RecordStatus.Pending,
|
|
|
|
value: thenable,
|
|
|
|
}
|
|
|
|
thenable.then(
|
|
|
|
function (value) {
|
|
|
|
if (record.status === RecordStatus.Pending) {
|
|
|
|
const resolvedRecord = record
|
|
|
|
resolvedRecord.status = RecordStatus.Resolved
|
|
|
|
resolvedRecord.value = value
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function (err) {
|
|
|
|
if (record.status === RecordStatus.Pending) {
|
|
|
|
const rejectedRecord = record
|
|
|
|
rejectedRecord.status = RecordStatus.Rejected
|
|
|
|
rejectedRecord.value = err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return record
|
|
|
|
}
|
|
|
|
|
|
|
|
function readRecordValue(record: Record) {
|
|
|
|
if (record.status === RecordStatus.Resolved) {
|
|
|
|
return record.value
|
|
|
|
} else {
|
|
|
|
throw record.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function preloadDataFetchingRecord(
|
|
|
|
map: Map<string, Record>,
|
|
|
|
key: string,
|
|
|
|
fetcher: () => Promise<any> | any
|
|
|
|
) {
|
|
|
|
let record = map.get(key)
|
|
|
|
|
|
|
|
if (!record) {
|
|
|
|
const thenable = fetcher()
|
|
|
|
record = createRecordFromThenable(thenable)
|
|
|
|
map.set(key, record)
|
|
|
|
}
|
|
|
|
|
|
|
|
return record
|
|
|
|
}
|
|
|
|
|
2022-05-18 13:18:28 +02:00
|
|
|
function useFlightResponse(
|
|
|
|
writable: WritableStream<Uint8Array>,
|
2022-06-02 18:14:48 +02:00
|
|
|
cachePrefix: string,
|
2022-06-02 17:43:25 +02:00
|
|
|
req: ReadableStream<Uint8Array>,
|
|
|
|
serverComponentManifest: any
|
2022-05-18 13:18:28 +02:00
|
|
|
) {
|
2022-06-02 18:14:48 +02:00
|
|
|
const id = cachePrefix + ',' + (React as any).useId()
|
2022-05-18 13:18:28 +02:00
|
|
|
let entry = rscCache.get(id)
|
|
|
|
if (!entry) {
|
|
|
|
const [renderStream, forwardStream] = readableStreamTee(req)
|
2022-06-02 17:43:25 +02:00
|
|
|
entry = createFromReadableStream(renderStream, {
|
|
|
|
moduleMap: serverComponentManifest.__ssr_module_mapping__,
|
|
|
|
})
|
2022-05-18 13:18:28 +02:00
|
|
|
rscCache.set(id, entry)
|
|
|
|
|
|
|
|
let bootstrapped = false
|
|
|
|
const forwardReader = forwardStream.getReader()
|
|
|
|
const writer = writable.getWriter()
|
|
|
|
function process() {
|
|
|
|
forwardReader.read().then(({ done, value }) => {
|
|
|
|
if (!bootstrapped) {
|
|
|
|
bootstrapped = true
|
|
|
|
writer.write(
|
|
|
|
encodeText(
|
|
|
|
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
|
|
|
|
JSON.stringify([0, id])
|
|
|
|
)})</script>`
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
2022-05-18 13:18:28 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
if (done) {
|
|
|
|
rscCache.delete(id)
|
|
|
|
writer.close()
|
|
|
|
} else {
|
|
|
|
writer.write(
|
|
|
|
encodeText(
|
|
|
|
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
|
|
|
|
JSON.stringify([1, id, decodeText(value)])
|
|
|
|
)})</script>`
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
2022-05-18 13:18:28 +02:00
|
|
|
)
|
|
|
|
process()
|
|
|
|
}
|
|
|
|
})
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
2022-05-18 13:18:28 +02:00
|
|
|
process()
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
2022-05-18 13:18:28 +02:00
|
|
|
return entry
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create the wrapper component for a Flight stream.
|
|
|
|
function createServerComponentRenderer(
|
|
|
|
ComponentToRender: React.ComponentType,
|
|
|
|
ComponentMod: any,
|
|
|
|
{
|
|
|
|
cachePrefix,
|
|
|
|
transformStream,
|
|
|
|
serverComponentManifest,
|
|
|
|
}: {
|
|
|
|
cachePrefix: string
|
|
|
|
transformStream: TransformStream<Uint8Array, Uint8Array>
|
|
|
|
serverComponentManifest: NonNullable<RenderOpts['serverComponentManifest']>
|
|
|
|
}
|
|
|
|
) {
|
|
|
|
// We need to expose the `__webpack_require__` API globally for
|
|
|
|
// react-server-dom-webpack. This is a hack until we find a better way.
|
2022-05-25 11:46:26 +02:00
|
|
|
if (ComponentMod.__next_app_webpack_require__ || ComponentMod.__next_rsc__) {
|
2022-05-03 12:37:23 +02:00
|
|
|
// @ts-ignore
|
2022-06-02 17:43:25 +02:00
|
|
|
globalThis.__next_require__ =
|
|
|
|
ComponentMod.__next_app_webpack_require__ ||
|
|
|
|
ComponentMod.__next_rsc__.__webpack_require__
|
2022-05-03 12:37:23 +02:00
|
|
|
|
|
|
|
// @ts-ignore
|
2022-05-25 00:04:27 +02:00
|
|
|
globalThis.__next_chunk_load__ = () => Promise.resolve()
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const writable = transformStream.writable
|
|
|
|
const ServerComponentWrapper = (props: any) => {
|
|
|
|
const reqStream: ReadableStream<Uint8Array> = renderToReadableStream(
|
|
|
|
<ComponentToRender {...props} />,
|
|
|
|
serverComponentManifest
|
|
|
|
)
|
|
|
|
|
|
|
|
const response = useFlightResponse(
|
|
|
|
writable,
|
2022-06-02 18:14:48 +02:00
|
|
|
cachePrefix,
|
2022-06-02 17:43:25 +02:00
|
|
|
reqStream,
|
|
|
|
serverComponentManifest
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
|
|
|
const root = response.readRoot()
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
|
|
|
return ServerComponentWrapper
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function renderToHTML(
|
|
|
|
req: IncomingMessage,
|
|
|
|
res: ServerResponse,
|
|
|
|
pathname: string,
|
|
|
|
query: NextParsedUrlQuery,
|
|
|
|
renderOpts: RenderOpts
|
|
|
|
): Promise<RenderResult | null> {
|
|
|
|
// don't modify original query object
|
|
|
|
query = Object.assign({}, query)
|
|
|
|
|
|
|
|
const {
|
|
|
|
buildManifest,
|
|
|
|
serverComponentManifest,
|
|
|
|
supportsDynamicHTML,
|
|
|
|
runtime,
|
|
|
|
ComponentMod,
|
|
|
|
} = renderOpts
|
|
|
|
|
2022-05-09 18:52:08 +02:00
|
|
|
const isFlight = query.__flight__ !== undefined
|
|
|
|
const flightRouterPath = isFlight ? query.__flight_router_path__ : undefined
|
2022-05-10 18:57:14 +02:00
|
|
|
delete query.__flight__
|
|
|
|
delete query.__flight_router_path__
|
2022-05-09 18:52:08 +02:00
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
const hasConcurrentFeatures = !!runtime
|
|
|
|
const pageIsDynamic = isDynamicRoute(pathname)
|
2022-05-10 18:57:14 +02:00
|
|
|
const componentPaths = Object.keys(ComponentMod.components)
|
|
|
|
const components = componentPaths
|
2022-05-08 18:38:43 +02:00
|
|
|
.filter((path) => {
|
|
|
|
// Rendering part of the page is only allowed for flight data
|
2022-05-09 18:52:08 +02:00
|
|
|
if (flightRouterPath) {
|
2022-05-08 18:38:43 +02:00
|
|
|
// TODO: check the actual path
|
|
|
|
const pathLength = path.length
|
2022-06-01 13:52:57 +02:00
|
|
|
return pathLength > flightRouterPath.length
|
2022-05-08 18:38:43 +02:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2022-05-05 22:42:22 +02:00
|
|
|
.sort()
|
2022-05-08 18:38:43 +02:00
|
|
|
.map((path) => {
|
|
|
|
const mod = ComponentMod.components[path]()
|
2022-06-02 12:02:05 +02:00
|
|
|
mod.Component = interopDefault(mod)
|
2022-06-01 13:52:57 +02:00
|
|
|
mod.path = path
|
2022-05-05 22:42:22 +02:00
|
|
|
return mod
|
|
|
|
})
|
2022-05-03 12:37:23 +02:00
|
|
|
|
2022-05-10 18:57:14 +02:00
|
|
|
const isSubtreeRender = components.length < componentPaths.length
|
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
// Reads of this are cached on the `req` object, so this should resolve
|
|
|
|
// instantly. There's no need to pass this data down from a previous
|
|
|
|
// invoke, where we'd have to consider server & serverless.
|
|
|
|
const previewData = tryGetPreviewData(
|
|
|
|
req,
|
|
|
|
res,
|
|
|
|
(renderOpts as any).previewProps
|
|
|
|
)
|
|
|
|
const isPreview = previewData !== false
|
|
|
|
const dataCache = new Map<string, Record>()
|
2022-05-10 18:57:14 +02:00
|
|
|
let WrappedComponent: any
|
2022-05-03 12:37:23 +02:00
|
|
|
|
2022-05-05 22:42:22 +02:00
|
|
|
for (let i = components.length - 1; i >= 0; i--) {
|
2022-05-03 12:37:23 +02:00
|
|
|
const dataCacheKey = i.toString()
|
2022-05-05 22:42:22 +02:00
|
|
|
const layout = components[i]
|
2022-05-03 12:37:23 +02:00
|
|
|
let fetcher: any
|
|
|
|
|
|
|
|
// TODO: pass a shared cache from previous getStaticProps/
|
|
|
|
// getServerSideProps calls?
|
|
|
|
if (layout.getServerSideProps) {
|
|
|
|
fetcher = () =>
|
|
|
|
Promise.resolve(
|
|
|
|
layout.getServerSideProps!({
|
|
|
|
req: req as any,
|
|
|
|
res: res,
|
|
|
|
query,
|
|
|
|
resolvedUrl: (renderOpts as any).resolvedUrl as string,
|
|
|
|
...(pageIsDynamic
|
|
|
|
? { params: (renderOpts as any).params as ParsedUrlQuery }
|
|
|
|
: undefined),
|
|
|
|
...(isPreview
|
|
|
|
? { preview: true, previewData: previewData }
|
|
|
|
: undefined),
|
|
|
|
locales: (renderOpts as any).locales,
|
|
|
|
locale: (renderOpts as any).locale,
|
|
|
|
defaultLocale: (renderOpts as any).defaultLocale,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// TODO: implement layout specific caching for getStaticProps
|
|
|
|
if (layout.getStaticProps) {
|
|
|
|
fetcher = () =>
|
|
|
|
Promise.resolve(
|
|
|
|
layout.getStaticProps!({
|
|
|
|
...(pageIsDynamic
|
|
|
|
? { params: query as ParsedUrlQuery }
|
|
|
|
: undefined),
|
|
|
|
...(isPreview
|
|
|
|
? { preview: true, previewData: previewData }
|
|
|
|
: undefined),
|
|
|
|
locales: (renderOpts as any).locales,
|
|
|
|
locale: (renderOpts as any).locale,
|
|
|
|
defaultLocale: (renderOpts as any).defaultLocale,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fetcher) {
|
|
|
|
// Kick off data fetching before rendering, this ensures there is no waterfall for layouts as
|
|
|
|
// all data fetching required to render the page is kicked off simultaneously
|
|
|
|
preloadDataFetchingRecord(dataCache, dataCacheKey, fetcher)
|
|
|
|
}
|
|
|
|
|
2022-06-01 13:52:57 +02:00
|
|
|
const LayoutRouter = ComponentMod.LayoutRouter
|
2022-06-02 12:02:05 +02:00
|
|
|
const getLoadingMod = ComponentMod.loadingComponents[layout.path]
|
|
|
|
const Loading = getLoadingMod ? interopDefault(getLoadingMod()) : null
|
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
// eslint-disable-next-line no-loop-func
|
|
|
|
const lastComponent = WrappedComponent
|
2022-05-10 18:57:14 +02:00
|
|
|
WrappedComponent = (props: any) => {
|
2022-05-03 12:37:23 +02:00
|
|
|
if (fetcher) {
|
|
|
|
// The data fetching was kicked off before rendering (see above)
|
|
|
|
// if the data was not resolved yet the layout rendering will be suspended
|
|
|
|
const record = preloadDataFetchingRecord(
|
|
|
|
dataCache,
|
|
|
|
dataCacheKey,
|
|
|
|
fetcher
|
|
|
|
)
|
|
|
|
// Result of calling getStaticProps or getServerSideProps. If promise is not resolve yet it will suspend.
|
|
|
|
const recordValue = readRecordValue(record)
|
2022-05-10 18:57:14 +02:00
|
|
|
|
|
|
|
if (props) {
|
|
|
|
props = Object.assign({}, props, recordValue.props)
|
|
|
|
} else {
|
|
|
|
props = recordValue.props
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-01 13:52:57 +02:00
|
|
|
const children = React.createElement(
|
|
|
|
lastComponent || React.Fragment,
|
|
|
|
{},
|
|
|
|
null
|
|
|
|
)
|
2022-06-02 12:02:05 +02:00
|
|
|
|
|
|
|
// TODO: add tests for loading.js
|
|
|
|
const chilrenWithLoading = Loading ? (
|
|
|
|
<React.Suspense fallback={<Loading />}>{children}</React.Suspense>
|
|
|
|
) : (
|
|
|
|
children
|
|
|
|
)
|
|
|
|
|
2022-06-01 13:52:57 +02:00
|
|
|
// Pages don't need to be wrapped in a router
|
2022-05-03 12:37:23 +02:00
|
|
|
return React.createElement(
|
|
|
|
layout.Component,
|
|
|
|
props,
|
2022-06-01 13:52:57 +02:00
|
|
|
layout.path.endsWith('/page') ? (
|
2022-06-02 12:02:05 +02:00
|
|
|
chilrenWithLoading
|
2022-06-01 13:52:57 +02:00
|
|
|
) : (
|
|
|
|
// TODO: only provide the part of the url that is relevant to the layout (see layout-router.client.tsx)
|
|
|
|
<LayoutRouter initialUrl={pathname} layoutPath={layout.path}>
|
2022-06-02 12:02:05 +02:00
|
|
|
{chilrenWithLoading}
|
2022-06-01 13:52:57 +02:00
|
|
|
</LayoutRouter>
|
|
|
|
)
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
// TODO: loading state
|
|
|
|
// const AfterWrap = WrappedComponent
|
|
|
|
// WrappedComponent = () => {
|
|
|
|
// return (
|
|
|
|
// <Suspense fallback={<>Loading...</>}>
|
|
|
|
// <AfterWrap />
|
|
|
|
// </Suspense>
|
|
|
|
// )
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2022-05-29 20:53:12 +02:00
|
|
|
const AppRouter = ComponentMod.AppRouter
|
|
|
|
const WrappedComponentWithRouter = () => {
|
2022-06-01 13:52:57 +02:00
|
|
|
if (flightRouterPath) {
|
|
|
|
return <WrappedComponent />
|
|
|
|
}
|
2022-05-29 20:53:12 +02:00
|
|
|
return (
|
2022-06-01 13:52:57 +02:00
|
|
|
// TODO: verify pathname passed is correct
|
|
|
|
<AppRouter initialUrl={pathname}>
|
2022-05-29 20:53:12 +02:00
|
|
|
<WrappedComponent />
|
|
|
|
</AppRouter>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-05-12 22:52:59 +02:00
|
|
|
const bootstrapScripts = !isSubtreeRender
|
|
|
|
? buildManifest.rootMainFiles.map((src) => '/_next/' + src)
|
2022-05-10 18:57:14 +02:00
|
|
|
: undefined
|
2022-05-03 12:37:23 +02:00
|
|
|
|
|
|
|
let serverComponentsInlinedTransformStream: TransformStream<
|
|
|
|
Uint8Array,
|
|
|
|
Uint8Array
|
|
|
|
> | null = null
|
|
|
|
|
|
|
|
serverComponentsInlinedTransformStream = new TransformStream()
|
|
|
|
const search = stringifyQuery(query)
|
|
|
|
|
2022-05-10 18:57:14 +02:00
|
|
|
const Component = createServerComponentRenderer(
|
2022-05-29 20:53:12 +02:00
|
|
|
WrappedComponentWithRouter,
|
2022-05-10 18:57:14 +02:00
|
|
|
ComponentMod,
|
|
|
|
{
|
|
|
|
cachePrefix: pathname + (search ? `?${search}` : ''),
|
|
|
|
transformStream: serverComponentsInlinedTransformStream,
|
|
|
|
serverComponentManifest,
|
|
|
|
}
|
|
|
|
)
|
2022-05-03 12:37:23 +02:00
|
|
|
|
|
|
|
const jsxStyleRegistry = createStyleRegistry()
|
|
|
|
|
|
|
|
const styledJsxFlushEffect = () => {
|
|
|
|
const styles = jsxStyleRegistry.styles()
|
|
|
|
jsxStyleRegistry.flush()
|
|
|
|
return <>{styles}</>
|
|
|
|
}
|
|
|
|
|
|
|
|
const AppContainer = ({ children }: { children: JSX.Element }) => (
|
2022-05-14 23:20:24 +02:00
|
|
|
<StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
|
|
|
|
2022-05-09 18:52:08 +02:00
|
|
|
const renderServerComponentData = isFlight
|
2022-05-03 12:37:23 +02:00
|
|
|
if (renderServerComponentData) {
|
|
|
|
return new RenderResult(
|
|
|
|
renderToReadableStream(
|
2022-05-29 20:53:12 +02:00
|
|
|
<WrappedComponentWithRouter />,
|
2022-05-03 12:37:23 +02:00
|
|
|
serverComponentManifest
|
|
|
|
).pipeThrough(createBufferedTransformStream())
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rules of Static & Dynamic HTML:
|
|
|
|
*
|
|
|
|
* 1.) We must generate static HTML unless the caller explicitly opts
|
|
|
|
* in to dynamic HTML support.
|
|
|
|
*
|
|
|
|
* 2.) If dynamic HTML support is requested, we must honor that request
|
|
|
|
* or throw an error. It is the sole responsibility of the caller to
|
|
|
|
* ensure they aren't e.g. requesting dynamic HTML for an AMP page.
|
|
|
|
*
|
|
|
|
* These rules help ensure that other existing features like request caching,
|
|
|
|
* coalescing, and ISR continue working as intended.
|
|
|
|
*/
|
|
|
|
const generateStaticHTML = supportsDynamicHTML !== true
|
|
|
|
const bodyResult = async () => {
|
|
|
|
const content = (
|
|
|
|
<AppContainer>
|
2022-05-12 22:52:59 +02:00
|
|
|
<Component />
|
2022-05-03 12:37:23 +02:00
|
|
|
</AppContainer>
|
|
|
|
)
|
|
|
|
|
|
|
|
const renderStream = await renderToInitialStream({
|
|
|
|
ReactDOMServer,
|
|
|
|
element: content,
|
2022-05-12 22:52:59 +02:00
|
|
|
streamOptions: {
|
|
|
|
bootstrapScripts,
|
|
|
|
},
|
2022-05-03 12:37:23 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const flushEffectHandler = (): string => {
|
2022-05-14 23:20:24 +02:00
|
|
|
const flushed = ReactDOMServer.renderToString(styledJsxFlushEffect())
|
2022-05-03 12:37:23 +02:00
|
|
|
return flushed
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle static data for server components.
|
|
|
|
// async function generateStaticFlightDataIfNeeded() {
|
|
|
|
// if (serverComponentsPageDataTransformStream) {
|
|
|
|
// // If it's a server component with the Node.js runtime, we also
|
|
|
|
// // statically generate the page data.
|
|
|
|
// let data = ''
|
|
|
|
|
|
|
|
// const readable = serverComponentsPageDataTransformStream.readable
|
|
|
|
// const reader = readable.getReader()
|
|
|
|
// const textDecoder = new TextDecoder()
|
|
|
|
|
|
|
|
// while (true) {
|
|
|
|
// const { done, value } = await reader.read()
|
|
|
|
// if (done) {
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// data += decodeText(value, textDecoder)
|
|
|
|
// }
|
|
|
|
|
|
|
|
// ;(renderOpts as any).pageData = {
|
|
|
|
// ...(renderOpts as any).pageData,
|
|
|
|
// __flight__: data,
|
|
|
|
// }
|
|
|
|
// return data
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// @TODO: A potential improvement would be to reuse the inlined
|
|
|
|
// data stream, or pass a callback inside as this doesn't need to
|
|
|
|
// be streamed.
|
|
|
|
// Do not use `await` here.
|
|
|
|
// generateStaticFlightDataIfNeeded()
|
|
|
|
|
2022-05-11 15:25:23 +02:00
|
|
|
return await continueFromInitialStream(renderStream, {
|
2022-05-03 12:37:23 +02:00
|
|
|
suffix: '',
|
|
|
|
dataStream: serverComponentsInlinedTransformStream?.readable,
|
|
|
|
generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures,
|
|
|
|
flushEffectHandler,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return new RenderResult(await bodyResult())
|
|
|
|
}
|