63
import isArray from 'shared/isArray';
64
65
import {
66
+ preinitImpl,
67
prepareToRenderResources,
68
finishRenderingResources,
69
resourcesFromElement,
106
// E.g. this can be used to distinguish legacy renderers from this modern one.
107
export const isPrimaryRenderer = true;
108
109
+export type StreamingFormat = 0 | 1;
110
+const ScriptStreamingFormat: StreamingFormat = 0;
111
+const DataStreamingFormat: StreamingFormat = 1;
112
+
113
// Per response, global state that is not contextual to the rendering subtree.
114
export type ResponseState = {
115
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
111
- startInlineScript: PrecomputedChunk,
116
placeholderPrefix: PrecomputedChunk,
117
segmentPrefix: PrecomputedChunk,
118
boundaryPrefix: string,
119
idPrefix: string,
120
nextSuspenseID: number,
121
+ streamingFormat: StreamingFormat,
122
+ // state for script streaming format, unused if using external runtime / data
123
+ startInlineScript: PrecomputedChunk,
124
sentCompleteSegmentFunction: boolean,
125
sentCompleteBoundaryFunction: boolean,
126
sentClientRenderFunction: boolean,
120
- sentStyleInsertionFunction: boolean, // We allow the legacy renderer to extend this object.
127
+ sentStyleInsertionFunction: boolean,
128
+ // state for data streaming format
129
+ externalRuntimeConfig: BootstrapScriptDescriptor | null,
130
+ // We allow the legacy renderer to extend this object.
131
...
132
};
133
134
+const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');
135
+
136
const startInlineScript = stringToPrecomputedChunk('<script>');
137
const endInlineScript = stringToPrecomputedChunk('</script>');
138
166
integrity?: string,
167
};
168
// Allows us to keep track of what we've already written so we can refer back to it.
169
+// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
170
+// is set, the server will send instructions via data attributes (instead of inline scripts)
171
export function createResponseState(
172
identifierPrefix: string | void,
173
nonce: string | void,
184
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
185
);
186
const bootstrapChunks = [];
187
+ let externalRuntimeDesc = null;
188
+ let streamingFormat = ScriptStreamingFormat;
189
if (bootstrapScriptContent !== undefined) {
190
bootstrapChunks.push(
191
inlineScriptWithNonce,
194
);
195
}
196
if (enableFizzExternalRuntime) {
181
- if (externalRuntimeConfig !== undefined) {
182
- const src =
183
- typeof externalRuntimeConfig === 'string'
184
- ? externalRuntimeConfig
185
- : externalRuntimeConfig.src;
186
- const integrity =
187
- typeof externalRuntimeConfig === 'string'
188
- ? undefined
189
- : externalRuntimeConfig.integrity;
190
- bootstrapChunks.push(
191
- startScriptSrc,
192
- stringToChunk(escapeTextForBrowser(src)),
197
+ if (!enableFloat) {
198
+ throw new Error(
199
+ 'enableFizzExternalRuntime without enableFloat is not supported. This should never appear in production, since it means you are using a misconfigured React bundle.',
200
);
194
- if (integrity) {
195
- bootstrapChunks.push(
196
- scriptIntegirty,
197
- stringToChunk(escapeTextForBrowser(integrity)),
198
- );
201
+ }
202
+ if (externalRuntimeConfig !== undefined) {
203
+ streamingFormat = DataStreamingFormat;
204
+ if (typeof externalRuntimeConfig === 'string') {
205
+ externalRuntimeDesc = {
206
+ src: externalRuntimeConfig,
207
+ integrity: undefined,
208
+ };
209
+ } else {
210
+ externalRuntimeDesc = externalRuntimeConfig;
211
}
200
- bootstrapChunks.push(endAsyncScript);
212
}
213
}
214
if (bootstrapScripts !== undefined) {
253
}
254
return {
255
bootstrapChunks: bootstrapChunks,
245
- startInlineScript: inlineScriptWithNonce,
256
placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
257
segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
258
boundaryPrefix: idPrefix + 'B:',
259
idPrefix: idPrefix,
260
nextSuspenseID: 0,
261
+ streamingFormat,
262
+ startInlineScript: inlineScriptWithNonce,
263
sentCompleteSegmentFunction: false,
264
sentCompleteBoundaryFunction: false,
265
sentClientRenderFunction: false,
266
sentStyleInsertionFunction: false,
267
+ externalRuntimeConfig: externalRuntimeDesc,
268
};
269
}
270
2397
);
2398
const completeSegmentScript1Partial = stringToPrecomputedChunk('$RS("');
2399
const completeSegmentScript2 = stringToPrecomputedChunk('","');
2387
-const completeSegmentScript3 = stringToPrecomputedChunk('")</script>');
2400
+const completeSegmentScriptEnd = stringToPrecomputedChunk('")</script>');
2401
+
2402
+const completeSegmentData1 = stringToPrecomputedChunk(
2403
+ '<template data-rsi="" data-sid="',
2404
+);
2405
+const completeSegmentData2 = stringToPrecomputedChunk('" data-pid="');
2406
+const completeSegmentDataEnd = dataElementQuotedEnd;
2407
2408
export function writeCompletedSegmentInstruction(
2409
destination: Destination,
2410
responseState: ResponseState,
2411
contentSegmentID: number,
2412
): boolean {
2394
- writeChunk(destination, responseState.startInlineScript);
2395
- if (!responseState.sentCompleteSegmentFunction) {
2396
- // The first time we write this, we'll need to include the full implementation.
2397
- responseState.sentCompleteSegmentFunction = true;
2398
- writeChunk(destination, completeSegmentScript1Full);
2413
+ const scriptFormat =
2414
+ !enableFizzExternalRuntime ||
2415
+ responseState.streamingFormat === ScriptStreamingFormat;
2416
+ if (scriptFormat) {
2417
+ writeChunk(destination, responseState.startInlineScript);
2418
+ if (!responseState.sentCompleteSegmentFunction) {
2419
+ // The first time we write this, we'll need to include the full implementation.
2420
+ responseState.sentCompleteSegmentFunction = true;
2421
+ writeChunk(destination, completeSegmentScript1Full);
2422
+ } else {
2423
+ // Future calls can just reuse the same function.
2424
+ writeChunk(destination, completeSegmentScript1Partial);
2425
+ }
2426
} else {
2400
- // Future calls can just reuse the same function.
2401
- writeChunk(destination, completeSegmentScript1Partial);
2427
+ writeChunk(destination, completeSegmentData1);
2428
}
2429
+
2430
+ // Write function arguments, which are string literals
2431
writeChunk(destination, responseState.segmentPrefix);
2432
const formattedID = stringToChunk(contentSegmentID.toString(16));
2433
writeChunk(destination, formattedID);
2406
- writeChunk(destination, completeSegmentScript2);
2434
+ if (scriptFormat) {
2435
+ writeChunk(destination, completeSegmentScript2);
2436
+ } else {
2437
+ writeChunk(destination, completeSegmentData2);
2438
+ }
2439
writeChunk(destination, responseState.placeholderPrefix);
2440
writeChunk(destination, formattedID);
2409
- return writeChunkAndReturn(destination, completeSegmentScript3);
2441
+
2442
+ if (scriptFormat) {
2443
+ return writeChunkAndReturn(destination, completeSegmentScriptEnd);
2444
+ } else {
2445
+ return writeChunkAndReturn(destination, completeSegmentDataEnd);
2446
+ }
2447
}
2448
2449
const completeBoundaryScript1Full = stringToPrecomputedChunk(
2461
'$RR("',
2462
);
2463
const completeBoundaryScript2 = stringToPrecomputedChunk('","');
2427
-const completeBoundaryScript2a = stringToPrecomputedChunk('",');
2428
-const completeBoundaryScript3 = stringToPrecomputedChunk('"');
2429
-const completeBoundaryScript4 = stringToPrecomputedChunk(')</script>');
2464
+const completeBoundaryScript3a = stringToPrecomputedChunk('",');
2465
+const completeBoundaryScript3b = stringToPrecomputedChunk('"');
2466
+const completeBoundaryScriptEnd = stringToPrecomputedChunk(')</script>');
2467
+
2468
+const completeBoundaryData1 = stringToPrecomputedChunk(
2469
+ '<template data-rci="" data-bid="',
2470
+);
2471
+const completeBoundaryWithStylesData1 = stringToPrecomputedChunk(
2472
+ '<template data-rri="" data-bid="',
2473
+);
2474
+const completeBoundaryData2 = stringToPrecomputedChunk('" data-sid="');
2475
+const completeBoundaryData3a = stringToPrecomputedChunk('" data-sty="');
2476
+const completeBoundaryDataEnd = dataElementQuotedEnd;
2477
2478
export function writeCompletedBoundaryInstruction(
2479
destination: Destination,
2486
if (enableFloat) {
2487
hasStyleDependencies = hasStyleResourceDependencies(boundaryResources);
2488
}
2442
- writeChunk(destination, responseState.startInlineScript);
2443
- if (enableFloat && hasStyleDependencies) {
2444
- if (!responseState.sentCompleteBoundaryFunction) {
2445
- responseState.sentCompleteBoundaryFunction = true;
2446
- responseState.sentStyleInsertionFunction = true;
2447
- writeChunk(
2448
- destination,
2449
- clonePrecomputedChunk(completeBoundaryWithStylesScript1FullBoth),
2450
- );
2451
- } else if (!responseState.sentStyleInsertionFunction) {
2452
- responseState.sentStyleInsertionFunction = true;
2453
- writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
2489
+ const scriptFormat =
2490
+ !enableFizzExternalRuntime ||
2491
+ responseState.streamingFormat === ScriptStreamingFormat;
2492
+ if (scriptFormat) {
2493
+ writeChunk(destination, responseState.startInlineScript);
2494
+ if (enableFloat && hasStyleDependencies) {
2495
+ if (!responseState.sentCompleteBoundaryFunction) {
2496
+ responseState.sentCompleteBoundaryFunction = true;
2497
+ responseState.sentStyleInsertionFunction = true;
2498
+ writeChunk(
2499
+ destination,
2500
+ clonePrecomputedChunk(completeBoundaryWithStylesScript1FullBoth),
2501
+ );
2502
+ } else if (!responseState.sentStyleInsertionFunction) {
2503
+ responseState.sentStyleInsertionFunction = true;
2504
+ writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
2505
+ } else {
2506
+ writeChunk(destination, completeBoundaryWithStylesScript1Partial);
2507
+ }
2508
} else {
2455
- writeChunk(destination, completeBoundaryWithStylesScript1Partial);
2509
+ if (!responseState.sentCompleteBoundaryFunction) {
2510
+ responseState.sentCompleteBoundaryFunction = true;
2511
+ writeChunk(destination, completeBoundaryScript1Full);
2512
+ } else {
2513
+ writeChunk(destination, completeBoundaryScript1Partial);
2514
+ }
2515
}
2516
} else {
2458
- if (!responseState.sentCompleteBoundaryFunction) {
2459
- responseState.sentCompleteBoundaryFunction = true;
2460
- writeChunk(destination, completeBoundaryScript1Full);
2517
+ if (enableFloat && hasStyleDependencies) {
2518
+ writeChunk(destination, completeBoundaryWithStylesData1);
2519
} else {
2462
- writeChunk(destination, completeBoundaryScript1Partial);
2520
+ writeChunk(destination, completeBoundaryData1);
2521
}
2522
}
2523
2527
);
2528
}
2529
2530
+ // Write function arguments, which are string and array literals
2531
const formattedContentID = stringToChunk(contentSegmentID.toString(16));
2532
writeChunk(destination, boundaryID);
2474
- writeChunk(destination, completeBoundaryScript2);
2533
+ if (scriptFormat) {
2534
+ writeChunk(destination, completeBoundaryScript2);
2535
+ } else {
2536
+ writeChunk(destination, completeBoundaryData2);
2537
+ }
2538
writeChunk(destination, responseState.segmentPrefix);
2539
writeChunk(destination, formattedContentID);
2540
if (enableFloat && hasStyleDependencies) {
2478
- writeChunk(destination, completeBoundaryScript2a);
2479
- writeStyleResourceDependencies(destination, boundaryResources);
2541
+ // Script and data writers must format this differently:
2542
+ // - script writer emits an array literal, whose string elements are
2543
+ // escaped for javascript e.g. ["A", "B"]
2544
+ // - data writer emits a string literal, which is escaped as html
2545
+ // e.g. ["A", "B"]
2546
+ if (scriptFormat) {
2547
+ writeChunk(destination, completeBoundaryScript3a);
2548
+ // boundaryResources encodes an array literal
2549
+ writeStyleResourceDependenciesInJS(destination, boundaryResources);
2550
+ } else {
2551
+ writeChunk(destination, completeBoundaryData3a);
2552
+ writeStyleResourceDependenciesInAttr(destination, boundaryResources);
2553
+ }
2554
+ } else {
2555
+ if (scriptFormat) {
2556
+ writeChunk(destination, completeBoundaryScript3b);
2557
+ }
2558
+ }
2559
+ if (scriptFormat) {
2560
+ return writeChunkAndReturn(destination, completeBoundaryScriptEnd);
2561
} else {
2481
- writeChunk(destination, completeBoundaryScript3);
2562
+ return writeChunkAndReturn(destination, completeBoundaryDataEnd);
2563
}
2483
- return writeChunkAndReturn(destination, completeBoundaryScript4);
2564
}
2565
2566
const clientRenderScript1Full = stringToPrecomputedChunk(
2568
);
2569
const clientRenderScript1Partial = stringToPrecomputedChunk('$RX("');
2570
const clientRenderScript1A = stringToPrecomputedChunk('"');
2491
-const clientRenderScript2 = stringToPrecomputedChunk(')</script>');
2571
const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk(',');
2572
+const clientRenderScriptEnd = stringToPrecomputedChunk(')</script>');
2573
+
2574
+const clientRenderData1 = stringToPrecomputedChunk(
2575
+ '<template data-rxi="" data-bid="',
2576
+);
2577
+const clientRenderData2 = stringToPrecomputedChunk('" data-dgst="');
2578
+const clientRenderData3 = stringToPrecomputedChunk('" data-msg="');
2579
+const clientRenderData4 = stringToPrecomputedChunk('" data-stck="');
2580
+const clientRenderDataEnd = dataElementQuotedEnd;
2581
2582
export function writeClientRenderBoundaryInstruction(
2583
destination: Destination,
2587
errorMessage?: string,
2588
errorComponentStack?: string,
2589
): boolean {
2502
- writeChunk(destination, responseState.startInlineScript);
2503
- if (!responseState.sentClientRenderFunction) {
2504
- // The first time we write this, we'll need to include the full implementation.
2505
- responseState.sentClientRenderFunction = true;
2506
- writeChunk(destination, clientRenderScript1Full);
2590
+ const scriptFormat =
2591
+ !enableFizzExternalRuntime ||
2592
+ responseState.streamingFormat === ScriptStreamingFormat;
2593
+ if (scriptFormat) {
2594
+ writeChunk(destination, responseState.startInlineScript);
2595
+ if (!responseState.sentClientRenderFunction) {
2596
+ // The first time we write this, we'll need to include the full implementation.
2597
+ responseState.sentClientRenderFunction = true;
2598
+ writeChunk(destination, clientRenderScript1Full);
2599
+ } else {
2600
+ // Future calls can just reuse the same function.
2601
+ writeChunk(destination, clientRenderScript1Partial);
2602
+ }
2603
} else {
2508
- // Future calls can just reuse the same function.
2509
- writeChunk(destination, clientRenderScript1Partial);
2604
+ // <template data-rxi="" data-bid="
2605
+ writeChunk(destination, clientRenderData1);
2606
}
2607
2608
if (boundaryID === null) {
2612
}
2613
2614
writeChunk(destination, boundaryID);
2519
- writeChunk(destination, clientRenderScript1A);
2615
+ if (scriptFormat) {
2616
+ // " needs to be inserted for scripts, since ArgInterstitual does not contain
2617
+ // leading or trailing quotes
2618
+ writeChunk(destination, clientRenderScript1A);
2619
+ }
2620
+
2621
if (errorDigest || errorMessage || errorComponentStack) {
2521
- writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2522
- writeChunk(
2523
- destination,
2524
- stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')),
2525
- );
2622
+ if (scriptFormat) {
2623
+ // ,"JSONString"
2624
+ writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2625
+ writeChunk(
2626
+ destination,
2627
+ stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')),
2628
+ );
2629
+ } else {
2630
+ // " data-dgst="HTMLString
2631
+ writeChunk(destination, clientRenderData2);
2632
+ writeChunk(
2633
+ destination,
2634
+ stringToChunk(escapeTextForBrowser(errorDigest || '')),
2635
+ );
2636
+ }
2637
}
2638
if (errorMessage || errorComponentStack) {
2528
- writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2529
- writeChunk(
2530
- destination,
2531
- stringToChunk(escapeJSStringsForInstructionScripts(errorMessage || '')),
2532
- );
2639
+ if (scriptFormat) {
2640
+ // ,"JSONString"
2641
+ writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2642
+ writeChunk(
2643
+ destination,
2644
+ stringToChunk(escapeJSStringsForInstructionScripts(errorMessage || '')),
2645
+ );
2646
+ } else {
2647
+ // " data-msg="HTMLString
2648
+ writeChunk(destination, clientRenderData3);
2649
+ writeChunk(
2650
+ destination,
2651
+ stringToChunk(escapeTextForBrowser(errorMessage || '')),
2652
+ );
2653
+ }
2654
}
2655
if (errorComponentStack) {
2535
- writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2536
- writeChunk(
2537
- destination,
2538
- stringToChunk(escapeJSStringsForInstructionScripts(errorComponentStack)),
2539
- );
2656
+ // ,"JSONString"
2657
+ if (scriptFormat) {
2658
+ writeChunk(destination, clientRenderErrorScriptArgInterstitial);
2659
+ writeChunk(
2660
+ destination,
2661
+ stringToChunk(
2662
+ escapeJSStringsForInstructionScripts(errorComponentStack),
2663
+ ),
2664
+ );
2665
+ } else {
2666
+ // " data-stck="HTMLString
2667
+ writeChunk(destination, clientRenderData4);
2668
+ writeChunk(
2669
+ destination,
2670
+ stringToChunk(escapeTextForBrowser(errorComponentStack)),
2671
+ );
2672
+ }
2673
+ }
2674
+
2675
+ if (scriptFormat) {
2676
+ // ></script>
2677
+ return writeChunkAndReturn(destination, clientRenderScriptEnd);
2678
+ } else {
2679
+ // "></template>
2680
+ return writeChunkAndReturn(destination, clientRenderDataEnd);
2681
}
2541
- return writeChunkAndReturn(destination, clientRenderScript2);
2682
}
2683
2684
const regexForJSStringsInInstructionScripts = /[<\u2028\u2029]/g;
2738
destination: Destination,
2739
resources: Resources,
2740
responseState: ResponseState,
2741
+ willFlushAllSegments: boolean,
2742
): boolean {
2743
+ // Write initially discovered resources after the shell completes
2744
+ if (
2745
+ enableFizzExternalRuntime &&
2746
+ !willFlushAllSegments &&
2747
+ responseState.externalRuntimeConfig
2748
+ ) {
2749
+ // If the root segment is incomplete due to suspended tasks
2750
+ // (e.g. willFlushAllSegments = false) and we are using data
2751
+ // streaming format, ensure the external runtime is sent.
2752
+ // (User code could choose to send this even earlier by calling
2753
+ // preinit(...), if they know they will suspend).
2754
+ const {src, integrity} = responseState.externalRuntimeConfig;
2755
+ preinitImpl(resources, src, {as: 'script', integrity});
2756
+ }
2757
function flushLinkResource(resource) {
2758
if (!resource.flushed) {
2759
pushLinkImpl(target, resource.props, responseState);
2994
const arrayInterstitial = stringToPrecomputedChunk(',');
2995
const arrayCloseBracket = stringToPrecomputedChunk(']');
2996
2842
-function writeStyleResourceDependencies(
2997
+// This function writes a 2D array of strings to be embedded in javascript.
2998
+// E.g.
2999
+// [["JS_escaped_string1", "JS_escaped_string2"]]
3000
+function writeStyleResourceDependenciesInJS(
3001
destination: Destination,
3002
boundaryResources: BoundaryResources,
3003
): void {
3010
// should be ready before content is shown on the client
3011
} else if (resource.flushed) {
3012
writeChunk(destination, nextArrayOpenBrackChunk);
2855
- writeStyleResourceDependencyHrefOnly(destination, resource.href);
3013
+ writeStyleResourceDependencyHrefOnlyInJS(destination, resource.href);
3014
writeChunk(destination, arrayCloseBracket);
3015
nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
3016
} else {
3017
writeChunk(destination, nextArrayOpenBrackChunk);
2860
- writeStyleResourceDependency(
3018
+ writeStyleResourceDependencyInJS(
3019
destination,
3020
resource.href,
3021
resource.precedence,
3031
writeChunk(destination, arrayCloseBracket);
3032
}
3033
2876
-function writeStyleResourceDependencyHrefOnly(
3034
+/* Helper functions */
3035
+function writeStyleResourceDependencyHrefOnlyInJS(
3036
destination: Destination,
3037
href: string,
3038
) {
3048
);
3049
}
3050
2892
-function writeStyleResourceDependency(
3051
+function writeStyleResourceDependencyInJS(
3052
destination: Destination,
3053
href: string,
3054
precedence: string,
3095
);
3096
// eslint-disable-next-line-no-fallthrough
3097
default:
2939
- writeStyleResourceAttribute(destination, propKey, propValue);
3098
+ writeStyleResourceAttributeInJS(destination, propKey, propValue);
3099
break;
3100
}
3101
}
3103
return null;
3104
}
3105
2947
-function writeStyleResourceAttribute(
3106
+function writeStyleResourceAttributeInJS(
3107
destination: Destination,
3108
name: string,
3109
value: string | boolean | number | Function | Object, // not null or undefined
3181
stringToChunk(escapeJSObjectForInstructionScripts(attributeValue)),
3182
);
3183
}
3184
+
3185
+// This function writes a 2D array of strings to be embedded in an attribute
3186
+// value and read with JSON.parse in ReactDOMServerExternalRuntime.js
3187
+// E.g.
3188
+// [["JSON_escaped_string1", "JSON_escaped_string2"]]
3189
+function writeStyleResourceDependenciesInAttr(
3190
+ destination: Destination,
3191
+ boundaryResources: BoundaryResources,
3192
+): void {
3193
+ writeChunk(destination, arrayFirstOpenBracket);
3194
+
3195
+ let nextArrayOpenBrackChunk = arrayFirstOpenBracket;
3196
+ boundaryResources.forEach(resource => {
3197
+ if (resource.inShell) {
3198
+ // We can elide this dependency because it was flushed in the shell and
3199
+ // should be ready before content is shown on the client
3200
+ } else if (resource.flushed) {
3201
+ writeChunk(destination, nextArrayOpenBrackChunk);
3202
+ writeStyleResourceDependencyHrefOnlyInAttr(destination, resource.href);
3203
+ writeChunk(destination, arrayCloseBracket);
3204
+ nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
3205
+ } else {
3206
+ writeChunk(destination, nextArrayOpenBrackChunk);
3207
+ writeStyleResourceDependencyInAttr(
3208
+ destination,
3209
+ resource.href,
3210
+ resource.precedence,
3211
+ resource.props,
3212
+ );
3213
+ writeChunk(destination, arrayCloseBracket);
3214
+ nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
3215
+
3216
+ resource.flushed = true;
3217
+ resource.hint.flushed = true;
3218
+ }
3219
+ });
3220
+ writeChunk(destination, arrayCloseBracket);
3221
+}
3222
+
3223
+/* Helper functions */
3224
+function writeStyleResourceDependencyHrefOnlyInAttr(
3225
+ destination: Destination,
3226
+ href: string,
3227
+) {
3228
+ // We should actually enforce this earlier when the resource is created but for
3229
+ // now we make sure we are actually dealing with a string here.
3230
+ if (__DEV__) {
3231
+ checkAttributeStringCoercion(href, 'href');
3232
+ }
3233
+ const coercedHref = '' + (href: any);
3234
+ writeChunk(
3235
+ destination,
3236
+ stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))),
3237
+ );
3238
+}
3239
+
3240
+function writeStyleResourceDependencyInAttr(
3241
+ destination: Destination,
3242
+ href: string,
3243
+ precedence: string,
3244
+ props: Object,
3245
+) {
3246
+ if (__DEV__) {
3247
+ checkAttributeStringCoercion(href, 'href');
3248
+ }
3249
+ const coercedHref = '' + (href: any);
3250
+ sanitizeURL(coercedHref);
3251
+ writeChunk(
3252
+ destination,
3253
+ stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))),
3254
+ );
3255
+
3256
+ if (__DEV__) {
3257
+ checkAttributeStringCoercion(precedence, 'precedence');
3258
+ }
3259
+ const coercedPrecedence = '' + (precedence: any);
3260
+ writeChunk(destination, arrayInterstitial);
3261
+ writeChunk(
3262
+ destination,
3263
+ stringToChunk(escapeTextForBrowser(JSON.stringify(coercedPrecedence))),
3264
+ );
3265
+
3266
+ for (const propKey in props) {
3267
+ if (hasOwnProperty.call(props, propKey)) {
3268
+ const propValue = props[propKey];
3269
+ if (propValue == null) {
3270
+ continue;
3271
+ }
3272
+ switch (propKey) {
3273
+ case 'href':
3274
+ case 'rel':
3275
+ case 'precedence':
3276
+ case 'data-precedence': {
3277
+ break;
3278
+ }
3279
+ case 'children':
3280
+ case 'dangerouslySetInnerHTML':
3281
+ throw new Error(
3282
+ `${'link'} is a self-closing tag and must neither have \`children\` nor ` +
3283
+ 'use `dangerouslySetInnerHTML`.',
3284
+ );
3285
+ // eslint-disable-next-line-no-fallthrough
3286
+ default:
3287
+ writeStyleResourceAttributeInAttr(destination, propKey, propValue);
3288
+ break;
3289
+ }
3290
+ }
3291
+ }
3292
+ return null;
3293
+}
3294
+
3295
+function writeStyleResourceAttributeInAttr(
3296
+ destination: Destination,
3297
+ name: string,
3298
+ value: string | boolean | number | Function | Object, // not null or undefined
3299
+): void {
3300
+ let attributeName = name.toLowerCase();
3301
+ let attributeValue;
3302
+ switch (typeof value) {
3303
+ case 'function':
3304
+ case 'symbol':
3305
+ return;
3306
+ }
3307
+
3308
+ switch (name) {
3309
+ // Reserved names
3310
+ case 'innerHTML':
3311
+ case 'dangerouslySetInnerHTML':
3312
+ case 'suppressContentEditableWarning':
3313
+ case 'suppressHydrationWarning':
3314
+ case 'style':
3315
+ // Ignored
3316
+ return;
3317
+
3318
+ // Attribute renames
3319
+ case 'className':
3320
+ attributeName = 'class';
3321
+ break;
3322
+
3323
+ // Booleans
3324
+ case 'hidden':
3325
+ if (value === false) {
3326
+ return;
3327
+ }
3328
+ attributeValue = '';
3329
+ break;
3330
+
3331
+ // Santized URLs
3332
+ case 'src':
3333
+ case 'href': {
3334
+ if (__DEV__) {
3335
+ checkAttributeStringCoercion(value, attributeName);
3336
+ }
3337
+ attributeValue = '' + (value: any);
3338
+ sanitizeURL(attributeValue);
3339
+ break;
3340
+ }
3341
+ default: {
3342
+ if (!isAttributeNameSafe(name)) {
3343
+ return;
3344
+ }
3345
+ }
3346
+ }
3347
+
3348
+ if (
3349
+ // shouldIgnoreAttribute
3350
+ // We have already filtered out null/undefined and reserved words.
3351
+ name.length > 2 &&
3352
+ (name[0] === 'o' || name[0] === 'O') &&
3353
+ (name[1] === 'n' || name[1] === 'N')
3354
+ ) {
3355
+ return;
3356
+ }
3357
+
3358
+ if (__DEV__) {
3359
+ checkAttributeStringCoercion(value, attributeName);
3360
+ }
3361
+ attributeValue = '' + (value: any);
3362
+ writeChunk(destination, arrayInterstitial);
3363
+ writeChunk(
3364
+ destination,
3365
+ stringToChunk(escapeTextForBrowser(JSON.stringify(attributeName))),
3366
+ );
3367
+ writeChunk(destination, arrayInterstitial);
3368
+ writeChunk(
3369
+ destination,
3370
+ stringToChunk(escapeTextForBrowser(JSON.stringify(attributeValue))),
3371
+ );
3372
+}