@@ -48,72 +48,91 @@ export function clientRenderBoundary(
48
48
}
49
49
50
50
export function completeBoundary ( suspenseBoundaryID , contentID ) {
51
- const contentNode = document . getElementById ( contentID ) ;
52
- if ( ! contentNode ) {
51
+ const contentNodeOuter = document . getElementById ( contentID ) ;
52
+ if ( ! contentNodeOuter ) {
53
53
// If the client has failed hydration we may have already deleted the streaming
54
54
// segments. The server may also have emitted a complete instruction but cancelled
55
55
// the segment. Regardless we can ignore this case.
56
56
return ;
57
57
}
58
58
// We'll detach the content node so that regardless of what happens next we don't leave in the tree.
59
59
// 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 ) ;
61
61
62
62
// Find the fallback's first element.
63
- const suspenseIdNode = document . getElementById ( suspenseBoundaryID ) ;
64
- if ( ! suspenseIdNode ) {
63
+ const suspenseIdNodeOuter = document . getElementById ( suspenseBoundaryID ) ;
64
+ if ( ! suspenseIdNodeOuter ) {
65
65
// The user must have already navigated away from this tree.
66
66
// E.g. because the parent was hydrated. That's fine there's nothing to do
67
67
// but we have to make sure that we already deleted the container node.
68
68
return ;
69
69
}
70
- // Find the boundary around the fallback. This is always the previous node.
71
- const suspenseNode = suspenseIdNode . previousSibling ;
72
70
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 ;
97
87
}
98
- }
99
88
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
+ }
104
112
105
- const endOfBoundary = node ;
113
+ const nextNode = node . nextSibling ;
114
+ parentInstance . removeChild ( node ) ;
115
+ node = nextNode ;
116
+ } while ( node ) ;
106
117
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 ;
111
119
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
+ }
113
124
114
- if ( suspenseNode [ '_reactRetry' ] ) {
115
- suspenseNode [ '_reactRetry' ] ( ) ;
125
+ suspenseNode . data = SUSPENSE_START_DATA ;
126
+ if ( suspenseNode [ '_reactRetry' ] ) {
127
+ suspenseNode [ '_reactRetry' ] ( ) ;
128
+ }
129
+ }
116
130
}
131
+
132
+ // Queue this boundary for the next batch
133
+ window [ '$RB' ] . push ( suspenseIdNodeOuter , contentNodeOuter ) ;
134
+
135
+ revealCompletedBoundaries ( ) ;
117
136
}
118
137
119
138
export function completeBoundaryWithStyles (
0 commit comments