[Static][Fizz] Carry forward bootstrap config to resume if postponing in the shell (#27672)

Previously it was possible to postpone in the shell during a prerender
and then during a resume the bootstrap scripts would not be emitted
leading to no hydration on the client. This change moves the bootstrap
configuration to `ResumableState` where it can be serialized after
postponing if it wasn't flushed as part of the static shell.
This commit is contained in:
Josh Story
2023-11-08 10:43:38 -08:00
committed by GitHub
parent 88b00dec47
commit 7508dcd5cc
11 changed files with 51 additions and 35 deletions

View File

@@ -245,6 +245,13 @@ export type ResumableState = {
nextFormID: number,
streamingFormat: StreamingFormat,
// We carry the bootstrap intializers in resumable state in case we postpone in the shell
// of a prerender. On resume we will reinitialize the bootstrap scripts if necessary.
// If we end up flushing the bootstrap scripts we void these on the resumable state
bootstrapScriptContent?: string | void,
bootstrapScripts?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
bootstrapModules?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
// state for script streaming format, unused if using external runtime / data
instructions: InstructionState,
@@ -349,9 +356,6 @@ const DEFAULT_HEADERS_CAPACITY_IN_UTF16_CODE_UNITS = 2000;
export function createRenderState(
resumableState: ResumableState,
nonce: string | void,
bootstrapScriptContent: string | void,
bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
importMap: ImportMap | void,
onHeaders: void | ((headers: HeadersDescriptor) => void),
@@ -367,6 +371,8 @@ export function createRenderState(
const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];
let externalRuntimeScript: null | ExternalRuntimeScript = null;
const {bootstrapScriptContent, bootstrapScripts, bootstrapModules} =
resumableState;
if (bootstrapScriptContent !== undefined) {
bootstrapChunks.push(
inlineScriptWithNonce,
@@ -612,9 +618,6 @@ export function resumeRenderState(
return createRenderState(
resumableState,
nonce,
// These should have already been flushed in the prerender.
undefined,
undefined,
undefined,
undefined,
undefined,
@@ -625,6 +628,9 @@ export function resumeRenderState(
export function createResumableState(
identifierPrefix: string | void,
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
bootstrapScriptContent: string | void,
bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
): ResumableState {
const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
@@ -638,6 +644,9 @@ export function createResumableState(
idPrefix: idPrefix,
nextFormID: 0,
streamingFormat,
bootstrapScriptContent,
bootstrapScripts,
bootstrapModules,
instructions: NothingSent,
hasBody: false,
hasHtml: false,
@@ -3714,7 +3723,11 @@ export function pushEndInstance(
function writeBootstrap(
destination: Destination,
renderState: RenderState,
resumableState: ResumableState,
): boolean {
resumableState.bootstrapScriptContent = undefined;
resumableState.bootstrapScripts = undefined;
resumableState.bootstrapModules = undefined;
const bootstrapChunks = renderState.bootstrapChunks;
let i = 0;
for (; i < bootstrapChunks.length - 1; i++) {
@@ -3731,8 +3744,9 @@ function writeBootstrap(
export function writeCompletedRoot(
destination: Destination,
renderState: RenderState,
resumableState: ResumableState,
): boolean {
return writeBootstrap(destination, renderState);
return writeBootstrap(destination, renderState, resumableState);
}
// Structural Nodes
@@ -4197,7 +4211,7 @@ export function writeCompletedBoundaryInstruction(
} else {
writeMore = writeChunkAndReturn(destination, completeBoundaryDataEnd);
}
return writeBootstrap(destination, renderState) && writeMore;
return writeBootstrap(destination, renderState, resumableState) && writeMore;
}
const clientRenderScript1Full = stringToPrecomputedChunk(

View File

@@ -92,8 +92,6 @@ export function createRenderState(
undefined,
undefined,
undefined,
undefined,
undefined,
);
return {
// Keep this in sync with ReactFizzConfigDOM

View File

@@ -114,6 +114,9 @@ function renderToReadableStream(
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createRequest(
children,
@@ -121,9 +124,6 @@ function renderToReadableStream(
createRenderState(
resumableState,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
onHeadersImpl,

View File

@@ -104,6 +104,9 @@ function renderToReadableStream(
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createRequest(
children,
@@ -111,9 +114,6 @@ function renderToReadableStream(
createRenderState(
resumableState,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
onHeadersImpl,

View File

@@ -114,6 +114,9 @@ function renderToReadableStream(
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createRequest(
children,
@@ -121,9 +124,6 @@ function renderToReadableStream(
createRenderState(
resumableState,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
onHeadersImpl,

View File

@@ -88,6 +88,9 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
return createRequest(
children,
@@ -95,9 +98,6 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
createRenderState(
resumableState,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
options ? options.onHeaders : undefined,

View File

@@ -94,6 +94,9 @@ function prerender(
const resources = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createPrerenderRequest(
children,
@@ -101,9 +104,6 @@ function prerender(
createRenderState(
resources,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
onHeadersImpl,

View File

@@ -93,6 +93,9 @@ function prerender(
const resources = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createPrerenderRequest(
children,
@@ -100,9 +103,6 @@ function prerender(
createRenderState(
resources,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
onHeadersImpl,

View File

@@ -94,6 +94,9 @@ function prerenderToNodeStream(
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createPrerenderRequest(
children,
@@ -101,9 +104,6 @@ function prerenderToNodeStream(
createRenderState(
resumableState,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.importMap : undefined,
options ? options.onHeaders : undefined,

View File

@@ -53,6 +53,9 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
);
const request = createRequest(
children,
@@ -60,9 +63,6 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
createRenderState(
resumableState,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(undefined),

View File

@@ -3968,7 +3968,11 @@ function flushCompletedQueues(
flushSegment(request, destination, completedRootSegment);
request.completedRootSegment = null;
writeCompletedRoot(destination, request.renderState);
writeCompletedRoot(
destination,
request.renderState,
request.resumableState,
);
} else {
// We haven't flushed the root yet so we don't need to check any other branches further down
return;