Skip to content

Commit 618388b

Browse files
authored
[Float] Support script preloads (#25432)
* support script preloads * gates
1 parent 65b3449 commit 618388b

4 files changed

Lines changed: 147 additions & 51 deletions

File tree

‎packages/react-dom-bindings/src/client/ReactDOMFloatClient.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostCo
2929

3030
// The resource types we support. currently they match the form for the as argument.
3131
// In the future this may need to change, especially when modules / scripts are supported
32-
typeResourceType='style'|'font';
32+
typeResourceType='style'|'font'|'script';
3333

3434
typePreloadProps={
3535
rel: 'preload',
@@ -150,7 +150,7 @@ function getDocumentFromRoot(root: FloatRoot): Document {
150150
// ReactDOM.Preload
151151
// --------------------------------------
152152
typePreloadAs=ResourceType;
153-
typePreloadOptions={as: PreloadAs,crossOrigin?: string};
153+
typePreloadOptions={as: PreloadAs,crossOrigin?: string,integrity?: string};
154154
functionpreload(href: string,options: PreloadOptions){
155155
if(__DEV__){
156156
validatePreloadArguments(href,options);
@@ -194,6 +194,7 @@ function preloadPropsFromPreloadOptions(
194194
rel: 'preload',
195195
as,
196196
crossOrigin: as==='font' ? '' : options.crossOrigin,
197+
integrity: options.integrity,
197198
};
198199
}
199200

@@ -832,7 +833,7 @@ export function isHostResourceType(type: string, props: Props): boolean {
832833
}
833834

834835
functionisResourceAsType(as: mixed): boolean{
835-
returnas==='style'||as==='font';
836+
returnas==='style'||as==='font'||as==='script';
836837
}
837838

838839
// When passing user input into querySelector(All) the embedded string must not alter

‎packages/react-dom-bindings/src/server/ReactDOMFloatServer.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919

2020
typeProps={[string]: mixed};
2121

22-
typeResourceType='style'|'font';
22+
typeResourceType='style'|'font'|'script';
2323

2424
typePreloadProps={
2525
rel: 'preload',
@@ -123,7 +123,7 @@ export const ReactDOMServerDispatcher = {
123123
};
124124

125125
typePreloadAs=ResourceType;
126-
typePreloadOptions={as: PreloadAs,crossOrigin?: string};
126+
typePreloadOptions={as: PreloadAs,crossOrigin?: string,integrity?: string};
127127
functionpreload(href: string,options: PreloadOptions){
128128
if(!currentResources){
129129
// While we expect that preload calls are primarily going to be observed
@@ -248,6 +248,7 @@ function preloadPropsFromPreloadOptions(
248248
rel: 'preload',
249249
as,
250250
crossOrigin: as==='font' ? '' : options.crossOrigin,
251+
integrity: options.integrity,
251252
};
252253
}
253254

@@ -526,6 +527,7 @@ export function resourcesFromLink(props: Props): boolean {
526527
returnfalse;
527528
}
528529
switch(as){
530+
case 'script':
529531
case 'style':
530532
case 'font': {
531533
if(__DEV__){

‎packages/react-dom-bindings/src/shared/ReactDOMResourceValidation.js‎

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,42 @@ export function validateUnmatchedLinkResourceProps(
1616
currentProps: ?Props,
1717
){
1818
if(__DEV__){
19-
if(pendingProps.rel!=='font'&&pendingProps.rel!=='style'){
20-
if(currentProps!=null){
21-
constoriginalResourceName=
22-
typeofcurrentProps.href==='string'
23-
? `Resource with href "${currentProps.href}"`
24-
: 'Resource';
25-
constoriginalRelStatement=getValueDescriptorExpectingEnumForWarning(
26-
currentProps.rel,
27-
);
28-
constpendingRelStatement=getValueDescriptorExpectingEnumForWarning(
29-
pendingProps.rel,
30-
);
31-
constpendingHrefStatement=
32-
typeofpendingProps.href==='string'
33-
? ` and the updated href is "${pendingProps.href}"`
34-
: '';
35-
console.error(
36-
'A <link> previously rendered as a %s but was updated with a rel type that is not'+
37-
' valid for a Resource type. Generally Resources are not expected to ever have updated'+
38-
' props however in some limited circumstances it can be valid when changing the href.'+
39-
' When React encounters props that invalidate the Resource it is the same as not rendering'+
40-
' a Resource at all. valid rel types for Resources are "font" and "style". The previous'+
41-
' rel for this instance was %s. The updated rel is %s%s.',
42-
originalResourceName,
43-
originalRelStatement,
44-
pendingRelStatement,
45-
pendingHrefStatement,
46-
);
47-
}else{
48-
constpendingRelStatement=getValueDescriptorExpectingEnumForWarning(
49-
pendingProps.rel,
50-
);
51-
console.error(
52-
'A <link> is rendering as a Resource but has an invalid rel property. The rel encountered is %s.'+
53-
' This is a bug in React.',
54-
pendingRelStatement,
55-
);
56-
}
19+
if(currentProps!=null){
20+
constoriginalResourceName=
21+
typeofcurrentProps.href==='string'
22+
? `Resource with href "${currentProps.href}"`
23+
: 'Resource';
24+
constoriginalRelStatement=getValueDescriptorExpectingEnumForWarning(
25+
currentProps.rel,
26+
);
27+
constpendingRelStatement=getValueDescriptorExpectingEnumForWarning(
28+
pendingProps.rel,
29+
);
30+
constpendingHrefStatement=
31+
typeofpendingProps.href==='string'
32+
? ` and the updated href is "${pendingProps.href}"`
33+
: '';
34+
console.error(
35+
'A <link> previously rendered as a %s but was updated with a rel type that is not'+
36+
' valid for a Resource type. Generally Resources are not expected to ever have updated'+
37+
' props however in some limited circumstances it can be valid when changing the href.'+
38+
' When React encounters props that invalidate the Resource it is the same as not rendering'+
39+
' a Resource at all. valid rel types for Resources are "stylesheet" and "preload". The previous'+
40+
' rel for this instance was %s. The updated rel is %s%s.',
41+
originalResourceName,
42+
originalRelStatement,
43+
pendingRelStatement,
44+
pendingHrefStatement,
45+
);
46+
}else{
47+
constpendingRelStatement=getValueDescriptorExpectingEnumForWarning(
48+
pendingProps.rel,
49+
);
50+
console.error(
51+
'A <link> is rendering as a Resource but has an invalid rel property. The rel encountered is %s.'+
52+
' This is a bug in React.',
53+
pendingRelStatement,
54+
);
5755
}
5856
}
5957
}
@@ -517,6 +515,7 @@ export function validatePreloadArguments(href: mixed, options: mixed) {
517515
}
518516
break;
519517
}
518+
case'script':
520519
case'style': {
521520
break;
522521
}
@@ -529,7 +528,7 @@ export function validatePreloadArguments(href: mixed, options: mixed) {
529528
' Please use one of the following valid values instead: %s. The href for the preload call where this'+
530529
' warning originated is "%s".',
531530
typeOfAs,
532-
'"style" and "font"',
531+
'"style", "font", or "script"',
533532
href,
534533
);
535534
}
@@ -557,7 +556,6 @@ export function validatePreinitArguments(href: mixed, options: mixed) {
557556
}else{
558557
constas=options.as;
559558
switch(as){
560-
case'font':
561559
case'style': {
562560
break;
563561
}

‎packages/react-dom/src/__tests__/ReactDOMFloat-test.js‎

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ describe('ReactDOMFloat', () => {
270270
' valid for a Resource type. Generally Resources are not expected to ever have updated'+
271271
' props however in some limited circumstances it can be valid when changing the href.'+
272272
' When React encounters props that invalidate the Resource it is the same as not rendering'+
273-
' a Resource at all. valid rel types for Resources are "font" and "style". The previous'+
273+
' a Resource at all. valid rel types for Resources are "stylesheet" and "preload". The previous'+
274274
' rel for this instance was "stylesheet". The updated rel is "author" and the updated href is "bar".',
275275
);
276276
expect(getVisibleChildren(document)).toEqual(
@@ -407,6 +407,97 @@ describe('ReactDOMFloat', () => {
407407
</html>,
408408
);
409409
});
410+
411+
// @gate enableFloat
412+
it('supports script preloads',async()=>{
413+
functionServerApp(){
414+
ReactDOM.preload('foo',{as: 'script',integrity: 'foo hash'});
415+
ReactDOM.preload('bar',{
416+
as: 'script',
417+
crossOrigin: 'use-credentials',
418+
integrity: 'bar hash',
419+
});
420+
return(
421+
<html>
422+
<linkrel="preload"href="baz"as="script"/>
423+
<head>
424+
<title>hi</title>
425+
</head>
426+
<body>foo</body>
427+
</html>
428+
);
429+
}
430+
functionClientApp(){
431+
ReactDOM.preload('foo',{as: 'script',integrity: 'foo hash'});
432+
ReactDOM.preload('qux',{as: 'script'});
433+
return(
434+
<html>
435+
<head>
436+
<title>hi</title>
437+
</head>
438+
<body>foo</body>
439+
<link
440+
rel="preload"
441+
href="quux"
442+
as="script"
443+
crossOrigin=""
444+
integrity="quux hash"
445+
/>
446+
</html>
447+
);
448+
}
449+
450+
awaitactIntoEmptyDocument(()=>{
451+
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<ServerApp/>);
452+
pipe(writable);
453+
});
454+
expect(getVisibleChildren(document)).toEqual(
455+
<html>
456+
<head>
457+
<linkrel="preload"as="script"href="foo"integrity="foo hash"/>
458+
<link
459+
rel="preload"
460+
as="script"
461+
href="bar"
462+
crossorigin="use-credentials"
463+
integrity="bar hash"
464+
/>
465+
<linkrel="preload"as="script"href="baz"/>
466+
<title>hi</title>
467+
</head>
468+
<body>foo</body>
469+
</html>,
470+
);
471+
472+
ReactDOMClient.hydrateRoot(document,<ClientApp/>);
473+
expect(Scheduler).toFlushWithoutYielding();
474+
475+
expect(getVisibleChildren(document)).toEqual(
476+
<html>
477+
<head>
478+
<linkrel="preload"as="script"href="foo"integrity="foo hash"/>
479+
<link
480+
rel="preload"
481+
as="script"
482+
href="bar"
483+
crossorigin="use-credentials"
484+
integrity="bar hash"
485+
/>
486+
<linkrel="preload"as="script"href="baz"/>
487+
<title>hi</title>
488+
<linkrel="preload"as="script"href="qux"/>
489+
<link
490+
rel="preload"
491+
as="script"
492+
href="quux"
493+
crossorigin=""
494+
integrity="quux hash"
495+
/>
496+
</head>
497+
<body>foo</body>
498+
</html>,
499+
);
500+
});
410501
});
411502

412503
describe('ReactDOM.preinit as style',()=>{
@@ -2885,7 +2976,11 @@ describe('ReactDOMFloat', () => {
28852976
(mockError,scenarioNumber)=>{
28862977
if(__DEV__){
28872978
expect(mockError.mock.calls[scenarioNumber]).toEqual(
2888-
makeArgs('undefined','"style" and "font"','foo'),
2979+
makeArgs(
2980+
'undefined',
2981+
'"style", "font", or "script"',
2982+
'foo',
2983+
),
28892984
);
28902985
}else{
28912986
expect(mockError).not.toHaveBeenCalled();
@@ -2898,7 +2993,7 @@ describe('ReactDOMFloat', () => {
28982993
(mockError,scenarioNumber)=>{
28992994
if(__DEV__){
29002995
expect(mockError.mock.calls[scenarioNumber]).toEqual(
2901-
makeArgs('null','"style" and "font"','bar'),
2996+
makeArgs('null','"style", "font", or "script"','bar'),
29022997
);
29032998
}else{
29042999
expect(mockError).not.toHaveBeenCalled();
@@ -2913,7 +3008,7 @@ describe('ReactDOMFloat', () => {
29133008
expect(mockError.mock.calls[scenarioNumber]).toEqual(
29143009
makeArgs(
29153010
'something with type "number"',
2916-
'"style" and "font"',
3011+
'"style", "font", or "script"',
29173012
'baz',
29183013
),
29193014
);
@@ -2930,7 +3025,7 @@ describe('ReactDOMFloat', () => {
29303025
expect(mockError.mock.calls[scenarioNumber]).toEqual(
29313026
makeArgs(
29323027
'something with type "object"',
2933-
'"style" and "font"',
3028+
'"style", "font", or "script"',
29343029
'qux',
29353030
),
29363031
);
@@ -2945,7 +3040,7 @@ describe('ReactDOMFloat', () => {
29453040
(mockError,scenarioNumber)=>{
29463041
if(__DEV__){
29473042
expect(mockError.mock.calls[scenarioNumber]).toEqual(
2948-
makeArgs('"bar"','"style" and "font"','quux'),
3043+
makeArgs('"bar"','"style", "font", or "script"','quux'),
29493044
);
29503045
}else{
29513046
expect(mockError).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)