Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait for shell resolve with gIP is customized in react 18 #36792

Merged
merged 7 commits into from May 11, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 19 additions & 21 deletions packages/next/server/node-web-streams-helper.ts
@@ -1,5 +1,9 @@
import { nonNullable } from '../lib/non-nullable'

export type ReactReadableStream = ReadableStream<Uint8Array> & {
allReady?: Promise<void> | undefined
}

export function readableStreamTee<T = any>(
readable: ReadableStream<T>
): [ReadableStream<T>, ReadableStream<T>] {
Expand Down Expand Up @@ -138,29 +142,24 @@ export function renderToInitialStream({
}: {
ReactDOMServer: any
element: React.ReactElement
}): Promise<
ReadableStream<Uint8Array> & {
allReady?: Promise<void>
}
> {
}): Promise<ReactReadableStream> {
return ReactDOMServer.renderToReadableStream(element)
}

export async function continueFromInitialStream({
suffix,
dataStream,
generateStaticHTML,
flushEffectHandler,
renderStream,
}: {
suffix?: string
dataStream?: ReadableStream<Uint8Array>
generateStaticHTML: boolean
flushEffectHandler?: () => string
renderStream: ReadableStream<Uint8Array> & {
allReady?: Promise<void>
export async function continueFromInitialStream(
renderStream: ReactReadableStream,
{
suffix,
dataStream,
generateStaticHTML,
flushEffectHandler,
}: {
suffix?: string
dataStream?: ReadableStream<Uint8Array>
generateStaticHTML: boolean
flushEffectHandler?: () => string
}
}): Promise<ReadableStream<Uint8Array>> {
): Promise<ReadableStream<Uint8Array>> {
const closeTag = '</body></html>'
const suffixUnclosed = suffix ? suffix.split(closeTag)[0] : null

Expand Down Expand Up @@ -198,12 +197,11 @@ export async function renderToStream({
flushEffectHandler?: () => string
}): Promise<ReadableStream<Uint8Array>> {
const renderStream = await renderToInitialStream({ ReactDOMServer, element })
return continueFromInitialStream({
return continueFromInitialStream(renderStream, {
suffix,
dataStream,
generateStaticHTML,
flushEffectHandler,
renderStream,
})
}

Expand Down
145 changes: 74 additions & 71 deletions packages/next/server/render.tsx
Expand Up @@ -20,6 +20,7 @@ import type { FontManifest } from './font-utils'
import type { LoadComponentsReturnType, ManifestItem } from './load-components'
import type { GetServerSideProps, GetStaticProps, PreviewData } from '../types'
import type { UnwrapPromise } from '../lib/coalesced-function'
import type { ReactReadableStream } from './node-web-streams-helper'

import React from 'react'
import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'
Expand Down Expand Up @@ -1340,11 +1341,11 @@ export async function renderToHTML(
}
}

async function documentInitialProps(
async function loadDocumentInitialProps(
renderShell?: (
_App: AppType,
_Component: NextComponentType
) => Promise<void>
) => Promise<ReactReadableStream>
) {
const renderPage: RenderPage = (
options: ComponentsEnhancer = {}
Expand Down Expand Up @@ -1373,11 +1374,13 @@ export async function renderToHTML(
enhanceComponents(options, App, Component)

if (renderShell) {
return renderShell(EnhancedApp, EnhancedComponent).then(() => {
// When using concurrent features, we don't have or need the full
// html so it's fine to return nothing here.
return { html: '', head }
})
return renderShell(EnhancedApp, EnhancedComponent).then(
async (stream) => {
await stream.allReady
const html = await streamToString(stream)
return { html, head }
huozhi marked this conversation as resolved.
Show resolved Hide resolved
}
)
}

const html = ReactDOMServer.renderToString(
Expand Down Expand Up @@ -1438,9 +1441,9 @@ export async function renderToHTML(
if (!process.env.__NEXT_REACT_ROOT) {
// Enabling react legacy rendering mode: __NEXT_REACT_ROOT = false
if (Document.getInitialProps) {
const documentInitialPropsRes = await documentInitialProps()
if (documentInitialPropsRes === null) return null
const { docProps, documentCtx } = documentInitialPropsRes
const documentInitialProps = await loadDocumentInitialProps()
if (documentInitialProps === null) return null
const { docProps, documentCtx } = documentInitialProps

return {
bodyResult: (suffix: string) =>
Expand Down Expand Up @@ -1473,97 +1476,97 @@ export async function renderToHTML(
}
} else {
// Enabling react concurrent rendering mode: __NEXT_REACT_ROOT = true
let renderStream: ReadableStream<Uint8Array> & {
allReady?: Promise<void> | undefined
}

const renderShell = async (
EnhancedApp: AppType,
EnhancedComponent: NextComponentType
) => {
const content = renderContent(EnhancedApp, EnhancedComponent)
renderStream = await renderToInitialStream({
return await renderToInitialStream({
ReactDOMServer,
element: content,
})
}

const bodyResult = async (suffix: string) => {
// this must be called inside bodyResult so appWrappers is
// up to date when `wrapApp` is called

const flushEffectHandler = (): string => {
const allFlushEffects = [
styledJsxFlushEffect,
...(flushEffects || []),
]
const flushed = ReactDOMServer.renderToString(
<>
{allFlushEffects.map((flushEffect, i) => (
<React.Fragment key={i}>{flushEffect()}</React.Fragment>
))}
</>
)
return flushed
}
const createBodyResult =
(initialStream: ReactReadableStream) => (suffix: string) => {
// this must be called inside bodyResult so appWrappers is
// up to date when `wrapApp` is called
const flushEffectHandler = (): string => {
const allFlushEffects = [
styledJsxFlushEffect,
...(flushEffects || []),
]
const flushed = ReactDOMServer.renderToString(
<>
{allFlushEffects.map((flushEffect, i) => (
<React.Fragment key={i}>{flushEffect()}</React.Fragment>
))}
</>
)
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
// 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)
}
data += decodeText(value, textDecoder)
}

;(renderOpts as any).pageData = {
...(renderOpts as any).pageData,
__flight__: data,
;(renderOpts as any).pageData = {
...(renderOpts as any).pageData,
__flight__: data,
}
return 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()
return await continueFromInitialStream({
renderStream,
suffix,
dataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML,
flushEffectHandler,
})
}
// @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()
return continueFromInitialStream(initialStream, {
suffix,
dataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML,
flushEffectHandler,
})
}

const hasDocumentGetInitialProps = !(
isServerComponent ||
process.env.NEXT_RUNTIME === 'edge' ||
!Document.getInitialProps
)

let bodyResult: (s: string) => Promise<ReadableStream<Uint8Array>>

// If it has getInitialProps, we will render the shell in `renderPage`.
// Otherwise we do it right now.
let documentInitialPropsRes:
| {}
| Awaited<ReturnType<typeof documentInitialProps>>
| Awaited<ReturnType<typeof loadDocumentInitialProps>>
if (hasDocumentGetInitialProps) {
documentInitialPropsRes = await documentInitialProps(renderShell)
documentInitialPropsRes = await loadDocumentInitialProps(renderShell)
if (documentInitialPropsRes === null) return null
const { docProps } = documentInitialPropsRes as any
bodyResult = createBodyResult(streamFromArray([docProps.html]))
} else {
await renderShell(App, Component)
const stream = await renderShell(App, Component)
bodyResult = createBodyResult(stream)
documentInitialPropsRes = {}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/next/server/view-render.tsx
Expand Up @@ -500,8 +500,7 @@ export async function renderToHTML(
// Do not use `await` here.
// generateStaticFlightDataIfNeeded()

return await continueFromInitialStream({
renderStream,
return await continueFromInitialStream(renderStream, {
suffix: '',
dataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures,
Expand Down
2 changes: 0 additions & 2 deletions test/integration/amphtml-fragment-style/next.config.js

This file was deleted.

9 changes: 2 additions & 7 deletions test/integration/amphtml-fragment-style/test/index.test.js
Expand Up @@ -12,19 +12,14 @@ import {
} from 'next-test-utils'

const appDir = join(__dirname, '../')
const nodeArgs = ['-r', join(appDir, '../../lib/react-17-require-hook.js')]
let appPort
let app

describe('AMP Fragment Styles', () => {
beforeAll(async () => {
await nextBuild(appDir, [], {
nodeArgs,
})
await nextBuild(appDir, [])
appPort = await findPort()
app = await nextStart(appDir, appPort, {
nodeArgs,
})
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))

Expand Down
4 changes: 0 additions & 4 deletions test/integration/styled-jsx-module/app/next.config.js

This file was deleted.

13 changes: 3 additions & 10 deletions test/integration/styled-jsx-module/test/index.test.js
Expand Up @@ -12,7 +12,6 @@ import {
} from 'next-test-utils'

const appDir = join(__dirname, '../app')
const nodeArgs = ['-r', join(appDir, '../../../lib/react-17-require-hook.js')]
let appPort
let app

Expand Down Expand Up @@ -49,13 +48,9 @@ function runTests() {
describe('styled-jsx using in node_modules', () => {
describe('Production', () => {
beforeAll(async () => {
await nextBuild(appDir, undefined, {
nodeArgs,
})
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort, {
nodeArgs,
})
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))

Expand All @@ -65,9 +60,7 @@ describe('styled-jsx using in node_modules', () => {
describe('Development', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort, {
nodeArgs,
})
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))

Expand Down