Skip to content

Commit 17aee90

Browse files
committed
Feedback: fork writeStyleResource for different contexts
1 parent 7cf96ce commit 17aee90

File tree

1 file changed

+216
-52
lines changed

1 file changed

+216
-52
lines changed

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

Lines changed: 216 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2543,18 +2543,10 @@ export function writeCompletedBoundaryInstruction(
25432543
if (scriptFormat) {
25442544
writeChunk(destination, completeBoundaryScript3a);
25452545
// boundaryResources encodes an array literal
2546-
writeStyleResourceDependencies(
2547-
destination,
2548-
boundaryResources,
2549-
scriptFormat ? JavascriptEmbedding : HTMLAttributeEmbedding,
2550-
);
2546+
writeStyleResourceDependenciesInJS(destination, boundaryResources);
25512547
} else {
25522548
writeChunk(destination, completeBoundaryData3aStart);
2553-
writeStyleResourceDependencies(
2554-
destination,
2555-
boundaryResources,
2556-
scriptFormat ? JavascriptEmbedding : HTMLAttributeEmbedding,
2557-
);
2549+
writeStyleResourceDependenciesInAttr(destination, boundaryResources);
25582550
writeChunk(destination, completeBoundaryData3aEnd);
25592551
}
25602552
} else {
@@ -2996,40 +2988,201 @@ const arraySubsequentOpenBracket = stringToPrecomputedChunk(',[');
29962988
const arrayInterstitial = stringToPrecomputedChunk(',');
29972989
const arrayCloseBracket = stringToPrecomputedChunk(']');
29982990

2999-
type EmbeddingContext = 0 | 1;
3000-
const HTMLAttributeEmbedding: EmbeddingContext = 0;
3001-
const JavascriptEmbedding: EmbeddingContext = 1;
2991+
// This function writes a 2D array of strings to be embedded in javascript.
2992+
// E.g.
2993+
// [["JS_escaped_string1", "JS_escaped_string2"]]
2994+
function writeStyleResourceDependenciesInJS(
2995+
destination: Destination,
2996+
boundaryResources: BoundaryResources,
2997+
): void {
2998+
writeChunk(destination, arrayFirstOpenBracket);
30022999

3003-
function writeStyleResourceObject(
3000+
let nextArrayOpenBrackChunk = arrayFirstOpenBracket;
3001+
boundaryResources.forEach(resource => {
3002+
if (resource.inShell) {
3003+
// We can elide this dependency because it was flushed in the shell and
3004+
// should be ready before content is shown on the client
3005+
} else if (resource.flushed) {
3006+
writeChunk(destination, nextArrayOpenBrackChunk);
3007+
writeStyleResourceDependencyHrefOnlyInJS(destination, resource.href);
3008+
writeChunk(destination, arrayCloseBracket);
3009+
nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
3010+
} else {
3011+
writeChunk(destination, nextArrayOpenBrackChunk);
3012+
writeStyleResourceDependencyInJS(
3013+
destination,
3014+
resource.href,
3015+
resource.precedence,
3016+
resource.props,
3017+
);
3018+
writeChunk(destination, arrayCloseBracket);
3019+
nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
3020+
3021+
resource.flushed = true;
3022+
resource.hint.flushed = true;
3023+
}
3024+
});
3025+
writeChunk(destination, arrayCloseBracket);
3026+
}
3027+
3028+
/* Helper functions */
3029+
function writeStyleResourceDependencyHrefOnlyInJS(
30043030
destination: Destination,
3005-
str: string,
3006-
context: EmbeddingContext,
3031+
href: string,
30073032
) {
3008-
if (context === JavascriptEmbedding) {
3009-
// write "script_escaped_string", since this is writing to a script tag
3010-
// and will be evaluated as a string literal inside an array literal
3011-
writeChunk(
3012-
destination,
3013-
stringToChunk(escapeJSObjectForInstructionScripts(str)),
3014-
);
3015-
} else if (context === HTMLAttributeEmbedding) {
3016-
// write "JSON_escaped_string" here, since this is writing
3017-
// to an attribute string and will be parsed manually via JSON.parse
3018-
writeChunk(
3019-
destination,
3020-
stringToChunk(escapeTextForBrowser(JSON.stringify(str))),
3021-
);
3022-
} else {
3023-
throw new Error(
3024-
'Unknown embedding context type when writing style resources. This is a bug in React.',
3025-
);
3033+
// We should actually enforce this earlier when the resource is created but for
3034+
// now we make sure we are actually dealing with a string here.
3035+
if (__DEV__) {
3036+
checkAttributeStringCoercion(href, 'href');
30263037
}
3038+
const coercedHref = '' + (href: any);
3039+
writeChunk(
3040+
destination,
3041+
stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)),
3042+
);
30273043
}
30283044

3029-
function writeStyleResourceDependencies(
3045+
function writeStyleResourceDependencyInJS(
3046+
destination: Destination,
3047+
href: string,
3048+
precedence: string,
3049+
props: Object,
3050+
) {
3051+
if (__DEV__) {
3052+
checkAttributeStringCoercion(href, 'href');
3053+
}
3054+
const coercedHref = '' + (href: any);
3055+
sanitizeURL(coercedHref);
3056+
writeChunk(
3057+
destination,
3058+
stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)),
3059+
);
3060+
3061+
if (__DEV__) {
3062+
checkAttributeStringCoercion(precedence, 'precedence');
3063+
}
3064+
const coercedPrecedence = '' + (precedence: any);
3065+
writeChunk(destination, arrayInterstitial);
3066+
writeChunk(
3067+
destination,
3068+
stringToChunk(escapeJSObjectForInstructionScripts(coercedPrecedence)),
3069+
);
3070+
3071+
for (const propKey in props) {
3072+
if (hasOwnProperty.call(props, propKey)) {
3073+
const propValue = props[propKey];
3074+
if (propValue == null) {
3075+
continue;
3076+
}
3077+
switch (propKey) {
3078+
case 'href':
3079+
case 'rel':
3080+
case 'precedence':
3081+
case 'data-precedence': {
3082+
break;
3083+
}
3084+
case 'children':
3085+
case 'dangerouslySetInnerHTML':
3086+
throw new Error(
3087+
`${'link'} is a self-closing tag and must neither have \`children\` nor ` +
3088+
'use `dangerouslySetInnerHTML`.',
3089+
);
3090+
// eslint-disable-next-line-no-fallthrough
3091+
default:
3092+
writeStyleResourceAttributeInJS(destination, propKey, propValue);
3093+
break;
3094+
}
3095+
}
3096+
}
3097+
return null;
3098+
}
3099+
3100+
function writeStyleResourceAttributeInJS(
3101+
destination: Destination,
3102+
name: string,
3103+
value: string | boolean | number | Function | Object, // not null or undefined
3104+
): void {
3105+
let attributeName = name.toLowerCase();
3106+
let attributeValue;
3107+
switch (typeof value) {
3108+
case 'function':
3109+
case 'symbol':
3110+
return;
3111+
}
3112+
3113+
switch (name) {
3114+
// Reserved names
3115+
case 'innerHTML':
3116+
case 'dangerouslySetInnerHTML':
3117+
case 'suppressContentEditableWarning':
3118+
case 'suppressHydrationWarning':
3119+
case 'style':
3120+
// Ignored
3121+
return;
3122+
3123+
// Attribute renames
3124+
case 'className':
3125+
attributeName = 'class';
3126+
break;
3127+
3128+
// Booleans
3129+
case 'hidden':
3130+
if (value === false) {
3131+
return;
3132+
}
3133+
attributeValue = '';
3134+
break;
3135+
3136+
// Santized URLs
3137+
case 'src':
3138+
case 'href': {
3139+
if (__DEV__) {
3140+
checkAttributeStringCoercion(value, attributeName);
3141+
}
3142+
attributeValue = '' + (value: any);
3143+
sanitizeURL(attributeValue);
3144+
break;
3145+
}
3146+
default: {
3147+
if (!isAttributeNameSafe(name)) {
3148+
return;
3149+
}
3150+
}
3151+
}
3152+
3153+
if (
3154+
// shouldIgnoreAttribute
3155+
// We have already filtered out null/undefined and reserved words.
3156+
name.length > 2 &&
3157+
(name[0] === 'o' || name[0] === 'O') &&
3158+
(name[1] === 'n' || name[1] === 'N')
3159+
) {
3160+
return;
3161+
}
3162+
3163+
if (__DEV__) {
3164+
checkAttributeStringCoercion(value, attributeName);
3165+
}
3166+
attributeValue = '' + (value: any);
3167+
writeChunk(destination, arrayInterstitial);
3168+
writeChunk(
3169+
destination,
3170+
stringToChunk(escapeJSObjectForInstructionScripts(attributeName)),
3171+
);
3172+
writeChunk(destination, arrayInterstitial);
3173+
writeChunk(
3174+
destination,
3175+
stringToChunk(escapeJSObjectForInstructionScripts(attributeValue)),
3176+
);
3177+
}
3178+
3179+
// This function writes a 2D array of strings to be embedded in an attribute
3180+
// value and read with JSON.parse in ReactDOMServerExternalRuntime.js
3181+
// E.g.
3182+
// [["JSON_escaped_string1", "JSON_escaped_string2"]]
3183+
function writeStyleResourceDependenciesInAttr(
30303184
destination: Destination,
30313185
boundaryResources: BoundaryResources,
3032-
context: EmbeddingContext,
30333186
): void {
30343187
writeChunk(destination, arrayFirstOpenBracket);
30353188

@@ -3040,17 +3193,16 @@ function writeStyleResourceDependencies(
30403193
// should be ready before content is shown on the client
30413194
} else if (resource.flushed) {
30423195
writeChunk(destination, nextArrayOpenBrackChunk);
3043-
writeStyleResourceDependencyHrefOnly(destination, resource.href, context);
3196+
writeStyleResourceDependencyHrefOnlyInAttr(destination, resource.href);
30443197
writeChunk(destination, arrayCloseBracket);
30453198
nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
30463199
} else {
30473200
writeChunk(destination, nextArrayOpenBrackChunk);
3048-
writeStyleResourceDependency(
3201+
writeStyleResourceDependencyInAttr(
30493202
destination,
30503203
resource.href,
30513204
resource.precedence,
30523205
resource.props,
3053-
context,
30543206
);
30553207
writeChunk(destination, arrayCloseBracket);
30563208
nextArrayOpenBrackChunk = arraySubsequentOpenBracket;
@@ -3062,41 +3214,48 @@ function writeStyleResourceDependencies(
30623214
writeChunk(destination, arrayCloseBracket);
30633215
}
30643216

3065-
function writeStyleResourceDependencyHrefOnly(
3217+
/* Helper functions */
3218+
function writeStyleResourceDependencyHrefOnlyInAttr(
30663219
destination: Destination,
30673220
href: string,
3068-
context: EmbeddingContext,
30693221
) {
30703222
// We should actually enforce this earlier when the resource is created but for
30713223
// now we make sure we are actually dealing with a string here.
30723224
if (__DEV__) {
30733225
checkAttributeStringCoercion(href, 'href');
30743226
}
30753227
const coercedHref = '' + (href: any);
3076-
writeStyleResourceObject(destination, coercedHref, context);
3228+
writeChunk(
3229+
destination,
3230+
stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))),
3231+
);
30773232
}
30783233

3079-
function writeStyleResourceDependency(
3234+
function writeStyleResourceDependencyInAttr(
30803235
destination: Destination,
30813236
href: string,
30823237
precedence: string,
30833238
props: Object,
3084-
context: EmbeddingContext,
30853239
) {
30863240
if (__DEV__) {
30873241
checkAttributeStringCoercion(href, 'href');
30883242
}
30893243
const coercedHref = '' + (href: any);
30903244
sanitizeURL(coercedHref);
3091-
writeStyleResourceObject(destination, coercedHref, context);
3245+
writeChunk(
3246+
destination,
3247+
stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))),
3248+
);
30923249

