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

[Fizz] Encode external fizz runtime into chunks eagerly #26752

Merged
merged 1 commit into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,8 @@ export type ResponseState = {
startInlineScript: PrecomputedChunk,
instructions: InstructionState,

// state for outputting CSP nonce
nonce: string | void,

// state for data streaming format
externalRuntimeConfig: BootstrapScriptDescriptor | null,
externalRuntimeScript: null | ExternalRuntimeScript,

// preamble and postamble chunks and state
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
Expand Down Expand Up @@ -196,6 +193,10 @@ export type BootstrapScriptDescriptor = {
src: string,
integrity?: string,
};
export type ExternalRuntimeScript = {
src: string,
chunks: Array<Chunk | PrecomputedChunk>,
};
// Allows us to keep track of what we've already written so we can refer back to it.
// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
// is set, the server will send instructions via data attributes (instead of inline scripts)
Expand All @@ -215,7 +216,7 @@ export function createResponseState(
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
);
const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];
let externalRuntimeDesc = null;
let externalRuntimeScript: null | ExternalRuntimeScript = null;
let streamingFormat = ScriptStreamingFormat;
if (bootstrapScriptContent !== undefined) {
bootstrapChunks.push(
Expand All @@ -233,12 +234,27 @@ export function createResponseState(
if (externalRuntimeConfig !== undefined) {
streamingFormat = DataStreamingFormat;
if (typeof externalRuntimeConfig === 'string') {
externalRuntimeDesc = {
externalRuntimeScript = {
src: externalRuntimeConfig,
integrity: undefined,
chunks: [],
};
pushScriptImpl(externalRuntimeScript.chunks, {
src: externalRuntimeConfig,
async: true,
integrity: undefined,
nonce: nonce,
});
} else {
externalRuntimeDesc = externalRuntimeConfig;
externalRuntimeScript = {
src: externalRuntimeConfig.src,
chunks: [],
};
pushScriptImpl(externalRuntimeScript.chunks, {
src: externalRuntimeConfig.src,
async: true,
integrity: externalRuntimeConfig.integrity,
nonce: nonce,
});
}
}
}
Expand Down Expand Up @@ -307,7 +323,7 @@ export function createResponseState(
streamingFormat,
startInlineScript: inlineScriptWithNonce,
instructions: NothingSent,
externalRuntimeConfig: externalRuntimeDesc,
externalRuntimeScript,
htmlChunks: null,
headChunks: null,
hasBody: false,
Expand Down Expand Up @@ -1293,7 +1309,7 @@ function injectFormReplayingRuntime(responseState: ResponseState): void {
// to emit anything. It's always used.
if (
(responseState.instructions & SentFormReplayingRuntime) === NothingSent &&
(!enableFizzExternalRuntime || !responseState.externalRuntimeConfig)
(!enableFizzExternalRuntime || !responseState.externalRuntimeScript)
) {
responseState.instructions |= SentFormReplayingRuntime;
responseState.bootstrapChunks.unshift(
Expand Down Expand Up @@ -4078,15 +4094,15 @@ export function writePreamble(
if (
enableFizzExternalRuntime &&
!willFlushAllSegments &&
responseState.externalRuntimeConfig
responseState.externalRuntimeScript
) {
// If the root segment is incomplete due to suspended tasks
// (e.g. willFlushAllSegments = false) and we are using data
// streaming format, ensure the external runtime is sent.
// (User code could choose to send this even earlier by calling
// preinit(...), if they know they will suspend).
const {src, integrity} = responseState.externalRuntimeConfig;
internalPreinitScript(resources, src, integrity, responseState.nonce);
const {src, chunks} = responseState.externalRuntimeScript;
internalPreinitScript(resources, src, chunks);
}

const htmlChunks = responseState.htmlChunks;
Expand Down Expand Up @@ -5362,32 +5378,22 @@ function preinit(href: string, options: PreinitOptions): void {
}
}

// This method is trusted. It must only be called from within this codebase and it assumes the arguments
// conform to the types because no user input is being passed in. It also assumes that it is being called as
// part of a work or flush loop and therefore does not need to request Fizz to flush Resources.
function internalPreinitScript(
resources: Resources,
src: string,
integrity: ?string,
nonce: ?string,
chunks: Array<Chunk | PrecomputedChunk>,
): void {
const key = getResourceKey('script', src);
let resource = resources.scriptsMap.get(key);
if (!resource) {
resource = {
type: 'script',
chunks: [],
chunks,
state: NoState,
props: null,
};
resources.scriptsMap.set(key, resource);
resources.scripts.add(resource);
pushScriptImpl(resource.chunks, {
async: true,
src,
integrity,
nonce,
});
}
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type {
BootstrapScriptDescriptor,
ExternalRuntimeScript,
FormatContext,
StreamingFormat,
InstructionState,
Expand Down Expand Up @@ -48,7 +49,7 @@ export type ResponseState = {
streamingFormat: StreamingFormat,
startInlineScript: PrecomputedChunk,
instructions: InstructionState,
externalRuntimeConfig: BootstrapScriptDescriptor | null,
externalRuntimeScript: null | ExternalRuntimeScript,
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
headChunks: null | Array<Chunk | PrecomputedChunk>,
hasBody: boolean,
Expand All @@ -57,7 +58,6 @@ export type ResponseState = {
preloadChunks: Array<Chunk | PrecomputedChunk>,
hoistableChunks: Array<Chunk | PrecomputedChunk>,
stylesToHoist: boolean,
nonce: string | void,
// This is an extra field for the legacy renderer
generateStaticMarkup: boolean,
};
Expand Down Expand Up @@ -86,7 +86,7 @@ export function createResponseState(
streamingFormat: responseState.streamingFormat,
startInlineScript: responseState.startInlineScript,
instructions: responseState.instructions,
externalRuntimeConfig: responseState.externalRuntimeConfig,
externalRuntimeScript: responseState.externalRuntimeScript,
htmlChunks: responseState.htmlChunks,
headChunks: responseState.headChunks,
hasBody: responseState.hasBody,
Expand All @@ -95,7 +95,6 @@ export function createResponseState(
preloadChunks: responseState.preloadChunks,
hoistableChunks: responseState.hoistableChunks,
stylesToHoist: responseState.stylesToHoist,
nonce: responseState.nonce,

// This is an extra field for the legacy renderer
generateStaticMarkup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3767,7 +3767,7 @@ describe('ReactDOMFizzServer', () => {
Array.from(document.head.getElementsByTagName('script')).map(
n => n.outerHTML,
),
).toEqual(['<script async="" src="src-of-external-runtime"></script>']);
).toEqual(['<script src="src-of-external-runtime" async=""></script>']);

expect(getVisibleChildren(document)).toEqual(
<html>
Expand Down