@@ -2222,10 +2222,10 @@ function pushMeta(
22222222 target : Array < Chunk | PrecomputedChunk > ,
22232223 props : Object ,
22242224 renderState : RenderState ,
2225- hoistableState : null | HoistableState ,
22262225 textEmbedded : boolean ,
22272226 insertionMode : InsertionMode ,
22282227 noscriptTagInScope : boolean ,
2228+ isFallback : boolean ,
22292229) : null {
22302230 if ( enableFloat ) {
22312231 if (
@@ -2241,31 +2241,26 @@ function pushMeta(
22412241 target . push ( textSeparator ) ;
22422242 }
22432243
2244- if ( typeof props . charSet === 'string' ) {
2245- return pushSelfClosing (
2246- hoistableState
2247- ? hoistableState . charsetChunks
2248- : renderState . charsetChunks ,
2249- props ,
2250- 'meta' ,
2251- ) ;
2244+ if ( isFallback ) {
2245+ // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early
2246+ // because they are likely superceded by primary content and we want to avoid needing to clean
2247+ // them up when the primary content is ready. They are never hydrated on the client anyway because
2248+ // boundaries in fallback are awaited or client render, in either case there is never hydration
2249+ return null ;
2250+ } else if ( typeof props . charSet === 'string' ) {
2251+ // "charset" Should really be config and not picked up from tags however since this is
2252+ // the only way to embed the tag today we flush it on a special queue on the Request so it
2253+ // can go before everything else. Like viewport this means that the tag will escape it's
2254+ // parent container.
2255+ return pushSelfClosing ( renderState . charsetChunks , props , 'meta' ) ;
22522256 } else if ( props . name === 'viewport' ) {
2253- // "viewport" isn't related to preconnect but it has the right priority
2254- return pushSelfClosing (
2255- hoistableState
2256- ? hoistableState . viewportChunks
2257- : renderState . viewportChunks ,
2258- props ,
2259- 'meta' ,
2260- ) ;
2257+ // "viewport" is flushed on the Request so it can go earlier that Float resources that
2258+ // might be affected by it. This means it can escape the boundary it is rendered within.
2259+ // This is a pragmatic solution to viewport being incredibly sensitive to document order
2260+ // without requiring all hoistables to be flushed too early.
2261+ return pushSelfClosing ( renderState . viewportChunks , props , 'meta' ) ;
22612262 } else {
2262- return pushSelfClosing (
2263- hoistableState
2264- ? hoistableState . hoistableChunks
2265- : renderState . hoistableChunks ,
2266- props ,
2267- 'meta' ,
2268- ) ;
2263+ return pushSelfClosing ( renderState . hoistableChunks , props , 'meta' ) ;
22692264 }
22702265 }
22712266 } else {
@@ -2282,6 +2277,7 @@ function pushLink(
22822277 textEmbedded : boolean ,
22832278 insertionMode : InsertionMode ,
22842279 noscriptTagInScope : boolean ,
2280+ isFallback : boolean ,
22852281) : null {
22862282 if ( enableFloat ) {
22872283 const rel = props . rel ;
@@ -2437,10 +2433,15 @@ function pushLink(
24372433 target . push ( textSeparator ) ;
24382434 }
24392435
2440- const hoistableChunks = hoistableState
2441- ? hoistableState . hoistableChunks
2442- : renderState . hoistableChunks ;
2443- return pushLinkImpl ( hoistableChunks , props ) ;
2436+ if ( isFallback ) {
2437+ // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early
2438+ // because they are likely superceded by primary content and we want to avoid needing to clean
2439+ // them up when the primary content is ready. They are never hydrated on the client anyway because
2440+ // boundaries in fallback are awaited or client render, in either case there is never hydration
2441+ return null ;
2442+ } else {
2443+ return pushLinkImpl ( renderState . hoistableChunks , props ) ;
2444+ }
24442445 }
24452446 } else {
24462447 return pushLinkImpl ( target , props ) ;
@@ -2894,9 +2895,9 @@ function pushTitle(
28942895 target : Array < Chunk | PrecomputedChunk > ,
28952896 props : Object ,
28962897 renderState : RenderState ,
2897- hoistableState : null | HoistableState ,
28982898 insertionMode : InsertionMode ,
28992899 noscriptTagInScope : boolean ,
2900+ isFallback : boolean ,
29002901) : ReactNodeList {
29012902 if ( __DEV__ ) {
29022903 if ( hasOwnProperty . call ( props , 'children' ) ) {
@@ -2952,11 +2953,15 @@ function pushTitle(
29522953 ! noscriptTagInScope &&
29532954 props . itemProp == null
29542955 ) {
2955- const hoistableTarget = hoistableState
2956- ? hoistableState . hoistableChunks
2957- : renderState . hoistableChunks ;
2958- pushTitleImpl ( hoistableTarget , props ) ;
2959- return null ;
2956+ if ( isFallback ) {
2957+ // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early
2958+ // because they are likely superceded by primary content and we want to avoid needing to clean
2959+ // them up when the primary content is ready. They are never hydrated on the client anyway because
2960+ // boundaries in fallback are awaited or client render, in either case there is never hydration
2961+ return null ;
2962+ } else {
2963+ pushTitleImpl ( renderState . hoistableChunks , props ) ;
2964+ }
29602965 } else {
29612966 return pushTitleImpl ( target , props ) ;
29622967 }
@@ -3490,6 +3495,7 @@ export function pushStartInstance(
34903495 hoistableState : null | HoistableState ,
34913496 formatContext : FormatContext ,
34923497 textEmbedded : boolean ,
3498+ isFallback : boolean ,
34933499) : ReactNodeList {
34943500 if ( __DEV__ ) {
34953501 validateARIAProperties ( type , props ) ;
@@ -3556,9 +3562,9 @@ export function pushStartInstance(
35563562 target ,
35573563 props ,
35583564 renderState ,
3559- hoistableState ,
35603565 formatContext . insertionMode ,
35613566 ! ! ( formatContext . tagScope & NOSCRIPT_SCOPE ) ,
3567+ isFallback ,
35623568 )
35633569 : pushStartTitle ( target , props ) ;
35643570 case 'link' :
@@ -3571,6 +3577,7 @@ export function pushStartInstance(
35713577 textEmbedded ,
35723578 formatContext . insertionMode ,
35733579 ! ! ( formatContext . tagScope & NOSCRIPT_SCOPE ) ,
3580+ isFallback ,
35743581 ) ;
35753582 case 'script' :
35763583 return enableFloat
@@ -3600,10 +3607,10 @@ export function pushStartInstance(
36003607 target ,
36013608 props ,
36023609 renderState ,
3603- hoistableState ,
36043610 textEmbedded ,
36053611 formatContext . insertionMode ,
36063612 ! ! ( formatContext . tagScope & NOSCRIPT_SCOPE ) ,
3613+ isFallback ,
36073614 ) ;
36083615 // Newline eating tags
36093616 case 'listing' :
@@ -4469,7 +4476,7 @@ function hasStylesToHoist(stylesheet: StylesheetResource): boolean {
44694476 return false ;
44704477}
44714478
4472- export function writeHoistablesForPartialBoundary (
4479+ export function writeHoistablesForBoundary (
44734480 destination : Destination ,
44744481 hoistableState : HoistableState ,
44754482 renderState : RenderState ,
@@ -4495,57 +4502,6 @@ export function writeHoistablesForPartialBoundary(
44954502 return destinationHasCapacity ;
44964503}
44974504
4498- export function writeHoistablesForCompletedBoundary (
4499- destination : Destination ,
4500- hoistableState : HoistableState ,
4501- renderState : RenderState ,
4502- ) : boolean {
4503- // Reset these on each invocation, they are only safe to read in this function
4504- currentlyRenderingBoundaryHasStylesToHoist = false ;
4505- destinationHasCapacity = true ;
4506-
4507- // Flush style tags for each precedence this boundary depends on
4508- hoistableState . styles . forEach ( flushStyleTagsLateForBoundary , destination ) ;
4509-
4510- // Determine if this boundary has stylesheets that need to be awaited upon completion
4511- hoistableState . stylesheets . forEach ( hasStylesToHoist ) ;
4512-
4513- // Flush Hoistable Elements
4514- let i ;
4515- const charsetChunks = hoistableState . charsetChunks ;
4516- for ( i = 0 ; i < charsetChunks . length - 1 ; i ++ ) {
4517- writeChunk ( destination , charsetChunks [ i ] ) ;
4518- }
4519- if ( i < charsetChunks . length ) {
4520- destinationHasCapacity = writeChunkAndReturn ( destination , charsetChunks [ i ] ) ;
4521- }
4522- const viewportChunks = hoistableState . viewportChunks ;
4523- for ( i = 0 ; i < viewportChunks . length - 1 ; i ++ ) {
4524- writeChunk ( destination , charsetChunks [ i ] ) ;
4525- }
4526- if ( i < viewportChunks . length ) {
4527- destinationHasCapacity = writeChunkAndReturn (
4528- destination ,
4529- viewportChunks [ i ] ,
4530- ) ;
4531- }
4532- const hoistableChunks = hoistableState . hoistableChunks ;
4533- for ( i = 0 ; i < hoistableChunks . length - 1 ; i ++ ) {
4534- writeChunk ( destination , hoistableChunks [ i ] ) ;
4535- }
4536- if ( i < hoistableChunks . length ) {
4537- destinationHasCapacity = writeChunkAndReturn (
4538- destination ,
4539- hoistableChunks [ i ] ,
4540- ) ;
4541- }
4542-
4543- if ( currentlyRenderingBoundaryHasStylesToHoist ) {
4544- renderState . stylesToHoist = true ;
4545- }
4546- return destinationHasCapacity ;
4547- }
4548-
45494505function flushResource ( this : Destination , resource : Resource ) {
45504506 for ( let i = 0 ; i < resource . length ; i ++ ) {
45514507 writeChunk ( this , resource [ i ] ) ;
@@ -4741,26 +4697,35 @@ export function writePreamble(
47414697 }
47424698 hoistableChunks . length = 0 ;
47434699
4744- // Flush closing head if necessary
47454700 if ( htmlChunks && headChunks === null ) {
4746- // We have an <html> rendered but no <head> rendered. We however inserted
4747- // a <head> up above so we need to emit the </head> now. This is safe because
4748- // if the main content contained the </head> it would also have provided a
4749- // <head>. This means that all the content inside <html> is either <body> or
4750- // invalid HTML
4701+ // we have an <html> but we inserted an implicit <head> tag. We need
4702+ // to close it since the main content won't have it
47514703 writeChunk ( destination , endChunkForTag ( 'head' ) ) ;
47524704 }
47534705}
47544706
4755- // This is an opportunity to write hoistables however in the current implemention
4756- // the only hoistables that make sense to write here are Resources. Hoistable Elements
4757- // would have already been written as part of the preamble or will be written as part
4758- // of a boundary completion and thus don't need to be written here .
4707+ // We don't bother reporting backpressure at the moment because we expect to
4708+ // flush the entire preamble in a single pass. This probably should be modified
4709+ // in the future to be backpressure sensitive but that requires a larger refactor
4710+ // of the flushing code in Fizz .
47594711export function writeHoistables (
47604712 destination : Destination ,
47614713 resumableState : ResumableState ,
47624714 renderState : RenderState ,
47634715) : void {
4716+ let i = 0 ;
4717+
4718+ // Emit high priority Hoistables
4719+
4720+ // We omit charsetChunks because we have already sent the shell and if it wasn't
4721+ // already sent it is too late now.
4722+
4723+ const viewportChunks = renderState . viewportChunks ;
4724+ for ( i = 0 ; i < viewportChunks . length ; i ++ ) {
4725+ writeChunk ( destination , viewportChunks [ i ] ) ;
4726+ }
4727+ viewportChunks . length = 0 ;
4728+
47644729 renderState . preconnects . forEach ( flushResource , destination ) ;
47654730 renderState . preconnects . clear ( ) ;
47664731
@@ -4787,6 +4752,13 @@ export function writeHoistables(
47874752
47884753 renderState . bulkPreloads . forEach ( flushResource , destination ) ;
47894754 renderState . bulkPreloads . clear ( ) ;
4755+
4756+ // Write embedding hoistableChunks
4757+ const hoistableChunks = renderState . hoistableChunks ;
4758+ for ( i = 0 ; i < hoistableChunks . length ; i ++ ) {
4759+ writeChunk ( destination , hoistableChunks [ i ] ) ;
4760+ }
4761+ hoistableChunks . length = 0 ;
47904762}
47914763
47924764export function writePostamble (
@@ -5259,10 +5231,6 @@ type StylesheetResource = {
52595231export type HoistableState = {
52605232 styles : Set < StyleQueue > ,
52615233 stylesheets : Set < StylesheetResource > ,
5262- // Hoistable chunks
5263- charsetChunks : Array < Chunk | PrecomputedChunk > ,
5264- viewportChunks : Array < Chunk | PrecomputedChunk > ,
5265- hoistableChunks : Array < Chunk | PrecomputedChunk > ,
52665234} ;
52675235
52685236export type StyleQueue = {
@@ -5276,9 +5244,6 @@ export function createHoistableState(): HoistableState {
52765244 return {
52775245 styles : new Set ( ) ,
52785246 stylesheets : new Set ( ) ,
5279- charsetChunks : [ ] ,
5280- viewportChunks : [ ] ,
5281- hoistableChunks : [ ] ,
52825247 } ;
52835248}
52845249
@@ -6142,44 +6107,12 @@ function hoistStylesheetDependency(
61426107 this . stylesheets . add ( stylesheet ) ;
61436108}
61446109
6145- export function hoistToBoundary (
6110+ export function hoistHoistables (
61466111 parentState : HoistableState ,
61476112 childState : HoistableState ,
61486113) : void {
61496114 childState . styles . forEach ( hoistStyleQueueDependency , parentState ) ;
61506115 childState . stylesheets . forEach ( hoistStylesheetDependency , parentState ) ;
6151- let i ;
6152- const charsetChunks = childState . charsetChunks ;
6153- for ( i = 0 ; i < charsetChunks . length ; i ++ ) {
6154- parentState . charsetChunks . push ( charsetChunks [ i ] ) ;
6155- }
6156- const viewportChunks = childState . viewportChunks ;
6157- for ( i = 0 ; i < charsetChunks . length ; i ++ ) {
6158- parentState . viewportChunks . push ( viewportChunks [ i ] ) ;
6159- }
6160- const hoistableChunks = childState . hoistableChunks ;
6161- for ( i = 0 ; i < hoistableChunks . length ; i ++ ) {
6162- parentState . hoistableChunks . push ( hoistableChunks [ i ] ) ;
6163- }
6164- }
6165-
6166- export function hoistToRoot (
6167- renderState : RenderState ,
6168- hoistableState : HoistableState ,
6169- ) : void {
6170- let i ;
6171- const charsetChunks = hoistableState . charsetChunks ;
6172- for ( i = 0 ; i < charsetChunks . length ; i ++ ) {
6173- renderState . charsetChunks . push ( charsetChunks [ i ] ) ;
6174- }
6175- const viewportChunks = hoistableState . viewportChunks ;
6176- for ( i = 0 ; i < charsetChunks . length ; i ++ ) {
6177- renderState . viewportChunks . push ( viewportChunks [ i ] ) ;
6178- }
6179- const hoistableChunks = hoistableState . hoistableChunks ;
6180- for ( i = 0 ; i < hoistableChunks . length ; i ++ ) {
6181- renderState . hoistableChunks . push ( hoistableChunks [ i ] ) ;
6182- }
61836116}
61846117
61856118// This function is called at various times depending on whether we are rendering
0 commit comments