@@ -48,72 +48,91 @@ export function clientRenderBoundary(
4848}
4949
5050export function completeBoundary ( suspenseBoundaryID , contentID ) {
51- const contentNode = document . getElementById ( contentID ) ;
52- if ( ! contentNode ) {
51+ const contentNodeOuter = document . getElementById ( contentID ) ;
52+ if ( ! contentNodeOuter ) {
5353 // If the client has failed hydration we may have already deleted the streaming
5454 // segments. The server may also have emitted a complete instruction but cancelled
5555 // the segment. Regardless we can ignore this case.
5656 return ;
5757 }
5858 // We'll detach the content node so that regardless of what happens next we don't leave in the tree.
5959 // This might also help by not causing recalcing each time we move a child from here to the target.
60- contentNode . parentNode . removeChild ( contentNode ) ;
60+ contentNodeOuter . parentNode . removeChild ( contentNodeOuter ) ;
6161
6262 // Find the fallback's first element.
63- const suspenseIdNode = document . getElementById ( suspenseBoundaryID ) ;
64- if ( ! suspenseIdNode ) {
63+ const suspenseIdNodeOuter = document . getElementById ( suspenseBoundaryID ) ;
64+ if ( ! suspenseIdNodeOuter ) {
6565 // The user must have already navigated away from this tree.
6666 // E.g. because the parent was hydrated. That's fine there's nothing to do
6767 // but we have to make sure that we already deleted the container node.
6868 return ;
6969 }
70- // Find the boundary around the fallback. This is always the previous node.
71- const suspenseNode = suspenseIdNode . previousSibling ;
7270
73- // Clear all the existing children. This is complicated because
74- // there can be embedded Suspense boundaries in the fallback.
75- // This is similar to clearSuspenseBoundary in ReactFiberConfigDOM.
76- // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
77- // They never hydrate anyway. However, currently we support incrementally loading the fallback.
78- const parentInstance = suspenseNode . parentNode ;
79- let node = suspenseNode . nextSibling ;
80- let depth = 0 ;
81- do {
82- if ( node && node . nodeType === COMMENT_NODE ) {
83- const data = node . data ;
84- if ( data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA ) {
85- if ( depth === 0 ) {
86- break ;
87- } else {
88- depth -- ;
89- }
90- } else if (
91- data === SUSPENSE_START_DATA ||
92- data === SUSPENSE_PENDING_START_DATA ||
93- data === SUSPENSE_FALLBACK_START_DATA ||
94- data === ACTIVITY_START_DATA
95- ) {
96- depth ++ ;
71+ function revealCompletedBoundaries ( ) {
72+ const batch = window [ '$RB' ] ;
73+ window [ '$RB' ] = [ ] ;
74+ for ( let i = 0 ; i < batch . length ; i += 2 ) {
75+ const suspenseIdNode = batch [ i ] ;
76+ const contentNode = batch [ i + 1 ] ;
77+
78+ // Clear all the existing children. This is complicated because
79+ // there can be embedded Suspense boundaries in the fallback.
80+ // This is similar to clearSuspenseBoundary in ReactFiberConfigDOM.
81+ // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
82+ // They never hydrate anyway. However, currently we support incrementally loading the fallback.
83+ const parentInstance = suspenseIdNode . parentNode ;
84+ if ( ! parentInstance ) {
85+ // We may have client-rendered this boundary already. Skip it.
86+ continue ;
9787 }
98- }
9988
100- const nextNode = node . nextSibling ;
101- parentInstance . removeChild ( node ) ;
102- node = nextNode ;
103- } while ( node ) ;
89+ // Find the boundary around the fallback. This is always the previous node.
90+ const suspenseNode = suspenseIdNode . previousSibling ;
91+
92+ let node = suspenseIdNode ;
93+ let depth = 0 ;
94+ do {
95+ if ( node && node . nodeType === COMMENT_NODE ) {
96+ const data = node . data ;
97+ if ( data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA ) {
98+ if ( depth === 0 ) {
99+ break ;
100+ } else {
101+ depth -- ;
102+ }
103+ } else if (
104+ data === SUSPENSE_START_DATA ||
105+ data === SUSPENSE_PENDING_START_DATA ||
106+ data === SUSPENSE_FALLBACK_START_DATA ||
107+ data === ACTIVITY_START_DATA
108+ ) {
109+ depth ++ ;
110+ }
111+ }
104112
105- const endOfBoundary = node ;
113+ const nextNode = node . nextSibling ;
114+ parentInstance . removeChild ( node ) ;
115+ node = nextNode ;
116+ } while ( node ) ;
106117
107- // Insert all the children from the contentNode between the start and end of suspense boundary.
108- while ( contentNode . firstChild ) {
109- parentInstance . insertBefore ( contentNode . firstChild , endOfBoundary ) ;
110- }
118+ const endOfBoundary = node ;
111119
112- suspenseNode . data = SUSPENSE_START_DATA ;
120+ // Insert all the children from the contentNode between the start and end of suspense boundary.
121+ while ( contentNode . firstChild ) {
122+ parentInstance . insertBefore ( contentNode . firstChild , endOfBoundary ) ;
123+ }
113124
114- if ( suspenseNode [ '_reactRetry' ] ) {
115- suspenseNode [ '_reactRetry' ] ( ) ;
125+ suspenseNode . data = SUSPENSE_START_DATA ;
126+ if ( suspenseNode [ '_reactRetry' ] ) {
127+ suspenseNode [ '_reactRetry' ] ( ) ;
128+ }
129+ }
116130 }
131+
132+ // Queue this boundary for the next batch
133+ window [ '$RB' ] . push ( suspenseIdNodeOuter , contentNodeOuter ) ;
134+
135+ revealCompletedBoundaries ( ) ;
117136}
118137
119138export function completeBoundaryWithStyles (
0 commit comments