Skip to content

Commit fa27471

Browse files
committed
Express completeBoundary in terms of a batch
1 parent 5196451 commit fa27471

File tree

4 files changed

+67
-45
lines changed

4 files changed

+67
-45
lines changed

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ import {completeBoundary} from './ReactDOMFizzInstructionSetShared';
22

33
// This is a string so Closure's advanced compilation mode doesn't mangle it.
44
// eslint-disable-next-line dot-notation
5+
window['$RB'] = [];
6+
// eslint-disable-next-line dot-notation
57
window['$RC'] = completeBoundary;

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
// This is a string so Closure's advanced compilation mode doesn't mangle it.
1414
// These will be renamed to local references by the external-runtime-plugin.
1515
window['$RM'] = new Map();
16+
window['$RB'] = [];
1617
window['$RX'] = clientRenderBoundary;
1718
window['$RC'] = completeBoundary;
1819
window['$RR'] = completeBoundaryWithStyles;

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,72 +48,91 @@ export function clientRenderBoundary(
4848
}
4949

5050
export 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

119138
export function completeBoundaryWithStyles(

0 commit comments

Comments
 (0)