[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.
Josh Story committed
Nov 8, 2023 at 10:43 UTC
7508dcd5cc245e376860d65402972e418199264d
11 files changed
+51
-35
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+22
-8
@@ -245,6 +245,13 @@ export type ResumableState = {
245
nextFormID: number,
246
streamingFormat: StreamingFormat,
247
248
+ // We carry the bootstrap intializers in resumable state in case we postpone in the shell
249
+ // of a prerender. On resume we will reinitialize the bootstrap scripts if necessary.
250
+ // If we end up flushing the bootstrap scripts we void these on the resumable state
251
+ bootstrapScriptContent?: string | void,
252
+ bootstrapScripts?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
253
+ bootstrapModules?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
254
+
255
// state for script streaming format, unused if using external runtime / data
256
instructions: InstructionState,
257
@@ -349,9 +356,6 @@ const DEFAULT_HEADERS_CAPACITY_IN_UTF16_CODE_UNITS = 2000;
356
export function createRenderState(
357
resumableState: ResumableState,
358
nonce: string | void,
352
- bootstrapScriptContent: string | void,
353
- bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
354
- bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
359
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
360
importMap: ImportMap | void,
361
onHeaders: void | ((headers: HeadersDescriptor) => void),
@@ -367,6 +371,8 @@ export function createRenderState(
371
372
const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];
373
let externalRuntimeScript: null | ExternalRuntimeScript = null;
374
+ const {bootstrapScriptContent, bootstrapScripts, bootstrapModules} =
375
+ resumableState;
376
if (bootstrapScriptContent !== undefined) {
377
bootstrapChunks.push(
378
inlineScriptWithNonce,
@@ -612,9 +618,6 @@ export function resumeRenderState(
618
return createRenderState(
619
resumableState,
620
nonce,
615
- // These should have already been flushed in the prerender.
616
- undefined,
617
- undefined,
621
undefined,
622
undefined,
623
undefined,
@@ -625,6 +628,9 @@ export function resumeRenderState(
628
export function createResumableState(
629
identifierPrefix: string | void,
630
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
631
+ bootstrapScriptContent: string | void,
632
+ bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
633
+ bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,
634
): ResumableState {
635
const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
636
@@ -638,6 +644,9 @@ export function createResumableState(
644
idPrefix: idPrefix,
645
nextFormID: 0,
646
streamingFormat,
647
+ bootstrapScriptContent,
648
+ bootstrapScripts,
649
+ bootstrapModules,
650
instructions: NothingSent,
651
hasBody: false,
652
hasHtml: false,
@@ -3714,7 +3723,11 @@ export function pushEndInstance(
3723
function writeBootstrap(
3724
destination: Destination,
3725
renderState: RenderState,
3726
+ resumableState: ResumableState,
3727
): boolean {
3728
+ resumableState.bootstrapScriptContent = undefined;
3729
+ resumableState.bootstrapScripts = undefined;
3730
+ resumableState.bootstrapModules = undefined;
3731
const bootstrapChunks = renderState.bootstrapChunks;
3732
let i = 0;
3733
for (; i < bootstrapChunks.length - 1; i++) {
@@ -3731,8 +3744,9 @@ function writeBootstrap(
3744
export function writeCompletedRoot(
3745
destination: Destination,
3746
renderState: RenderState,
3747
+ resumableState: ResumableState,
3748
): boolean {
3735
- return writeBootstrap(destination, renderState);
3749
+ return writeBootstrap(destination, renderState, resumableState);
3750
}
3751
3752
// Structural Nodes
@@ -4197,7 +4211,7 @@ export function writeCompletedBoundaryInstruction(
4211
} else {
4212
writeMore = writeChunkAndReturn(destination, completeBoundaryDataEnd);
4213
}
4200
- return writeBootstrap(destination, renderState) && writeMore;
4214
+ return writeBootstrap(destination, renderState, resumableState) && writeMore;
4215
}
4216
4217
const clientRenderScript1Full = stringToPrecomputedChunk(
packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
-2
@@ -92,8 +92,6 @@ export function createRenderState(
92
undefined,
93
undefined,
94
undefined,
95
- undefined,
96
- undefined,
95
);
96
return {
97
// Keep this in sync with ReactFizzConfigDOM
packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
+3
-3
@@ -114,6 +114,9 @@ function renderToReadableStream(
114
const resumableState = createResumableState(
115
options ? options.identifierPrefix : undefined,
116
options ? options.unstable_externalRuntimeSrc : undefined,
117
+ options ? options.bootstrapScriptContent : undefined,
118
+ options ? options.bootstrapScripts : undefined,
119
+ options ? options.bootstrapModules : undefined,
120
);
121
const request = createRequest(
122
children,
@@ -121,9 +124,6 @@ function renderToReadableStream(
124
createRenderState(
125
resumableState,
126
options ? options.nonce : undefined,
124
- options ? options.bootstrapScriptContent : undefined,
125
- options ? options.bootstrapScripts : undefined,
126
- options ? options.bootstrapModules : undefined,
127
options ? options.unstable_externalRuntimeSrc : undefined,
128
options ? options.importMap : undefined,
129
onHeadersImpl,
packages/react-dom/src/server/ReactDOMFizzServerBun.js
+3
-3
@@ -104,6 +104,9 @@ function renderToReadableStream(
104
const resumableState = createResumableState(
105
options ? options.identifierPrefix : undefined,
106
options ? options.unstable_externalRuntimeSrc : undefined,
107
+ options ? options.bootstrapScriptContent : undefined,
108
+ options ? options.bootstrapScripts : undefined,
109
+ options ? options.bootstrapModules : undefined,
110
);
111
const request = createRequest(
112
children,
@@ -111,9 +114,6 @@ function renderToReadableStream(
114
createRenderState(
115
resumableState,
116
options ? options.nonce : undefined,
114
- options ? options.bootstrapScriptContent : undefined,
115
- options ? options.bootstrapScripts : undefined,
116
- options ? options.bootstrapModules : undefined,
117
options ? options.unstable_externalRuntimeSrc : undefined,
118
options ? options.importMap : undefined,
119
onHeadersImpl,
packages/react-dom/src/server/ReactDOMFizzServerEdge.js
+3
-3
@@ -114,6 +114,9 @@ function renderToReadableStream(
114
const resumableState = createResumableState(
115
options ? options.identifierPrefix : undefined,
116
options ? options.unstable_externalRuntimeSrc : undefined,
117
+ options ? options.bootstrapScriptContent : undefined,
118
+ options ? options.bootstrapScripts : undefined,
119
+ options ? options.bootstrapModules : undefined,
120
);
121
const request = createRequest(
122
children,
@@ -121,9 +124,6 @@ function renderToReadableStream(
124
createRenderState(
125
resumableState,
126
options ? options.nonce : undefined,
124
- options ? options.bootstrapScriptContent : undefined,
125
- options ? options.bootstrapScripts : undefined,
126
- options ? options.bootstrapModules : undefined,
127
options ? options.unstable_externalRuntimeSrc : undefined,
128
options ? options.importMap : undefined,
129
onHeadersImpl,
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+3
-3
@@ -88,6 +88,9 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
88
const resumableState = createResumableState(
89
options ? options.identifierPrefix : undefined,
90
options ? options.unstable_externalRuntimeSrc : undefined,
91
+ options ? options.bootstrapScriptContent : undefined,
92
+ options ? options.bootstrapScripts : undefined,
93
+ options ? options.bootstrapModules : undefined,
94
);
95
return createRequest(
96
children,
@@ -95,9 +98,6 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
98
createRenderState(
99
resumableState,
100
options ? options.nonce : undefined,
98
- options ? options.bootstrapScriptContent : undefined,
99
- options ? options.bootstrapScripts : undefined,
100
- options ? options.bootstrapModules : undefined,
101
options ? options.unstable_externalRuntimeSrc : undefined,
102
options ? options.importMap : undefined,
103
options ? options.onHeaders : undefined,
packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
+3
-3
@@ -94,6 +94,9 @@ function prerender(
94
const resources = createResumableState(
95
options ? options.identifierPrefix : undefined,
96
options ? options.unstable_externalRuntimeSrc : undefined,
97
+ options ? options.bootstrapScriptContent : undefined,
98
+ options ? options.bootstrapScripts : undefined,
99
+ options ? options.bootstrapModules : undefined,
100
);
101
const request = createPrerenderRequest(
102
children,
@@ -101,9 +104,6 @@ function prerender(
104
createRenderState(
105
resources,
106
undefined, // nonce is not compatible with prerendered bootstrap scripts
104
- options ? options.bootstrapScriptContent : undefined,
105
- options ? options.bootstrapScripts : undefined,
106
- options ? options.bootstrapModules : undefined,
107
options ? options.unstable_externalRuntimeSrc : undefined,
108
options ? options.importMap : undefined,
109
onHeadersImpl,
packages/react-dom/src/server/ReactDOMFizzStaticEdge.js
+3
-3
@@ -93,6 +93,9 @@ function prerender(
93
const resources = createResumableState(
94
options ? options.identifierPrefix : undefined,
95
options ? options.unstable_externalRuntimeSrc : undefined,
96
+ options ? options.bootstrapScriptContent : undefined,
97
+ options ? options.bootstrapScripts : undefined,
98
+ options ? options.bootstrapModules : undefined,
99
);
100
const request = createPrerenderRequest(
101
children,
@@ -100,9 +103,6 @@ function prerender(
103
createRenderState(
104
resources,
105
undefined, // nonce is not compatible with prerendered bootstrap scripts
103
- options ? options.bootstrapScriptContent : undefined,
104
- options ? options.bootstrapScripts : undefined,
105
- options ? options.bootstrapModules : undefined,
106
options ? options.unstable_externalRuntimeSrc : undefined,
107
options ? options.importMap : undefined,
108
onHeadersImpl,
packages/react-dom/src/server/ReactDOMFizzStaticNode.js
+3
-3
@@ -94,6 +94,9 @@ function prerenderToNodeStream(
94
const resumableState = createResumableState(
95
options ? options.identifierPrefix : undefined,
96
options ? options.unstable_externalRuntimeSrc : undefined,
97
+ options ? options.bootstrapScriptContent : undefined,
98
+ options ? options.bootstrapScripts : undefined,
99
+ options ? options.bootstrapModules : undefined,
100
);
101
const request = createPrerenderRequest(
102
children,
@@ -101,9 +104,6 @@ function prerenderToNodeStream(
104
createRenderState(
105
resumableState,
106
undefined, // nonce is not compatible with prerendered bootstrap scripts
104
- options ? options.bootstrapScriptContent : undefined,
105
- options ? options.bootstrapScripts : undefined,
106
- options ? options.bootstrapModules : undefined,
107
options ? options.unstable_externalRuntimeSrc : undefined,
108
options ? options.importMap : undefined,
109
options ? options.onHeaders : undefined,
packages/react-server-dom-fb/src/ReactDOMServerFB.js
+3
-3
@@ -53,6 +53,9 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
53
const resumableState = createResumableState(
54
options ? options.identifierPrefix : undefined,
55
options ? options.unstable_externalRuntimeSrc : undefined,
56
+ options ? options.bootstrapScriptContent : undefined,
57
+ options ? options.bootstrapScripts : undefined,
58
+ options ? options.bootstrapModules : undefined,
59
);
60
const request = createRequest(
61
children,
@@ -60,9 +63,6 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
63
createRenderState(
64
resumableState,
65
undefined,
63
- options ? options.bootstrapScriptContent : undefined,
64
- options ? options.bootstrapScripts : undefined,
65
- options ? options.bootstrapModules : undefined,
66
options ? options.unstable_externalRuntimeSrc : undefined,
67
),
68
createRootFormatContext(undefined),
packages/react-server/src/ReactFizzServer.js
+5
-1
@@ -3968,7 +3968,11 @@ function flushCompletedQueues(
3968
3969
flushSegment(request, destination, completedRootSegment);
3970
request.completedRootSegment = null;
3971
- writeCompletedRoot(destination, request.renderState);
3971
+ writeCompletedRoot(
3972
+ destination,
3973
+ request.renderState,
3974
+ request.resumableState,
3975
+ );
3976
} else {
3977
// We haven't flushed the root yet so we don't need to check any other branches further down
3978
return;