30933250
if (__DEV__) {
30943251
checkAttributeStringCoercion(precedence, 'precedence');
30953252
}
30963253
const coercedPrecedence = '' + (precedence: any);
30973254
writeChunk(destination, arrayInterstitial);
3098-
3099-
writeStyleResourceObject(destination, coercedPrecedence, context);
3255+
writeChunk(
3256+
destination,
3257+
stringToChunk(escapeTextForBrowser(JSON.stringify(coercedPrecedence))),
3258+
);
31003259

31013260
for (const propKey in props) {
31023261
if (hasOwnProperty.call(props, propKey)) {
@@ -3119,19 +3278,18 @@ function writeStyleResourceDependency(
31193278
);
31203279
// eslint-disable-next-line-no-fallthrough
31213280
default:
3122-
writeStyleResourceAttribute(destination, propKey, propValue, context);
3281+
writeStyleResourceAttributeInAttr(destination, propKey, propValue);
31233282
break;
31243283
}
31253284
}
31263285
}
31273286
return null;
31283287
}
31293288

3130-
function writeStyleResourceAttribute(
3289+
function writeStyleResourceAttributeInAttr(
31313290
destination: Destination,
31323291
name: string,
31333292
value: string | boolean | number | Function | Object, // not null or undefined
3134-
context: EmbeddingContext,
31353293
): void {
31363294
let attributeName = name.toLowerCase();
31373295
let attributeValue;
@@ -3196,7 +3354,13 @@ function writeStyleResourceAttribute(
31963354
}
31973355
attributeValue = '' + (value: any);
31983356
writeChunk(destination, arrayInterstitial);
3199-
writeStyleResourceObject(destination, attributeName, context);
3357+
writeChunk(
3358+
destination,
3359+
stringToChunk(escapeTextForBrowser(JSON.stringify(attributeName))),
3360+
);
32003361
writeChunk(destination, arrayInterstitial);
3201-
writeStyleResourceObject(destination, attributeValue, context);
3362+
writeChunk(
3363+
destination,
3364+
stringToChunk(escapeTextForBrowser(JSON.stringify(attributeValue))),
3365+
);
32023366
}

0 commit comments

Comments
 (0)