@@ -228,6 +228,9 @@ export type ResumableState = {
228228 anonymous : { [ key : string ] : Exists } ,
229229 credentials : { [ key : string ] : Exists } ,
230230 } ,
231+ imageResources : {
232+ [ key : string ] : Preloaded ,
233+ } ,
231234 styleResources : {
232235 [ key : string ] : Exists | Preloaded | PreloadedWithCredentials ,
233236 } ,
@@ -566,6 +569,7 @@ export function createResumableState(
566569 anonymous : { } ,
567570 credentials : { } ,
568571 } ,
572+ imageResources : { } ,
569573 styleResources : { } ,
570574 scriptResources : { } ,
571575 moduleUnknownResources : { } ,
@@ -2588,43 +2592,58 @@ function pushImg(
25882592 // resumableState.
25892593 const sizes = typeof props . sizes === 'string' ? props . sizes : undefined ;
25902594 const key = getImageResourceKey ( src , srcSet , sizes ) ;
2591- const resources : ResumableState [ 'unknownResources' ] [ 'asType' ] =
2592- resumableState . unknownResources . hasOwnProperty ( 'image' )
2593- ? resumableState . unknownResources . image
2594- : ( resumableState . unknownResources . image = { } ) ;
25952595
2596- let resource : void | Resource ;
2597- if ( ! resources . hasOwnProperty ( key ) ) {
2598- const preloadProps : PreloadProps = {
2599- rel : 'preload ',
2600- as : 'image' ,
2601- // There is a bug in Safari where imageSrcSet is not respected on preload links
2602- // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
2603- // This harms older browers that do not support imageSrcSet by making their preloads not work
2604- // but this population is shrinking fast and is already small so we accept this tradeoff.
2605- href : srcSet ? undefined : src ,
2606- imageSrcSet : srcSet ,
2607- imageSizes : sizes ,
2608- crossOrigin : props . crossOrigin ,
2609- integrity : props . integrity ,
2610- type : props . type ,
2611- fetchPriority : props . fetchPriority ,
2612- referrerPolicy : props . referrerPolicy ,
2613- } ;
2614- resource = [ ] ;
2615- resources [ key ] = PRELOAD_SIGIL ;
2616- pushLinkImpl ( resource , preloadProps ) ;
2617- } else {
2618- resource = renderState . preloads . images . get ( key ) ;
2619- }
2596+ const promotablePreloads = renderState . preloads . images ;
2597+
2598+ let resource = promotablePreloads . get ( key ) ;
26202599 if ( resource ) {
2600+ // We consider whether this preload can be promoted to higher priority flushing queue.
2601+ // The only time a resource will exist here is if it was created during this render
2602+ // and was not already in the high priority queue.
2603+ if (
2604+ props . fetchPriority === 'high' ||
2605+ renderState . highImagePreloads . size < 10
2606+ ) {
2607+ // Delete the resource from the map since we are promoting it and don't want to
2608+ // reenter this branch in a second pass for duplicate img hrefs.
2609+ promotablePreloads . delete ( key ) ;
2610+
2611+ // $FlowFixMe - Flow should understand that this is a Resource if the condition was true
2612+ renderState . highImagePreloads . add ( resource ) ;
2613+ }
2614+ } else if ( ! resumableState . imageResources . hasOwnProperty ( key ) ) {
2615+ // We must construct a new preload resource
2616+ resumableState . imageResources [ key ] = PRELOAD_SIGIL ;
2617+ resource = [ ] ;
2618+ pushLinkImpl (
2619+ resource ,
2620+ ( {
2621+ rel : 'preload' ,
2622+ as : 'image' ,
2623+ // There is a bug in Safari where imageSrcSet is not respected on preload links
2624+ // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
2625+ // This harms older browers that do not support imageSrcSet by making their preloads not work
2626+ // but this population is shrinking fast and is already small so we accept this tradeoff.
2627+ href : srcSet ? undefined : src ,
2628+ imageSrcSet : srcSet ,
2629+ imageSizes : sizes ,
2630+ crossOrigin : props . crossOrigin ,
2631+ integrity : props . integrity ,
2632+ type : props . type ,
2633+ fetchPriority : props . fetchPriority ,
2634+ referrerPolicy : props . referrerPolicy ,
2635+ } : PreloadProps ) ,
2636+ ) ;
26212637 if (
26222638 props . fetchPriority === 'high' ||
26232639 renderState . highImagePreloads . size < 10
26242640 ) {
26252641 renderState . highImagePreloads . add ( resource ) ;
26262642 } else {
26272643 renderState . bulkPreloads . add ( resource ) ;
2644+ // We can bump the priority up if the same img is rendered later
2645+ // with fetchPriority="high"
2646+ promotablePreloads . set ( key , resource ) ;
26282647 }
26292648 }
26302649 }
@@ -5057,8 +5076,8 @@ function getResourceKey(href: string): string {
50575076
50585077function getImageResourceKey (
50595078 href : string ,
5060- imageSrcSet : ?string ,
5061- imageSizes : ?string ,
5079+ imageSrcSet ? : ?string ,
5080+ imageSizes ? : ?string ,
50625081) : string {
50635082 if ( imageSrcSet ) {
50645083 return imageSrcSet + '\n' + ( imageSizes || '' ) ;
@@ -5147,20 +5166,48 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
51475166 const resumableState = getResumableState ( request ) ;
51485167 const renderState = getRenderState ( request ) ;
51495168 if ( as && href ) {
5150- options = options || { } ;
5151- let key : string ;
5152-
5153- if ( as === 'image' ) {
5154- // For image preloads the key contains either the imageSrcSet + imageSizes or the href but not
5155- // both. This is to prevent identical calls with the same srcSet and sizes to be duplicated
5156- // by varying the href. this is an edge case but it is the most correct behavior.
5157- key = getImageResourceKey ( href , options . imageSrcSet , options . imageSizes ) ;
5158- } else {
5159- key = getResourceKey ( href ) ;
5160- }
5161-
51625169 switch ( as ) {
5170+ case 'image' : {
5171+ let imageSrcSet , imageSizes , fetchPriority ;
5172+ if ( options ) {
5173+ imageSrcSet = options . imageSrcSet ;
5174+ imageSizes = options . imageSizes ;
5175+ fetchPriority = options . fetchPriority ;
5176+ }
5177+ const key = getImageResourceKey ( href , imageSrcSet , imageSizes ) ;
5178+ if ( resumableState . imageResources . hasOwnProperty ( key ) ) {
5179+ // we can return if we already have this resource
5180+ return ;
5181+ }
5182+ resumableState . imageResources [ key ] = PRELOAD_SIGIL ;
5183+ const resource = ( [ ] : Resource ) ;
5184+ pushLinkImpl (
5185+ resource ,
5186+ Object . assign (
5187+ ( {
5188+ rel : 'preload' ,
5189+ // There is a bug in Safari where imageSrcSet is not respected on preload links
5190+ // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
5191+ // This harms older browers that do not support imageSrcSet by making their preloads not work
5192+ // but this population is shrinking fast and is already small so we accept this tradeoff.
5193+ href : imageSrcSet ? undefined : href ,
5194+ as,
5195+ } : PreloadAsProps ) ,
5196+ options ,
5197+ ) ,
5198+ ) ;
5199+ if ( fetchPriority === 'high' ) {
5200+ renderState . highImagePreloads . add ( resource ) ;
5201+ } else {
5202+ renderState . bulkPreloads . add ( resource ) ;
5203+ // Stash the resource in case we need to promote it to higher priority
5204+ // when an img tag is rendered
5205+ renderState . preloads . images . set ( key , resource ) ;
5206+ }
5207+ break ;
5208+ }
51635209 case 'style' : {
5210+ const key = getResourceKey ( href ) ;
51645211 if ( resumableState . styleResources . hasOwnProperty ( key ) ) {
51655212 // we can return if we already have this resource
51665213 return ;
@@ -5171,15 +5218,17 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
51715218 Object . assign ( ( { rel : 'preload' , href, as} : PreloadAsProps ) , options ) ,
51725219 ) ;
51735220 resumableState . styleResources [ key ] =
5174- typeof options . crossOrigin === 'string' ||
5175- typeof options . integrity === 'string'
5221+ options &&
5222+ ( typeof options . crossOrigin === 'string' ||
5223+ typeof options . integrity === 'string' )
51765224 ? [ options . crossOrigin , options . integrity ]
51775225 : PRELOAD_SIGIL ;
51785226 renderState . preloads . stylesheets . set ( key , resource ) ;
51795227 renderState . bulkPreloads . add ( resource ) ;
51805228 break ;
51815229 }
51825230 case 'script' : {
5231+ const key = getResourceKey ( href ) ;
51835232 if ( resumableState . scriptResources . hasOwnProperty ( key ) ) {
51845233 // we can return if we already have this resource
51855234 return ;
@@ -5192,13 +5241,15 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
51925241 Object . assign ( ( { rel : 'preload' , href, as} : PreloadAsProps ) , options ) ,
51935242 ) ;
51945243 resumableState . scriptResources [ key ] =
5195- typeof options . crossOrigin === 'string' ||
5196- typeof options . integrity === 'string'
5244+ options &&
5245+ ( typeof options . crossOrigin === 'string' ||
5246+ typeof options . integrity === 'string' )
51975247 ? [ options . crossOrigin , options . integrity ]
51985248 : PRELOAD_SIGIL ;
51995249 break ;
52005250 }
52015251 default : {
5252+ const key = getResourceKey ( href ) ;
52025253 const hasAsType = resumableState . unknownResources . hasOwnProperty ( as ) ;
52035254 let resources ;
52045255 if ( hasAsType ) {
@@ -5224,18 +5275,6 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
52245275 case 'font' :
52255276 renderState . fontPreloads . add ( resource ) ;
52265277 break ;
5227- case 'image' :
5228- renderState . preloads . images . set ( key , resource ) ;
5229- if ( options . imageSrcSet ) {
5230- // There is a bug in Safari where imageSrcSet is not respected on preload links
5231- // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
5232- // This harms older browers that do not support imageSrcSet by making their preloads not work
5233- // but this population is shrinking fast and is already small so we accept this tradeoff.
5234- props . href = null ;
5235- }
5236- if ( options . fetchPriority === 'high' ) {
5237- renderState . highImagePreloads . add ( resource ) ;
5238- }
52395278 // intentional fall through
52405279 default :
52415280 renderState . bulkPreloads . add ( resource ) ;
0 commit comments