@@ -707,6 +707,7 @@ type Frame = {
707
707
type : mixed ,
708
708
domNamespace : string ,
709
709
children : FlatReactChildren ,
710
+ fallbackFrame ?: Frame ,
710
711
childIndex : number ,
711
712
context : Object ,
712
713
footer : string ,
@@ -723,6 +724,7 @@ class ReactDOMServerRenderer {
723
724
currentSelectValue: any;
724
725
previousWasTextNode: boolean;
725
726
makeStaticMarkup: boolean;
727
+ suspenseDepth: number;
726
728
727
729
contextIndex: number;
728
730
contextStack: Array< ReactContext < any > > ;
@@ -750,6 +752,7 @@ class ReactDOMServerRenderer {
750
752
this . currentSelectValue = null ;
751
753
this . previousWasTextNode = false ;
752
754
this . makeStaticMarkup = makeStaticMarkup ;
755
+ this . suspenseDepth = 0 ;
753
756
754
757
// Context (new API)
755
758
this . contextIndex = - 1 ;
@@ -825,16 +828,18 @@ class ReactDOMServerRenderer {
825
828
ReactCurrentOwner . currentDispatcher = DispatcherWithoutHooks ;
826
829
}
827
830
try {
828
- let out = '' ;
829
- while ( out . length < bytes ) {
831
+ // Markup generated within <Suspense> ends up buffered until we know
832
+ // nothing in that boundary suspended
833
+ let out = [ '' ] ;
834
+ let suspended = false ;
835
+ while ( out [ 0 ] . length < bytes ) {
830
836
if ( this . stack . length === 0 ) {
831
837
this . exhausted = true ;
832
838
break ;
833
839
}
834
840
const frame : Frame = this . stack [ this . stack . length - 1 ] ;
835
- if ( frame . childIndex >= frame . children . length ) {
841
+ if ( suspended || frame . childIndex >= frame . children . length ) {
836
842
const footer = frame . footer ;
837
- out += footer ;
838
843
if ( footer !== '' ) {
839
844
this . previousWasTextNode = false ;
840
845
}
@@ -848,26 +853,57 @@ class ReactDOMServerRenderer {
848
853
) {
849
854
const provider : ReactProvider < any > = ( frame . type : any ) ;
850
855
this . popProvider ( provider ) ;
856
+ } else if ( frame . type === REACT_SUSPENSE_TYPE ) {
857
+ this . suspenseDepth -- ;
858
+ const buffered = out . pop ( ) ;
859
+
860
+ if ( suspended ) {
861
+ suspended = false ;
862
+ // If rendering was suspended at this boundary, render the fallbackFrame
863
+ const fallbackFrame = frame . fallbackFrame ;
864
+ invariant (
865
+ fallbackFrame ,
866
+ 'suspense fallback not found, something is broken' ,
867
+ ) ;
868
+ this . stack . push ( fallbackFrame ) ;
869
+ // Skip flushing output since we're switching to the fallback
870
+ continue ;
871
+ } else {
872
+ out [ this . suspenseDepth ] += buffered ;
873
+ }
851
874
}
875
+
876
+ // Flush output
877
+ out [ this . suspenseDepth ] += footer ;
852
878
continue ;
853
879
}
854
880
const child = frame . children [ frame . childIndex ++ ] ;
881
+
882
+ let outBuffer = '' ;
855
883
if ( __DEV__ ) {
856
884
pushCurrentDebugStack ( this . stack ) ;
857
885
// We're starting work on this frame, so reset its inner stack.
858
886
( ( frame : any ) : FrameDev ) . debugElementStack . length = 0 ;
859
- try {
860
- // Be careful! Make sure this matches the PROD path below.
861
- out += this . render ( child , frame . context , frame . domNamespace ) ;
862
- } finally {
887
+ }
888
+ try {
889
+ outBuffer += this . render ( child , frame . context , frame . domNamespace ) ;
890
+ } catch ( err ) {
891
+ if ( enableSuspenseServerRenderer && typeof err . then === 'function' ) {
892
+ suspended = true ;
893
+ } else {
894
+ throw err ;
895
+ }
896
+ } finally {
897
+ if ( __DEV__ ) {
863
898
popCurrentDebugStack( ) ;
864
899
}
865
- } else {
866
- // Be careful! Make sure this matches the DEV path above.
867
- out += this . render ( child , frame . context , frame . domNamespace ) ;
868
900
}
901
+ if ( out . length < = this . suspenseDepth ) {
902
+ out . push ( '' ) ;
903
+ }
904
+ out [ this . suspenseDepth ] + = outBuffer;
869
905
}
870
- return out ;
906
+ return out [ 0 ] ;
871
907
} finally {
872
908
ReactCurrentOwner . currentDispatcher = prevDispatcher ;
873
909
}
@@ -960,22 +996,36 @@ class ReactDOMServerRenderer {
960
996
}
961
997
case REACT_SUSPENSE_TYPE : {
962
998
if ( enableSuspenseServerRenderer ) {
963
- const nextChildren = toArray (
964
- // Always use the fallback when synchronously rendering to string.
999
+ const fallbackChildren = toArray (
965
1000
( ( nextChild : any ) : ReactElement ) . props . fallback ,
966
1001
) ;
967
- const frame : Frame = {
1002
+ const nextChildren = toArray (
1003
+ ( ( nextChild : any ) : ReactElement ) . props . children ,
1004
+ ) ;
1005
+ const fallbackFrame : Frame = {
968
1006
type : null ,
969
1007
domNamespace : parentNamespace ,
1008
+ children : fallbackChildren ,
1009
+ childIndex : 0 ,
1010
+ context : context ,
1011
+ footer : '',
1012
+ out : '',
1013
+ } ;
1014
+ const frame : Frame = {
1015
+ fallbackFrame ,
1016
+ type : REACT_SUSPENSE_TYPE ,
1017
+ domNamespace : parentNamespace ,
970
1018
children : nextChildren ,
971
1019
childIndex : 0 ,
972
1020
context : context ,
973
1021
footer : '',
974
1022
} ;
975
1023
if ( __DEV__ ) {
976
1024
( ( frame : any ) : FrameDev ) . debugElementStack = [ ] ;
1025
+ ( ( fallbackFrame : any ) : FrameDev ) . debugElementStack = [ ] ;
977
1026
}
978
1027
this . stack . push ( frame ) ;
1028
+ this . suspenseDepth ++ ;
979
1029
return '' ;
980
1030
} else {
981
1031
invariant ( false , 'ReactDOMServer does not yet support Suspense.' ) ;
0 commit comments