@@ -13,7 +13,7 @@ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js';
13
13
const { Dispatcher} = ReactDOMSharedInternals ;
14
14
import { DOCUMENT_NODE } from '../shared/HTMLNodeType' ;
15
15
import {
16
- validateUnmatchedLinkResourceProps ,
16
+ warnOnMissingHrefAndRel ,
17
17
validatePreloadResourceDifference ,
18
18
validateURLKeyedUpdatedProps ,
19
19
validateStyleResourceDifference ,
@@ -54,7 +54,7 @@ type StyleProps = {
54
54
'data-precedence' : string ,
55
55
[ string ] : mixed ,
56
56
} ;
57
- export type StyleResource = {
57
+ type StyleResource = {
58
58
type : 'style' ,
59
59
60
60
// Ref count for resource
@@ -79,7 +79,7 @@ type ScriptProps = {
79
79
src : string ,
80
80
[ string ] : mixed ,
81
81
} ;
82
- export type ScriptResource = {
82
+ type ScriptResource = {
83
83
type : 'script' ,
84
84
src : string ,
85
85
props : ScriptProps ,
@@ -88,12 +88,10 @@ export type ScriptResource = {
88
88
root : FloatRoot ,
89
89
} ;
90
90
91
- export type HeadResource = TitleResource | MetaResource ;
92
-
93
91
type TitleProps = {
94
92
[ string ] : mixed ,
95
93
} ;
96
- export type TitleResource = {
94
+ type TitleResource = {
97
95
type : 'title' ,
98
96
props : TitleProps ,
99
97
@@ -105,7 +103,7 @@ export type TitleResource = {
105
103
type MetaProps = {
106
104
[ string ] : mixed ,
107
105
} ;
108
- export type MetaResource = {
106
+ type MetaResource = {
109
107
type : 'meta' ,
110
108
matcher : string ,
111
109
property : ?string ,
@@ -117,8 +115,23 @@ export type MetaResource = {
117
115
root : Document ,
118
116
} ;
119
117
118
+ type LinkProps = {
119
+ href : string ,
120
+ rel : string ,
121
+ [ string ] : mixed ,
122
+ } ;
123
+ type LinkResource = {
124
+ type : 'link' ,
125
+ props : LinkProps ,
126
+
127
+ count : number ,
128
+ instance : ?Element ,
129
+ root : Document ,
130
+ } ;
131
+
120
132
type Props = { [ string ] : mixed } ;
121
133
134
+ type HeadResource = TitleResource | MetaResource | LinkResource ;
122
135
type Resource = StyleResource | ScriptResource | PreloadResource | HeadResource ;
123
136
124
137
export type RootResources = {
@@ -617,8 +630,30 @@ export function getResource(
617
630
return null ;
618
631
}
619
632
default : {
633
+ const { href , sizes , media } = pendingProps ;
634
+ if ( typeof rel === 'string' && typeof href === 'string' ) {
635
+ const sizeKey =
636
+ '::sizes :' + ( typeof sizes === 'string' ? sizes : '' ) ;
637
+ const mediaKey =
638
+ '::media:' + ( typeof media === 'string' ? media : '' ) ;
639
+ const key = 'rel:' + rel + '::href:' + href + sizeKey + mediaKey ;
640
+ const headRoot = getDocumentFromRoot ( resourceRoot ) ;
641
+ const headResources = getResourcesFromRoot ( headRoot ) . head ;
642
+ let resource = headResources . get ( key ) ;
643
+ if ( ! resource ) {
644
+ resource = {
645
+ type : 'link' ,
646
+ props : Object . assign ( { } , pendingProps ) ,
647
+ count : 0 ,
648
+ instance : null ,
649
+ root : headRoot ,
650
+ } ;
651
+ headResources . set ( key , resource ) ;
652
+ }
653
+ return resource ;
654
+ }
620
655
if ( __DEV__ ) {
621
- validateUnmatchedLinkResourceProps ( pendingProps , currentProps ) ;
656
+ warnOnMissingHrefAndRel ( pendingProps , currentProps ) ;
622
657
}
623
658
return null ;
624
659
}
@@ -710,6 +745,7 @@ function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
710
745
export function acquireResource ( resource : Resource ) : Instance {
711
746
switch ( resource . type ) {
712
747
case 'title' :
748
+ case 'link' :
713
749
case 'meta' : {
714
750
return acquireHeadResource ( resource ) ;
715
751
}
@@ -732,6 +768,7 @@ export function acquireResource(resource: Resource): Instance {
732
768
733
769
export function releaseResource ( resource : Resource ) : void {
734
770
switch ( resource . type ) {
771
+ case 'link ':
735
772
case 'title ':
736
773
case 'meta': {
737
774
return releaseHeadResource ( resource ) ;
@@ -1050,6 +1087,41 @@ function acquireHeadResource(resource: HeadResource): Instance {
1050
1087
insertResourceInstanceBefore ( root , instance , insertBefore ) ;
1051
1088
break ;
1052
1089
}
1090
+ case 'link' : {
1091
+ const linkProps : LinkProps = ( props : any ) ;
1092
+ const limitedEscapedRel = escapeSelectorAttributeValueInsideDoubleQuotes (
1093
+ linkProps . rel ,
1094
+ ) ;
1095
+ const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes (
1096
+ linkProps . href ,
1097
+ ) ;
1098
+ let selector = `link[rel="${ limitedEscapedRel } "][href="${ limitedEscapedHref } "]` ;
1099
+ if ( typeof linkProps . sizes === 'string' ) {
1100
+ const limitedEscapedSizes = escapeSelectorAttributeValueInsideDoubleQuotes (
1101
+ linkProps . sizes ,
1102
+ ) ;
1103
+ selector += `[sizes="${ limitedEscapedSizes } "]` ;
1104
+ }
1105
+ if ( typeof linkProps . media === 'string' ) {
1106
+ const limitedEscapedMedia = escapeSelectorAttributeValueInsideDoubleQuotes (
1107
+ linkProps . media ,
1108
+ ) ;
1109
+ selector += `[media="${ limitedEscapedMedia } "]` ;
1110
+ }
1111
+ const existingEl = root . querySelector ( selector ) ;
1112
+ if ( existingEl ) {
1113
+ instance = resource . instance = existingEl ;
1114
+ markNodeAsResource ( instance ) ;
1115
+ return instance ;
1116
+ }
1117
+ instance = resource . instance = createResourceInstance (
1118
+ type ,
1119
+ props ,
1120
+ root ,
1121
+ ) ;
1122
+ insertResourceInstanceBefore ( root , instance , null ) ;
1123
+ return instance ;
1124
+ }
1053
1125
default : {
1054
1126
throw new Error (
1055
1127
`acquireHeadResource encountered a resource type it did not expect: "${ type } ". This is a bug in React.` ,
@@ -1265,26 +1337,27 @@ export function isHostResourceType(type: string, props: Props): boolean {
1265
1337
return true ;
1266
1338
}
1267
1339
case 'link ': {
1340
+ const { onLoad , onError } = props ;
1341
+ if ( onLoad || onError ) {
1342
+ return false ;
1343
+ }
1268
1344
switch ( props . rel ) {
1269
1345
case 'stylesheet ': {
1270
1346
if ( __DEV__ ) {
1271
1347
validateLinkPropsForStyleResource ( props ) ;
1272
1348
}
1273
- const { href , precedence , onLoad , onError , disabled } = props ;
1349
+ const { href , precedence , disabled } = props ;
1274
1350
return (
1275
1351
typeof href === 'string' &&
1276
1352
typeof precedence === 'string' &&
1277
- ! onLoad &&
1278
- ! onError &&
1279
1353
disabled == null
1280
1354
) ;
1281
1355
}
1282
- case ' preload ' : {
1283
- const { href , onLoad , onError } = props ;
1284
- return ! onLoad && ! onError && typeof href === 'string' ;
1356
+ default : {
1357
+ const { rel , href } = props ;
1358
+ return typeof href === 'string' && typeof rel === 'string' ;
1285
1359
}
1286
1360
}
1287
- return false ;
1288
1361
}
1289
1362
case 'script' : {
1290
1363
// We don't validate because it is valid to use async with onLoad/onError unlike combining
0 commit comments