@@ -707,6 +707,7 @@ type Frame = {
707707 type : mixed ,
708708 domNamespace : string ,
709709 children : FlatReactChildren ,
710+ fallbackFrame ?: Frame ,
710711 childIndex : number ,
711712 context : Object ,
712713 footer : string ,
@@ -723,6 +724,7 @@ class ReactDOMServerRenderer {
723724 currentSelectValue: any;
724725 previousWasTextNode: boolean;
725726 makeStaticMarkup: boolean;
727+ suspenseDepth: number;
726728
727729 contextIndex: number;
728730 contextStack: Array< ReactContext < any > > ;
@@ -750,6 +752,7 @@ class ReactDOMServerRenderer {
750752 this . currentSelectValue = null ;
751753 this . previousWasTextNode = false ;
752754 this . makeStaticMarkup = makeStaticMarkup ;
755+ this . suspenseDepth = 0 ;
753756
754757 // Context (new API)
755758 this . contextIndex = - 1 ;
@@ -825,16 +828,18 @@ class ReactDOMServerRenderer {
825828 ReactCurrentOwner . currentDispatcher = DispatcherWithoutHooks ;
826829 }
827830 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 ) {
830836 if ( this . stack . length === 0 ) {
831837 this . exhausted = true ;
832838 break ;
833839 }
834840 const frame : Frame = this . stack [ this . stack . length - 1 ] ;
835- if ( frame . childIndex >= frame . children . length ) {
841+ if ( suspended || frame . childIndex >= frame . children . length ) {
836842 const footer = frame . footer ;
837- out += footer ;
838843 if ( footer !== '' ) {
839844 this . previousWasTextNode = false ;
840845 }
@@ -848,26 +853,57 @@ class ReactDOMServerRenderer {
848853 ) {
849854 const provider : ReactProvider < any > = ( frame . type : any ) ;
850855 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+ }
851874 }
875+
876+ // Flush output
877+ out [ this . suspenseDepth ] += footer ;
852878 continue ;
853879 }
854880 const child = frame . children [ frame . childIndex ++ ] ;
881+
882+ let outBuffer = '' ;
855883 if ( __DEV__ ) {
856884 pushCurrentDebugStack ( this . stack ) ;
857885 // We're starting work on this frame, so reset its inner stack.
858886 ( ( 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__ ) {
863898 popCurrentDebugStack( ) ;
864899 }
865- } else {
866- // Be careful! Make sure this matches the DEV path above.
867- out += this . render ( child , frame . context , frame . domNamespace ) ;
868900 }
901+ if ( out . length < = this . suspenseDepth ) {
902+ out . push ( '' ) ;
903+ }
904+ out [ this . suspenseDepth ] + = outBuffer;
869905 }
870- return out ;
906+ return out [ 0 ] ;
871907 } finally {
872908 ReactCurrentOwner . currentDispatcher = prevDispatcher ;
873909 }
@@ -960,22 +996,36 @@ class ReactDOMServerRenderer {
960996 }
961997 case REACT_SUSPENSE_TYPE : {
962998 if ( enableSuspenseServerRenderer ) {
963- const nextChildren = toArray (
964- // Always use the fallback when synchronously rendering to string.
999+ const fallbackChildren = toArray (
9651000 ( ( nextChild : any ) : ReactElement ) . props . fallback ,
9661001 ) ;
967- const frame : Frame = {
1002+ const nextChildren = toArray (
1003+ ( ( nextChild : any ) : ReactElement ) . props . children ,
1004+ ) ;
1005+ const fallbackFrame : Frame = {
9681006 type : null ,
9691007 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 ,
9701018 children : nextChildren ,
9711019 childIndex : 0 ,
9721020 context : context ,
9731021 footer : '',
9741022 } ;
9751023 if ( __DEV__ ) {
9761024 ( ( frame : any ) : FrameDev ) . debugElementStack = [ ] ;
1025+ ( ( fallbackFrame : any ) : FrameDev ) . debugElementStack = [ ] ;
9771026 }
9781027 this . stack . push ( frame ) ;
1028+ this . suspenseDepth ++ ;
9791029 return '' ;
9801030 } else {
9811031 invariant ( false , 'ReactDOMServer does not yet support Suspense.' ) ;
0 commit comments