Skip to content

Commit 0881b4b

Browse files
committed
Add non-execution format, runtime, and unit tests
1 parent 6d6c37a commit 0881b4b

File tree

5 files changed

+573
-121
lines changed

5 files changed

+573
-121
lines changed

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

Lines changed: 167 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -106,26 +106,33 @@ export const isPrimaryRenderer = true;
106106
// Per response, global state that is not contextual to the rendering subtree.
107107
export type ResponseState = {
108108
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
109-
startInlineScript: PrecomputedChunk,
110109
placeholderPrefix: PrecomputedChunk,
111110
segmentPrefix: PrecomputedChunk,
112111
boundaryPrefix: string,
113112
idPrefix: string,
114113
nextSuspenseID: number,
114+
streamingFormat: 'SCRIPT' | 'DATA',
115+
// state for script streaming format, unused if using external runtime / data
116+
startInlineScript: PrecomputedChunk,
115117
sentCompleteSegmentFunction: boolean,
116118
sentCompleteBoundaryFunction: boolean,
117119
sentClientRenderFunction: boolean,
118-
sentStyleInsertionFunction: boolean, // We allow the legacy renderer to extend this object.
120+
sentStyleInsertionFunction: boolean,
121+
// We allow the legacy renderer to extend this object.
119122
...
120123
};
121124

125+
const dataElementQuotedEnd = stringToPrecomputedChunk('"></div>');
126+
const dataElementUnquotedEnd = stringToPrecomputedChunk('"></div>');
127+
122128
const startInlineScript = stringToPrecomputedChunk('<script>');
123129
const endInlineScript = stringToPrecomputedChunk('</script>');
124130

125131
const startScriptSrc = stringToPrecomputedChunk('<script src="');
126132
const startModuleSrc = stringToPrecomputedChunk('<script type="module" src="');
127133
const scriptIntegirty = stringToPrecomputedChunk('" integrity="');
128134
const endAsyncScript = stringToPrecomputedChunk('" async=""></script>');
135+
// const endAsyncScript = stringToPrecomputedChunk('"></script>');
129136

