@@ -212,6 +212,12 @@ describe('ReactDOMFizzServer', () => {
212
212
while ( true ) {
213
213
const bufferedContent = buffer ;
214
214
215
+ document . __headOpen =
216
+ document . __headOpen ||
217
+ ( ( bufferedContent . includes ( '<head>' ) ||
218
+ bufferedContent . includes ( '<head ' ) ) &&
219
+ ! bufferedContent . includes ( '</head>' ) ) ;
220
+
215
221
let parent ;
216
222
let temporaryHostElement ;
217
223
if ( bufferedContent . startsWith ( '<!DOCTYPE html>' ) ) {
@@ -255,6 +261,12 @@ describe('ReactDOMFizzServer', () => {
255
261
temporaryHostElement = document . createElement ( 'head' ) ;
256
262
temporaryHostElement . innerHTML = headContent ;
257
263
buffer = bodyContent ;
264
+ document . __headOpen = false ;
265
+ } else if ( document . __headOpen ) {
266
+ parent = document . head ;
267
+ temporaryHostElement = document . createElement ( 'head' ) ;
268
+ temporaryHostElement . innerHTML = bufferedContent ;
269
+ buffer = '' ;
258
270
} else {
259
271
parent = document . body ;
260
272
temporaryHostElement = document . createElement ( 'body' ) ;
@@ -6368,6 +6380,55 @@ describe('ReactDOMFizzServer', () => {
6368
6380
) ;
6369
6381
} ) ;
6370
6382
6383
+ it ( 'can render an empty fallback' , async ( ) => {
6384
+ function Throw ( ) {
6385
+ throw new Error ( 'uh oh' ) ;
6386
+ }
6387
+
6388
+ let content = '' ;
6389
+ writable . on ( 'data' , chunk => ( content += chunk ) ) ;
6390
+
6391
+ let didBootstrap = false ;
6392
+ function bootstrap ( ) {
6393
+ didBootstrap = true ;
6394
+ }
6395
+ window . __INIT__ = bootstrap ;
6396
+
6397
+ const errors = [ ] ;
6398
+ await act ( ( ) => {
6399
+ const { pipe} = renderIntoDocumentAsPipeableStream (
6400
+ < html id = "html" >
6401
+ < link rel = "stylesheet" href = "foo" precedence = "foo" />
6402
+ < link rel = "stylesheet" href = "bar" precedence = "bar" />
6403
+ < body id = "body" >
6404
+ < Throw />
6405
+ </ body >
6406
+ </ html > ,
6407
+ undefined ,
6408
+ {
6409
+ onError ( err ) {
6410
+ errors . push ( err . message ) ;
6411
+ } ,
6412
+ fallbackBootstrapScriptContent : '__INIT__()' ,
6413
+ } ,
6414
+ ) ;
6415
+ pipe ( writable ) ;
6416
+ } ) ;
6417
+
6418
+ expect ( errors ) . toEqual ( [ 'uh oh' ] ) ;
6419
+ expect ( didBootstrap ) . toBe ( true ) ;
6420
+
6421
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6422
+ < html >
6423
+ < head >
6424
+ < link rel = "stylesheet" href = "foo" data-precedence = "foo" />
6425
+ < link rel = "stylesheet" href = "bar" data-precedence = "bar" />
6426
+ </ head >
6427
+ < body />
6428
+ </ html > ,
6429
+ ) ;
6430
+ } ) ;
6431
+
6371
6432
it ( 'emits fallback bootstrap scripts if configured when rendering the fallback shell' , async ( ) => {
6372
6433
function Throw ( ) {
6373
6434
throw new Error ( 'uh oh' ) ;
@@ -6461,5 +6522,202 @@ describe('ReactDOMFizzServer', () => {
6461
6522
6462
6523
expect ( didBootstrap ) . toBe ( true ) ;
6463
6524
} ) ;
6525
+
6526
+ it ( 'does not work on the fallback unless the primary children error in the shell' , async ( ) => {
6527
+ function Throw ( ) {
6528
+ throw new Error ( 'uh oh' ) ;
6529
+ }
6530
+
6531
+ const logs = [ ] ;
6532
+ function BlockOn ( { value, children} ) {
6533
+ readText ( value ) ;
6534
+ logs . push ( value ) ;
6535
+ return children ;
6536
+ }
6537
+
6538
+ const errors = [ ] ;
6539
+ await act ( ( ) => {
6540
+ const { pipe} = renderIntoDocumentAsPipeableStream (
6541
+ < html id = "html" >
6542
+ < body id = "body" >
6543
+ < BlockOn value = "error" >
6544
+ < Throw />
6545
+ </ BlockOn >
6546
+ < BlockOn value = "resource" >
6547
+ < link rel = "stylesheet" href = "foo" precedence = "foo" />
6548
+ </ BlockOn >
6549
+ hello world
6550
+ </ body >
6551
+ </ html > ,
6552
+ < div >
6553
+ < BlockOn value = "fallback" > fallback</ BlockOn >
6554
+ </ div > ,
6555
+ {
6556
+ onError ( err ) {
6557
+ errors . push ( err . message ) ;
6558
+ } ,
6559
+ } ,
6560
+ ) ;
6561
+ pipe ( writable ) ;
6562
+ } ) ;
6563
+
6564
+ expect ( logs ) . toEqual ( [ ] ) ;
6565
+ logs . length = 0 ;
6566
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6567
+ < html id = "html" >
6568
+ < head />
6569
+ < body />
6570
+ </ html > ,
6571
+ ) ;
6572
+
6573
+ // Even though we unblock fallback since the task is not scheduled no log is observed
6574
+ await act ( ( ) => {
6575
+ resolveText ( 'fallback' ) ;
6576
+ } ) ;
6577
+ expect ( logs ) . toEqual ( [ ] ) ;
6578
+ logs . length = 0 ;
6579
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6580
+ < html id = "html" >
6581
+ < head />
6582
+ < body />
6583
+ </ html > ,
6584
+ ) ;
6585
+
6586
+ // When we resolve the resource it is emitted in the open preamble.
6587
+ await act ( ( ) => {
6588
+ resolveText ( 'resource' ) ;
6589
+ } ) ;
6590
+ expect ( logs ) . toEqual ( [ 'resource' ] ) ;
6591
+ logs . length = 0 ;
6592
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6593
+ < html id = "html" >
6594
+ < head >
6595
+ < link rel = "stylesheet" href = "foo" data-precedence = "foo" />
6596
+ </ head >
6597
+ < body />
6598
+ </ html > ,
6599
+ ) ;
6600
+
6601
+ // When we resolve the resource it is emitted in the open preamble.
6602
+ await act ( ( ) => {
6603
+ resolveText ( 'error' ) ;
6604
+ } ) ;
6605
+ expect ( logs ) . toEqual ( [ 'error' , 'fallback' ] ) ;
6606
+ logs . length = 0 ;
6607
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6608
+ < html id = "html" >
6609
+ < head >
6610
+ < link rel = "stylesheet" href = "foo" data-precedence = "foo" />
6611
+ </ head >
6612
+ < body >
6613
+ < div > fallback</ div >
6614
+ </ body >
6615
+ </ html > ,
6616
+ ) ;
6617
+ } ) ;
6618
+
6619
+ it ( 'only emits stylesheets up to the first precedence during the early preamble' , async ( ) => {
6620
+ function BlockOn ( { value, children} ) {
6621
+ readText ( value ) ;
6622
+ return children ;
6623
+ }
6624
+
6625
+ await act ( ( ) => {
6626
+ const { pipe} = renderIntoDocumentAsPipeableStream (
6627
+ < html id = "html" >
6628
+ < head />
6629
+ < body id = "body" >
6630
+ < BlockOn value = "two" >
6631
+ < link rel = "stylesheet" href = "baz1" precedence = "baz" />
6632
+ < link rel = "stylesheet" href = "foo2" precedence = "foo" />
6633
+ < script async = { true } src = "script2" />
6634
+ </ BlockOn >
6635
+ < BlockOn value = "one" >
6636
+ < link rel = "stylesheet" href = "foo1" precedence = "foo" />
6637
+ < link rel = "stylesheet" href = "bar1" precedence = "bar" />
6638
+ < script async = { true } src = "script1" />
6639
+ </ BlockOn >
6640
+ < BlockOn value = "three" >
6641
+ < link rel = "stylesheet" href = "baz2" precedence = "baz" />
6642
+ < link rel = "stylesheet" href = "bar2" precedence = "bar" />
6643
+ < link rel = "stylesheet" href = "foo3" precedence = "foo" />
6644
+ < script async = { true } src = "script3" />
6645
+ </ BlockOn >
6646
+ hello world
6647
+ </ body >
6648
+ </ html > ,
6649
+ ) ;
6650
+ pipe ( writable ) ;
6651
+ } ) ;
6652
+
6653
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6654
+ < html id = "html" >
6655
+ < head />
6656
+ < body />
6657
+ </ html > ,
6658
+ ) ;
6659
+
6660
+ // We emit all resources that are unblocked which is most types except stylesheets.
6661
+ // For stylesheets we can only emit up to the first precedence since we may discover
6662
+ // additional stylesheets with this precedence level after this flush and would violate
6663
+ // stylesheet order. For stylesheets we cannot emit we can emit a preload instead so the
6664
+ // browser can start downloading the resource as soon as possible
6665
+ await act ( ( ) => {
6666
+ resolveText ( 'one' ) ;
6667
+ } ) ;
6668
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6669
+ < html id = "html" >
6670
+ < head >
6671
+ < link rel = "stylesheet" href = "foo1" data-precedence = "foo" />
6672
+ < link rel = "preload" href = "bar1" as = "style" />
6673
+ < script async = "" src = "script1" />
6674
+ </ head >
6675
+ < body />
6676
+ </ html > ,
6677
+ ) ;
6678
+
6679
+ // We can continue to emit early resources according to these rules
6680
+ await act ( ( ) => {
6681
+ resolveText ( 'two' ) ;
6682
+ } ) ;
6683
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6684
+ < html id = "html" >
6685
+ < head >
6686
+ < link rel = "stylesheet" href = "foo1" data-precedence = "foo" />
6687
+ < link rel = "preload" href = "bar1" as = "style" />
6688
+ < script async = "" src = "script1" />
6689
+ < link rel = "stylesheet" href = "foo2" data-precedence = "foo" />
6690
+ < link rel = "preload" href = "baz1" as = "style" />
6691
+ < script async = "" src = "script2" />
6692
+ </ head >
6693
+ < body />
6694
+ </ html > ,
6695
+ ) ;
6696
+
6697
+ // Once the Shell is unblocked we can now emit the entire preamble as well as
6698
+ // the main content
6699
+ await act ( ( ) => {
6700
+ resolveText ( 'three' ) ;
6701
+ } ) ;
6702
+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
6703
+ < html id = "html" >
6704
+ < head >
6705
+ < link rel = "stylesheet" href = "foo1" data-precedence = "foo" />
6706
+ < link rel = "preload" href = "bar1" as = "style" />
6707
+ < script async = "" src = "script1" />
6708
+ < link rel = "stylesheet" href = "foo2" data-precedence = "foo" />
6709
+ < link rel = "preload" href = "baz1" as = "style" />
6710
+ < script async = "" src = "script2" />
6711
+ < link rel = "stylesheet" href = "foo3" data-precedence = "foo" />
6712
+ < link rel = "stylesheet" href = "bar1" data-precedence = "bar" />
6713
+ < link rel = "stylesheet" href = "bar2" data-precedence = "bar" />
6714
+ < link rel = "stylesheet" href = "baz1" data-precedence = "baz" />
6715
+ < link rel = "stylesheet" href = "baz2" data-precedence = "baz" />
6716
+ < script async = "" src = "script3" />
6717
+ </ head >
6718
+ < body id = "body" > hello world</ body >
6719
+ </ html > ,
6720
+ ) ;
6721
+ } ) ;
6464
6722
} ) ;
6465
6723
} ) ;
0 commit comments