Skip to content

Commit 1f03442

Browse files
committed
Encode lazy Node slots properly in key path and resumable paths
It's possible to postpone a specific node and not using a wrapper component. Therefore we encode the resumable slot as the index slot. When it's a plain client component that postpones, it's encoded as the child slot inside that component which is the one that's postponed rather than the component itself. Since it's possible for a child slot to suspend (e.g. React.lazy's microtask in this case) retryTask might need to keep its index around when it resolves.
1 parent 627b7ab commit 1f03442

File tree

2 files changed

+72
-15
lines changed

2 files changed

+72
-15
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,47 @@ describe('ReactDOMFizzStaticBrowser', () => {
493493
// TODO: expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
494494
});
495495

496+
// @gate enablePostpone
497+
it('supports postponing in lazy in prerender and resuming later', async () => {
498+
let prerendering = true;
499+
const Hole = React.lazy(async () => {
500+
React.unstable_postpone();
501+
});
502+
503+
function Postpone() {
504+
return 'Hello';
505+
}
506+
507+
function App() {
508+
return (
509+
<div>
510+
<Suspense fallback="Loading...">
511+
Hi
512+
{prerendering ? Hole : <Postpone />}
513+
</Suspense>
514+
</div>
515+
);
516+
}
517+
518+
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
519+
expect(prerendered.postponed).not.toBe(null);
520+
521+
prerendering = false;
522+
523+
const resumed = await ReactDOMFizzServer.resume(
524+
<App />,
525+
prerendered.postponed,
526+
);
527+
528+
await readIntoContainer(prerendered.prelude);
529+
530+
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
531+
532+
await readIntoContainer(resumed);
533+
534+
// TODO: expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
535+
});
536+
496537
// @gate enablePostpone
497538
it('only emits end tags once when resuming', async () => {
498539
let prerendering = true;

packages/react-server/src/ReactFizzServer.js

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,7 @@ type ResumableNode =
186186
| ResumableParentNode
187187
| [
188188
2, // RESUME_SEGMENT
189-
string | null /* name */,
190-
string | number /* key */,
189+
number /* index */,
191190
number /* segment id */,
192191
];
193192

@@ -220,6 +219,7 @@ type SuspenseBoundary = {
220219

221220
export type Task = {
222221
node: ReactNodeList,
222+
childIndex: number,
223223
ping: () => void,
224224
blockedBoundary: Root | SuspenseBoundary,
225225
blockedSegment: Segment, // the segment we'll write to
@@ -1632,6 +1632,7 @@ function renderNodeDestructiveImpl(
16321632
// Stash the node we're working on. We'll pick up from this task in case
16331633
// something suspends.
16341634
task.node = node;
1635+
task.childIndex = childIndex;
16351636

16361637
// Handle object types
16371638
if (typeof node === 'object' && node !== null) {
@@ -1831,6 +1832,7 @@ function trackPostpone(
18311832
request: Request,
18321833
trackedPostpones: PostponedHoles,
18331834
task: Task,
1835+
childIndex: number,
18341836
segment: Segment,
18351837
): void {
18361838
segment.status = POSTPONED;
@@ -1862,7 +1864,7 @@ function trackPostpone(
18621864
boundary.id,
18631865
];
18641866
trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
1865-
addToResumableParent(boundaryNode, boundaryKeyPath, trackedPostpones);
1867+
addToResumableParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
18661868
}
18671869

18681870
const keyPath = task.keyPath;
@@ -1872,12 +1874,7 @@ function trackPostpone(
18721874
);
18731875
}
18741876

1875-
const segmentNode: ResumableNode = [
1876-
RESUME_SEGMENT,
1877-
keyPath[1],
1878-
keyPath[2],
1879-
segment.id,
1880-
];
1877+
const segmentNode: ResumableNode = [RESUME_SEGMENT, childIndex, segment.id];
18811878
addToResumableParent(segmentNode, keyPath, trackedPostpones);
18821879
}
18831880

@@ -1941,6 +1938,7 @@ function spawnNewSuspendedTask(
19411938
task.context,
19421939
task.treeContext,
19431940
);
1941+
newTask.childIndex = task.childIndex;
19441942

19451943
if (__DEV__) {
19461944
if (task.componentStack !== null) {
@@ -2035,7 +2033,13 @@ function renderNode(
20352033
task,
20362034
postponeInstance.message,
20372035
);
2038-
trackPostpone(request, trackedPostpones, task, postponedSegment);
2036+
trackPostpone(
2037+
request,
2038+
trackedPostpones,
2039+
task,
2040+
childIndex,
2041+
postponedSegment,
2042+
);
20392043

20402044
// Restore the context. We assume that this will be restored by the inner
20412045
// functions in case nothing throws so we don't use "finally" here.
@@ -2328,7 +2332,13 @@ function retryTask(request: Request, task: Task): void {
23282332
const prevThenableState = task.thenableState;
23292333
task.thenableState = null;
23302334

2331-
renderNodeDestructive(request, task, prevThenableState, task.node, 0);
2335+
renderNodeDestructive(
2336+
request,
2337+
task,
2338+
prevThenableState,
2339+
task.node,
2340+
task.childIndex,
2341+
);
23322342
pushSegmentFinale(
23332343
segment.chunks,
23342344
request.renderState,
@@ -2377,8 +2387,15 @@ function retryTask(request: Request, task: Task): void {
23772387
task.abortSet.delete(task);
23782388
const postponeInstance: Postpone = (x: any);
23792389
logPostpone(request, postponeInstance.message);
2380-
trackPostpone(request, trackedPostpones, task, segment);
2390+
trackPostpone(
2391+
request,
2392+
trackedPostpones,
2393+
task,
2394+
task.childIndex,
2395+
segment,
2396+
);
23812397
finishedTask(request, task.blockedBoundary, segment);
2398+
return;
23822399
}
23832400
}
23842401
task.abortSet.delete(task);
@@ -2975,10 +2992,9 @@ export function getResumableState(request: Request): ResumableState {
29752992

29762993
function addToResumableParent(
29772994
node: ResumableNode,
2978-
keyPath: KeyNode,
2995+
parentKeyPath: Root | KeyNode,
29792996
trackedPostpones: PostponedHoles,
29802997
): void {
2981-
const parentKeyPath = keyPath[0];
29822998
if (parentKeyPath === null) {
29832999
trackedPostpones.root.push(node);
29843000
} else {
@@ -2992,7 +3008,7 @@ function addToResumableParent(
29923008
([]: Array<ResumableNode>),
29933009
]: ResumableParentNode);
29943010
workingMap.set(parentKeyPath, parentNode);
2995-
addToResumableParent(parentNode, parentKeyPath, trackedPostpones);
3011+
addToResumableParent(parentNode, parentKeyPath[0], trackedPostpones);
29963012
}
29973013
parentNode[3].push(node);
29983014
}

0 commit comments

Comments
 (0)