130137
/**
131138
* This escaping function is designed to work with bootstrapScriptContent only.
@@ -152,6 +159,8 @@ export type BootstrapScriptDescriptor = {
152159
integrity?: string,
153160
};
154161
// Allows us to keep track of what we've already written so we can refer back to it.
162+
// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
163+
// is set, the server will send instructions via data attributes (instead of inline scripts)
155164
export function createResponseState(
156165
identifierPrefix: string | void,
157166
nonce: string | void,
@@ -168,6 +177,7 @@ export function createResponseState(
168177
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
169178
);
170179
const bootstrapChunks = [];
180+
let streamingFormat = 'SCRIPT';
171181
if (bootstrapScriptContent !== undefined) {
172182
bootstrapChunks.push(
173183
inlineScriptWithNonce,
@@ -177,6 +187,7 @@ export function createResponseState(
177187
}
178188
if (enableFizzExternalRuntime) {
179189
if (externalRuntimeConfig !== undefined) {
190+
streamingFormat = 'DATA';
180191
const src =
181192
typeof externalRuntimeConfig === 'string'
182193
? externalRuntimeConfig
@@ -240,12 +251,13 @@ export function createResponseState(
240251
}
241252
return {
242253
bootstrapChunks: bootstrapChunks,
243-
startInlineScript: inlineScriptWithNonce,
244254
placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
245255
segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
246256
boundaryPrefix: idPrefix + 'B:',
247257
idPrefix: idPrefix,
248258
nextSuspenseID: 0,
259+
streamingFormat,
260+
startInlineScript: inlineScriptWithNonce,
249261
sentCompleteSegmentFunction: false,
250262
sentCompleteBoundaryFunction: false,
251263
sentClientRenderFunction: false,
@@ -2081,29 +2093,54 @@ const completeSegmentScript1Full = stringToPrecomputedChunk(
20812093
);
20822094
const completeSegmentScript1Partial = stringToPrecomputedChunk('$RS("');
20832095
const completeSegmentScript2 = stringToPrecomputedChunk('","');
2084-
const completeSegmentScript3 = stringToPrecomputedChunk('")</script>');
2096+
const completeSegmentScriptEnd = stringToPrecomputedChunk('")</script>');
2097+
2098+
const completeSegmentData1 = stringToPrecomputedChunk(
2099+
'<div hidden data-react-server-instruction="$RS" data-react-server-arg0="',
2100+
);
2101+
const completeSegmentData2 = stringToPrecomputedChunk(
2102+
'" data-react-server-arg1="',
2103+
);
2104+
const completeSegmentDataEnd = dataElementQuotedEnd;
20852105

20862106
export function writeCompletedSegmentInstruction(
20872107
destination: Destination,
20882108
responseState: ResponseState,
20892109
contentSegmentID: number,
20902110
): boolean {
2091-
writeChunk(destination, responseState.startInlineScript);
2092-
if (!responseState.sentCompleteSegmentFunction) {
2093-
// The first time we write this, we'll need to include the full implementation.
2094-
responseState.sentCompleteSegmentFunction = true;
2095-
writeChunk(destination, completeSegmentScript1Full);
2111+
const scriptFormat =
2112+
!enableFizzExternalRuntime || responseState.streamingFormat === 'SCRIPT';
2113+
if (scriptFormat) {
2114+
writeChunk(destination, responseState.startInlineScript);
2115+
if (!responseState.sentCompleteSegmentFunction) {
2116+
// The first time we write this, we'll need to include the full implementation.
2117+
responseState.sentCompleteSegmentFunction = true;
2118+
writeChunk(destination, completeSegmentScript1Full);
2119+
} else {
2120+
// Future calls can just reuse the same function.
2121+
writeChunk(destination, completeSegmentScript1Partial);
2122+
}
20962123
} else {
2097-
// Future calls can just reuse the same function.
2098-
writeChunk(destination, completeSegmentScript1Partial);
2124+
writeChunk(destination, completeSegmentData1);
20992125
}
2126+
2127+
// Write function arguments, which are string literals
21002128
writeChunk(destination, responseState.segmentPrefix);
21012129
const formattedID = stringToChunk(contentSegmentID.toString(16));
21022130
writeChunk(destination, formattedID);
2103-
writeChunk(destination, completeSegmentScript2);
2131+
if (scriptFormat) {
2132+
writeChunk(destination, completeSegmentScript2);
2133+
} else {
2134+
writeChunk(destination, completeSegmentData2);
2135+
}
21042136
writeChunk(destination, responseState.placeholderPrefix);
21052137
writeChunk(destination, formattedID);
2106-
return writeChunkAndReturn(destination, completeSegmentScript3);
2138+
2139+
if (scriptFormat) {
2140+
return writeChunkAndReturn(destination, completeSegmentScriptEnd);
2141+
} else {
2142+
return writeChunkAndReturn(destination, completeSegmentDataEnd);
2143+
}
21072144
}
21082145

21092146
const completeBoundaryScript1Full = stringToPrecomputedChunk(
@@ -2121,9 +2158,25 @@ const completeBoundaryWithStylesScript1Partial = stringToPrecomputedChunk(
21212158
'$RR("',
21222159
);
21232160
const completeBoundaryScript2 = stringToPrecomputedChunk('","');
2124-
const completeBoundaryScript2a = stringToPrecomputedChunk('",');
2125-
const completeBoundaryScript3 = stringToPrecomputedChunk('"');
2126-
const completeBoundaryScript4 = stringToPrecomputedChunk(')</script>');
2161+
const completeBoundaryScript3a = stringToPrecomputedChunk('",');
2162+
const completeBoundaryScript3b = stringToPrecomputedChunk('"');
2163+
const completeBoundaryScriptEnd = stringToPrecomputedChunk(')</script>');
2164+
2165+
const completeBoundaryData1 = stringToPrecomputedChunk(
2166+
'<div hidden data-react-server-instruction="$RC" data-react-server-arg0="',
2167+
);
2168+
const completeBoundaryWithStylesData1 = stringToPrecomputedChunk(
2169+
'<div hidden data-react-server-instruction="$RR" data-react-server-arg0="',
2170+
);
2171+
const completeBoundaryData2 = stringToPrecomputedChunk(
2172+
'" data-react-server-arg1="',
2173+
);
2174+
const completeBoundaryData3aStart = stringToPrecomputedChunk(
2175+
'" data-react-server-arg2=\'',
2176+
);
2177+
const completeBoundaryData3aEnd = stringToPrecomputedChunk("'");
2178+
const completeBoundaryData3b = completeBoundaryScript3b;
2179+
const completeBoundaryDataEnd = dataElementUnquotedEnd;
21272180

21282181
export function writeCompletedBoundaryInstruction(
21292182
destination: Destination,
@@ -2136,24 +2189,34 @@ export function writeCompletedBoundaryInstruction(
21362189
if (enableFloat) {
21372190
hasStyleDependencies = hasStyleResourceDependencies(boundaryResources);
21382191
}
2139-
writeChunk(destination, responseState.startInlineScript);
2140-
if (enableFloat && hasStyleDependencies) {
2141-
if (!responseState.sentCompleteBoundaryFunction) {
2142-
responseState.sentCompleteBoundaryFunction = true;
2143-
responseState.sentStyleInsertionFunction = true;
2144-
writeChunk(destination, completeBoundaryWithStylesScript1FullBoth);
2145-
} else if (!responseState.sentStyleInsertionFunction) {
2146-
responseState.sentStyleInsertionFunction = true;
2147-
writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
2192+
const scriptFormat =
2193+
!enableFizzExternalRuntime || responseState.streamingFormat === 'SCRIPT';
2194+
if (scriptFormat) {
2195+
writeChunk(destination, responseState.startInlineScript);
2196+
if (enableFloat && hasStyleDependencies) {
2197+
if (!responseState.sentCompleteBoundaryFunction) {
2198+
responseState.sentCompleteBoundaryFunction = true;
2199+
responseState.sentStyleInsertionFunction = true;
2200+
writeChunk(destination, completeBoundaryWithStylesScript1FullBoth);
2201+
} else if (!responseState.sentStyleInsertionFunction) {
2202+
responseState.sentStyleInsertionFunction = true;
2203+
writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
2204+
} else {
2205+
writeChunk(destination, completeBoundaryWithStylesScript1Partial);
2206+
}
21482207
} else {
2149-
writeChunk(destination, completeBoundaryWithStylesScript1Partial);
2208+
if (!responseState.sentCompleteBoundaryFunction) {
2209+
responseState.sentCompleteBoundaryFunction = true;
2210+
writeChunk(destination, completeBoundaryScript1Full);
2211+
} else {
2212+
writeChunk(destination, completeBoundaryScript1Partial);
2213+
}
21502214
}
21512215
} else {
2152-
if (!responseState.sentCompleteBoundaryFunction) {
2153-
responseState.sentCompleteBoundaryFunction = true;
2154-
writeChunk(destination, completeBoundaryScript1Full);
2216+
if (enableFloat && hasStyleDependencies) {
2217+
writeChunk(destination, completeBoundaryWithStylesData1);
21552218
} else {
2156-
writeChunk(destination, completeBoundaryScript1Partial);
2219+
writeChunk(destination, completeBoundaryData1);
21572220
}
21582221
}
21592222

@@ -2163,27 +2226,56 @@ export function writeCompletedBoundaryInstruction(
21632226
);
21642227
}
21652228

2229+
// Write function arguments, which are string and array literals
21662230
const formattedContentID = stringToChunk(contentSegmentID.toString(16));
21672231
writeChunk(destination, boundaryID);
2168-
writeChunk(destination, completeBoundaryScript2);
2232+
if (scriptFormat) {
2233+
writeChunk(destination, completeBoundaryScript2);
2234+
} else {
2235+
writeChunk(destination, completeBoundaryData2);
2236+
}
21692237
writeChunk(destination, responseState.segmentPrefix);
21702238
writeChunk(destination, formattedContentID);
21712239
if (enableFloat && hasStyleDependencies) {
2172-
writeChunk(destination, completeBoundaryScript2a);
2240+
if (scriptFormat) {
2241+
writeChunk(destination, completeBoundaryScript3a);
2242+
} else {
2243+
writeChunk(destination, completeBoundaryData3aStart);
2244+
}
21732245
writeStyleResourceDependencies(destination, boundaryResources);
2246+
if (!scriptFormat) {
2247+
writeChunk(destination, completeBoundaryData3aEnd);
2248+
}
21742249
} else {
2175-
writeChunk(destination, completeBoundaryScript3);
2250+
if (scriptFormat) {
2251+
writeChunk(destination, completeBoundaryScript3b);
2252+
} else {
2253+
writeChunk(destination, completeBoundaryData3b);
2254+
}
2255+
}
2256+
if (scriptFormat) {
2257+
return writeChunkAndReturn(destination, completeBoundaryScriptEnd);
2258+
} else {
2259+
return writeChunkAndReturn(destination, completeBoundaryDataEnd);
21762260
}
2177-
return writeChunkAndReturn(destination, completeBoundaryScript4);
21782261
}
21792262

21802263
const clientRenderScript1Full = stringToPrecomputedChunk(
21812264
clientRenderFunction + ';$RX("',
21822265
);
21832266
const clientRenderScript1Partial = stringToPrecomputedChunk('$RX("');
21842267
const clientRenderScript1A = stringToPrecomputedChunk('"');
2185-
const clientRenderScript2 = stringToPrecomputedChunk(')</script>');
21862268
const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk(',');
2269+
const clientRenderScriptEnd = stringToPrecomputedChunk(')</script>');
2270+
2271+
const clientRenderData1 = stringToPrecomputedChunk(
2272+
'<div hidden data-react-server-instruction="$RX" data-react-server-arg0="',
2273+
);
2274+
const clientRenderData1A = clientRenderScript1A;
2275+
const clientRenderData2 = stringToPrecomputedChunk(' data-react-server-arg1=');
2276+
const clientRenderData3 = stringToPrecomputedChunk(' data-react-server-arg2=');
2277+
const clientRenderData4 = stringToPrecomputedChunk(' data-react-server-arg3=');
2278+
const clientRenderDataEnd = dataElementUnquotedEnd;
21872279

21882280
export function writeClientRenderBoundaryInstruction(
21892281
destination: Destination,
@@ -2193,14 +2285,20 @@ export function writeClientRenderBoundaryInstruction(
21932285
errorMessage?: string,
21942286
errorComponentStack?: string,
21952287
): boolean {
2196-
writeChunk(destination, responseState.startInlineScript);
2197-
if (!responseState.sentClientRenderFunction) {
2198-
// The first time we write this, we'll need to include the full implementation.
2199-
responseState.sentClientRenderFunction = true;
2200-
writeChunk(destination, clientRenderScript1Full);
2288+
const scriptFormat =
2289+
!enableFizzExternalRuntime || responseState.streamingFormat === 'SCRIPT';
2290+
if (scriptFormat) {
2291+
writeChunk(destination, responseState.startInlineScript);
2292+
if (!responseState.sentClientRenderFunction) {
2293+
// The first time we write this, we'll need to include the full implementation.
2294+
responseState.sentClientRenderFunction = true;
2295+
writeChunk(destination, clientRenderScript1Full);
2296+
} else {
2297+
// Future calls can just reuse the same function.
2298+
writeChunk(destination, clientRenderScript1Partial);
2299+
}
22012300
} else {
2202-
// Future calls can just reuse the same function.
2203-
writeChunk(destination, clientRenderScript1Partial);
2301+
writeChunk(destination, clientRenderData1);
22042302
}
22052303

22062304
if (boundaryID === null) {
@@ -2210,29 +2308,51 @@ export function writeClientRenderBoundaryInstruction(
22102308
}
22112309

22122310
writeChunk(destination, boundaryID);
2213-
writeChunk(destination, clientRenderScript1A);
2311+
if (scriptFormat) {
2312+
writeChunk(destination, clientRenderScript1A);
2313+
} else {
2314+
writeChunk(destination, clientRenderData1A);
2315+
}
2316+
22142317
if (errorDigest || errorMessage || errorComponentStack) {
2215-
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2318+
if (scriptFormat) {
2319+
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2320+
} else {
2321+
writeChunk(destination, clientRenderData2);
2322+
}
22162323
writeChunk(
22172324
destination,
22182325
stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')),
22192326
);
22202327
}
22212328
if (errorMessage || errorComponentStack) {
2222-
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2329+
if (scriptFormat) {
2330+
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2331+
} else {
2332+
writeChunk(destination, clientRenderData3);
2333+
}
22232334
writeChunk(
22242335
destination,
22252336
stringToChunk(escapeJSStringsForInstructionScripts(errorMessage || '')),
22262337
);
22272338
}
22282339
if (errorComponentStack) {
2229-
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2340+
if (scriptFormat) {
2341+
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2342+
} else {
2343+
writeChunk(destination, clientRenderData4);
2344+
}
22302345
writeChunk(
22312346
destination,
22322347
stringToChunk(escapeJSStringsForInstructionScripts(errorComponentStack)),
22332348
);
22342349
}
2235-
return writeChunkAndReturn(destination, clientRenderScript2);
2350+
2351+
if (scriptFormat) {
2352+
return writeChunkAndReturn(destination, clientRenderScriptEnd);
2353+
} else {
2354+
return writeChunkAndReturn(destination, clientRenderDataEnd);
2355+
}
22362356
}
22372357

22382358
const regexForJSStringsInInstructionScripts = /[<\u2028\u2029]/g;

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ export const isPrimaryRenderer = false;
3131
export type ResponseState = {
3232
// Keep this in sync with ReactDOMServerFormatConfig
3333
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
34-
startInlineScript: PrecomputedChunk,
3534
placeholderPrefix: PrecomputedChunk,
3635
segmentPrefix: PrecomputedChunk,
3736
boundaryPrefix: string,
3837
idPrefix: string,
3938
nextSuspenseID: number,
39+
streamingFormat: 'SCRIPT' | 'DATA',
40+
// state for script streaming format, unused if using external runtime / data
41+
startInlineScript: PrecomputedChunk,
4042
sentCompleteSegmentFunction: boolean,
4143
sentCompleteBoundaryFunction: boolean,
4244
sentClientRenderFunction: boolean,
@@ -53,12 +55,13 @@ export function createResponseState(
5355
return {
5456
// Keep this in sync with ReactDOMServerFormatConfig
5557
bootstrapChunks: responseState.bootstrapChunks,
56-
startInlineScript: responseState.startInlineScript,
5758
placeholderPrefix: responseState.placeholderPrefix,
5859
segmentPrefix: responseState.segmentPrefix,
5960
boundaryPrefix: responseState.boundaryPrefix,
6061
idPrefix: responseState.idPrefix,
6162
nextSuspenseID: responseState.nextSuspenseID,
63+
streamingFormat: responseState.streamingFormat,
64+
startInlineScript: responseState.startInlineScript,
6265
sentCompleteSegmentFunction: responseState.sentCompleteSegmentFunction,
6366
sentCompleteBoundaryFunction: responseState.sentCompleteBoundaryFunction,
6467
sentClientRenderFunction: responseState.sentClientRenderFunction,

0 commit comments

Comments
 (0)