120
const DataStreamingFormat: StreamingFormat = 1;
121
122
export type InstructionState = number;
123
-const NothingSent /* */ = 0b00000;
124
-const SentCompleteSegmentFunction /* */ = 0b00001;
125
-const SentCompleteBoundaryFunction /* */ = 0b00010;
126
-const SentClientRenderFunction /* */ = 0b00100;
127
-const SentStyleInsertionFunction /* */ = 0b01000;
128
-const SentFormReplayingRuntime /* */ = 0b10000;
123
+const NothingSent /* */ = 0b000000;
124
+const SentCompleteSegmentFunction /* */ = 0b000001;
125
+const SentCompleteBoundaryFunction /* */ = 0b000010;
126
+const SentClientRenderFunction /* */ = 0b000100;
127
+const SentStyleInsertionFunction /* */ = 0b001000;
128
+const SentFormReplayingRuntime /* */ = 0b010000;
129
+const SentCompletedShellId /* */ = 0b100000;
130
131
// Per request, global state that is not contextual to the rendering subtree.
132
// This cannot be resumed and therefore should only contain things that are
290
291
const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');
292
292
-const startInlineScript = stringToPrecomputedChunk('<script>');
293
+const startInlineScript = stringToPrecomputedChunk('<script');
294
const endInlineScript = stringToPrecomputedChunk('</script>');
295
296
const startScriptSrc = stringToPrecomputedChunk('<script src="');
297
const startModuleSrc = stringToPrecomputedChunk('<script type="module" src="');
297
-const scriptNonce = stringToPrecomputedChunk('" nonce="');
298
-const scriptIntegirty = stringToPrecomputedChunk('" integrity="');
299
-const scriptCrossOrigin = stringToPrecomputedChunk('" crossorigin="');
300
-const endAsyncScript = stringToPrecomputedChunk('" async=""></script>');
298
+const scriptNonce = stringToPrecomputedChunk(' nonce="');
299
+const scriptIntegirty = stringToPrecomputedChunk(' integrity="');
300
+const scriptCrossOrigin = stringToPrecomputedChunk(' crossorigin="');
301
+const endAsyncScript = stringToPrecomputedChunk(' async=""></script>');
302
303
/**
304
* This escaping function is designed to work with with inline scripts where the entire
368
nonce === undefined
369
? startInlineScript
370
: stringToPrecomputedChunk(
370
- '<script nonce="' + escapeTextForBrowser(nonce) + '">',
371
+ '<script nonce="' + escapeTextForBrowser(nonce) + '"',
372
);
373
const idPrefix = resumableState.idPrefix;
374
377
const {bootstrapScriptContent, bootstrapScripts, bootstrapModules} =
378
resumableState;
379
if (bootstrapScriptContent !== undefined) {
380
+ bootstrapChunks.push(inlineScriptWithNonce);
381
+ pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
382
bootstrapChunks.push(
380
- inlineScriptWithNonce,
383
+ endOfStartTag,
384
stringToChunk(escapeEntireInlineScriptContent(bootstrapScriptContent)),
385
endInlineScript,
386
);
530
bootstrapChunks.push(
531
startScriptSrc,
532
stringToChunk(escapeTextForBrowser(src)),
533
+ attributeEnd,
534
);
535
if (nonce) {
536
bootstrapChunks.push(
537
scriptNonce,
538
stringToChunk(escapeTextForBrowser(nonce)),
539
+ attributeEnd,
540
);
541
}
542
if (typeof integrity === 'string') {
543
bootstrapChunks.push(
544
scriptIntegirty,
545
stringToChunk(escapeTextForBrowser(integrity)),
546
+ attributeEnd,
547
);
548
}
549
if (typeof crossOrigin === 'string') {
550
bootstrapChunks.push(
551
scriptCrossOrigin,
552
stringToChunk(escapeTextForBrowser(crossOrigin)),
553
+ attributeEnd,
554
);
555
}
556
+ pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
557
bootstrapChunks.push(endAsyncScript);
558
}
559
}
587
bootstrapChunks.push(
588
startModuleSrc,
589
stringToChunk(escapeTextForBrowser(src)),
590
+ attributeEnd,
591
);
583
-
592
if (nonce) {
593
bootstrapChunks.push(
594
scriptNonce,
595
stringToChunk(escapeTextForBrowser(nonce)),
596
+ attributeEnd,
597
);
598
}
599
if (typeof integrity === 'string') {
600
bootstrapChunks.push(
601
scriptIntegirty,
602
stringToChunk(escapeTextForBrowser(integrity)),
603
+ attributeEnd,
604
);
605
}
606
if (typeof crossOrigin === 'string') {
607
bootstrapChunks.push(
608
scriptCrossOrigin,
609
stringToChunk(escapeTextForBrowser(crossOrigin)),
610
+ attributeEnd,
611
);
612
}
613
+ pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
614
bootstrapChunks.push(endAsyncScript);
615
}
616
}
1972
(!enableFizzExternalRuntime || !renderState.externalRuntimeScript)
1973
) {
1974
resumableState.instructions |= SentFormReplayingRuntime;
1963
- renderState.bootstrapChunks.unshift(
1964
- renderState.startInlineScript,
1965
- formReplayingRuntimeScript,
1966
- endInlineScript,
1967
- );
1975
+ const preamble = renderState.preamble;
1976
+ const bootstrapChunks = renderState.bootstrapChunks;
1977
+ if (
1978
+ (preamble.htmlChunks || preamble.headChunks) &&
1979
+ bootstrapChunks.length === 0
1980
+ ) {
1981
+ // If we rendered the whole document, then we emitted a rel="expect" that needs a
1982
+ // matching target. If we haven't emitted that yet, we need to include it in this
1983
+ // script tag.
1984
+ bootstrapChunks.push(renderState.startInlineScript);
1985
+ pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
1986
+ bootstrapChunks.push(
1987
+ endOfStartTag,
1988
+ formReplayingRuntimeScript,
1989
+ endInlineScript,
1990
+ );
1991
+ } else {
1992
+ // Otherwise we added to the beginning of the scripts. This will mean that it
1993
+ // appears before the shell ID unfortunately.
1994
+ bootstrapChunks.unshift(
1995
+ renderState.startInlineScript,
1996
+ endOfStartTag,
1997
+ formReplayingRuntimeScript,
1998
+ endInlineScript,
1999
+ );
2000
+ }
2001
}
2002
}
2003
4108
4109
export function writeCompletedRoot(
4110
destination: Destination,
4111
+ resumableState: ResumableState,
4112
renderState: RenderState,
4113
): boolean {
4114
+ const preamble = renderState.preamble;
4115
+ if (preamble.htmlChunks || preamble.headChunks) {
4116
+ // If we rendered the whole document, then we emitted a rel="expect" that needs a
4117
+ // matching target. Normally we use one of the bootstrap scripts for this but if
4118
+ // there are none, then we need to emit a tag to complete the shell.
4119
+ if ((resumableState.instructions & SentCompletedShellId) === NothingSent) {
4120
+ const bootstrapChunks = renderState.bootstrapChunks;
4121
+ bootstrapChunks.push(startChunkForTag('template'));
4122
+ pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
4123
+ bootstrapChunks.push(endOfStartTag, endChunkForTag('template'));
4124
+ }
4125
+ }
4126
return writeBootstrap(destination, renderState);
4127
}
4128
4446
resumableState.streamingFormat === ScriptStreamingFormat;
4447
if (scriptFormat) {
4448
writeChunk(destination, renderState.startInlineScript);
4449
+ writeChunk(destination, endOfStartTag);
4450
if (
4451
(resumableState.instructions & SentCompleteSegmentFunction) ===
4452
NothingSent
4528
resumableState.streamingFormat === ScriptStreamingFormat;
4529
if (scriptFormat) {
4530
writeChunk(destination, renderState.startInlineScript);
4531
+ writeChunk(destination, endOfStartTag);
4532
if (requiresStyleInsertion) {
4533
if (
4534
(resumableState.instructions & SentCompleteBoundaryFunction) ===
4639
resumableState.streamingFormat === ScriptStreamingFormat;
4640
if (scriptFormat) {
4641
writeChunk(destination, renderState.startInlineScript);
4642
+ writeChunk(destination, endOfStartTag);
4643
if (
4644
(resumableState.instructions & SentClientRenderFunction) ===
4645
NothingSent
4982
styleQueue.sheets.clear();
4983
}
4984
4985
+const blockingRenderChunkStart = stringToPrecomputedChunk(
4986
+ '<link rel="expect" href="#',
4987
+);
4988
+const blockingRenderChunkEnd = stringToPrecomputedChunk(
4989
+ '" blocking="render"/>',
4990
+);
4991
+
4992
+function writeBlockingRenderInstruction(
4993
+ destination: Destination,
4994
+ resumableState: ResumableState,
4995
+ renderState: RenderState,
4996
+): void {
4997
+ const idPrefix = resumableState.idPrefix;
4998
+ const shellId = '\u00AB' + idPrefix + 'R\u00BB';
4999
+ writeChunk(destination, blockingRenderChunkStart);
5000
+ writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId)));
5001
+ writeChunk(destination, blockingRenderChunkEnd);
5002
+}
5003
+
5004
+const completedShellIdAttributeStart = stringToPrecomputedChunk(' id="');
5005
+
5006
+function pushCompletedShellIdAttribute(
5007
+ target: Array<Chunk | PrecomputedChunk>,
5008
+ resumableState: ResumableState,
5009
+): void {
5010
+ if ((resumableState.instructions & SentCompletedShellId) !== NothingSent) {
5011
+ return;
5012
+ }
5013
+ resumableState.instructions |= SentCompletedShellId;
5014
+ const idPrefix = resumableState.idPrefix;
5015
+ const shellId = '\u00AB' + idPrefix + 'R\u00BB';
5016
+ target.push(
5017
+ completedShellIdAttributeStart,
5018
+ stringToChunk(escapeTextForBrowser(shellId)),
5019
+ attributeEnd,
5020
+ );
5021
+}
5022
+
5023
// We don't bother reporting backpressure at the moment because we expect to
5024
// flush the entire preamble in a single pass. This probably should be modified
5025
// in the future to be backpressure sensitive but that requires a larger refactor
5029
resumableState: ResumableState,
5030
renderState: RenderState,
5031
willFlushAllSegments: boolean,
5032
+ skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
5033
): void {
5034
// This function must be called exactly once on every request
5035
if (
5115
renderState.bulkPreloads.forEach(flushResource, destination);
5116
renderState.bulkPreloads.clear();
5117
5118
+ if ((htmlChunks || headChunks) && !skipExpect) {
5119
+ // If we have any html or head chunks we know that we're rendering a full document.
5120
+ // A full document should block display until the full shell has downloaded.
5121
+ // Therefore we insert a render blocking instruction referring to the last body
5122
+ // element that's considered part of the shell. We do this after the important loads
5123
+ // have already been emitted so we don't do anything to delay them but early so that
5124
+ // the browser doesn't risk painting too early.
5125
+ writeBlockingRenderInstruction(destination, resumableState, renderState);
5126
+ }
5127
+
5128
// Write embedding hoistableChunks
5129
const hoistableChunks = renderState.hoistableChunks;
5130
for (i = 0; i < hoistableChunks.length; i++) {