Skip to content

Commit 8ea96ef

Browse files
authored
[Fizz] Encode external fizz runtime into chunks eagerly (#26752)
in #26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place.
1 parent 491aec5 commit 8ea96ef

File tree

3 files changed

+35
-30
lines changed

3 files changed

+35
-30
lines changed

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,8 @@ export type ResponseState = {
130130
startInlineScript: PrecomputedChunk,
131131
instructions: InstructionState,
132132

133-
// state for outputting CSP nonce
134-
nonce: string | void,
135-
136133
// state for data streaming format
137-
externalRuntimeConfig: BootstrapScriptDescriptor | null,
134+
externalRuntimeScript: null | ExternalRuntimeScript,
138135

139136
// preamble and postamble chunks and state
140137
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
@@ -196,6 +193,10 @@ export type BootstrapScriptDescriptor = {
196193
src: string,
197194
integrity?: string,
198195
};
196+
export type ExternalRuntimeScript = {
197+
src: string,
198+
chunks: Array<Chunk | PrecomputedChunk>,
199+
};
199200
// Allows us to keep track of what we've already written so we can refer back to it.
200201
// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
201202
// is set, the server will send instructions via data attributes (instead of inline scripts)
@@ -215,7 +216,7 @@ export function createResponseState(
215216
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
216217
);
217218
const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];
218-
let externalRuntimeDesc = null;
219+
let externalRuntimeScript: null | ExternalRuntimeScript = null;
219220
let streamingFormat = ScriptStreamingFormat;
220221
if (bootstrapScriptContent !== undefined) {
221222
bootstrapChunks.push(
@@ -233,12 +234,27 @@ export function createResponseState(
233234
if (externalRuntimeConfig !== undefined) {
234235
streamingFormat = DataStreamingFormat;
235236
if (typeof externalRuntimeConfig === 'string') {
236-
externalRuntimeDesc = {
237+
externalRuntimeScript = {
237238
src: externalRuntimeConfig,
238-
integrity: undefined,
239+
chunks: [],
239240
};
241+
pushScriptImpl(externalRuntimeScript.chunks, {
242+
src: externalRuntimeConfig,
243+
async: true,
244+
integrity: undefined,
245+
nonce: nonce,
246+
});
240247
} else {
241-
externalRuntimeDesc = externalRuntimeConfig;
248+
externalRuntimeScript = {
249+
src: externalRuntimeConfig.src,
250+
chunks: [],
251+
};
252+
pushScriptImpl(externalRuntimeScript.chunks, {
253+
src: externalRuntimeConfig.src,
254+
async: true,
255+
integrity: externalRuntimeConfig.integrity,
256+
nonce: nonce,
257+
});
242258
}
243259
}
244260
}
@@ -307,7 +323,7 @@ export function createResponseState(
307323
streamingFormat,
308324
startInlineScript: inlineScriptWithNonce,
309325
instructions: NothingSent,
310-
externalRuntimeConfig: externalRuntimeDesc,
326+
externalRuntimeScript,
311327
htmlChunks: null,
312328
headChunks: null,
313329
hasBody: false,
@@ -1293,7 +1309,7 @@ function injectFormReplayingRuntime(responseState: ResponseState): void {
12931309
// to emit anything. It's always used.
12941310
if (
12951311
(responseState.instructions & SentFormReplayingRuntime) === NothingSent &&
1296-
(!enableFizzExternalRuntime || !responseState.externalRuntimeConfig)
1312+
(!enableFizzExternalRuntime || !responseState.externalRuntimeScript)
12971313
) {
12981314
responseState.instructions |= SentFormReplayingRuntime;
12991315
responseState.bootstrapChunks.unshift(
@@ -4078,15 +4094,15 @@ export function writePreamble(
40784094
if (
40794095
enableFizzExternalRuntime &&
40804096
!willFlushAllSegments &&
4081-
responseState.externalRuntimeConfig
4097+
responseState.externalRuntimeScript
40824098
) {
40834099
// If the root segment is incomplete due to suspended tasks
40844100
// (e.g. willFlushAllSegments = false) and we are using data
40854101
// streaming format, ensure the external runtime is sent.
40864102
// (User code could choose to send this even earlier by calling
40874103
// preinit(...), if they know they will suspend).
4088-
const {src, integrity} = responseState.externalRuntimeConfig;
4089-
internalPreinitScript(resources, src, integrity, responseState.nonce);
4104+
const {src, chunks} = responseState.externalRuntimeScript;
4105+
internalPreinitScript(resources, src, chunks);
40904106
}
40914107

40924108
const htmlChunks = responseState.htmlChunks;
@@ -5362,32 +5378,22 @@ function preinit(href: string, options: PreinitOptions): void {
53625378
}
53635379
}
53645380

5365-
// This method is trusted. It must only be called from within this codebase and it assumes the arguments
5366-
// conform to the types because no user input is being passed in. It also assumes that it is being called as
5367-
// part of a work or flush loop and therefore does not need to request Fizz to flush Resources.
53685381
function internalPreinitScript(
53695382
resources: Resources,
53705383
src: string,
5371-
integrity: ?string,
5372-
nonce: ?string,
5384+
chunks: Array<Chunk | PrecomputedChunk>,
53735385
): void {
53745386
const key = getResourceKey('script', src);
53755387
let resource = resources.scriptsMap.get(key);
53765388
if (!resource) {
53775389
resource = {
53785390
type: 'script',
5379-
chunks: [],
5391+
chunks,
53805392
state: NoState,
53815393
props: null,
53825394
};
53835395
resources.scriptsMap.set(key, resource);
53845396
resources.scripts.add(resource);
5385-
pushScriptImpl(resource.chunks, {
5386-
async: true,
5387-
src,
5388-
integrity,
5389-
nonce,
5390-
});
53915397
}
53925398
return;
53935399
}

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {
1111
BootstrapScriptDescriptor,
12+
ExternalRuntimeScript,
1213
FormatContext,
1314
StreamingFormat,
1415
InstructionState,
@@ -48,7 +49,7 @@ export type ResponseState = {
4849
streamingFormat: StreamingFormat,
4950
startInlineScript: PrecomputedChunk,
5051
instructions: InstructionState,
51-
externalRuntimeConfig: BootstrapScriptDescriptor | null,
52+
externalRuntimeScript: null | ExternalRuntimeScript,
5253
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
5354
headChunks: null | Array<Chunk | PrecomputedChunk>,
5455
hasBody: boolean,
@@ -57,7 +58,6 @@ export type ResponseState = {
5758
preloadChunks: Array<Chunk | PrecomputedChunk>,
5859
hoistableChunks: Array<Chunk | PrecomputedChunk>,
5960
stylesToHoist: boolean,
60-
nonce: string | void,
6161
// This is an extra field for the legacy renderer
6262
generateStaticMarkup: boolean,
6363
};
@@ -86,7 +86,7 @@ export function createResponseState(
8686
streamingFormat: responseState.streamingFormat,
8787
startInlineScript: responseState.startInlineScript,
8888
instructions: responseState.instructions,
89-
externalRuntimeConfig: responseState.externalRuntimeConfig,
89+
externalRuntimeScript: responseState.externalRuntimeScript,
9090
htmlChunks: responseState.htmlChunks,
9191
headChunks: responseState.headChunks,
9292
hasBody: responseState.hasBody,
@@ -95,7 +95,6 @@ export function createResponseState(
9595
preloadChunks: responseState.preloadChunks,
9696
hoistableChunks: responseState.hoistableChunks,
9797
stylesToHoist: responseState.stylesToHoist,
98-
nonce: responseState.nonce,
9998

10099
// This is an extra field for the legacy renderer
101100
generateStaticMarkup,

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3767,7 +3767,7 @@ describe('ReactDOMFizzServer', () => {
37673767
Array.from(document.head.getElementsByTagName('script')).map(
37683768
n => n.outerHTML,
37693769
),
3770-
).toEqual(['<script async="" src="src-of-external-runtime"></script>']);
3770+
).toEqual(['<script src="src-of-external-runtime" async=""></script>']);
37713771

37723772
expect(getVisibleChildren(document)).toEqual(
37733773
<html>

0 commit comments

Comments
 (0)