Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prerender during same pass if blocked anyway #30879

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,14 @@ describe('ReactCache', () => {
await act(() => jest.advanceTimersByTime(100));
assertLog([
'Promise resolved [4]',

1,
4,
'Suspend! [5]',
1,
4,
'Suspend! [5]',

'Promise resolved [5]',
1,
4,
Expand Down Expand Up @@ -274,12 +276,14 @@ describe('ReactCache', () => {
await act(() => jest.advanceTimersByTime(100));
assertLog([
'Promise resolved [2]',

1,
2,
'Suspend! [3]',
1,
2,
'Suspend! [3]',

'Promise resolved [3]',
1,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ describe('ReactDOMFiberAsync', () => {
// Because it suspended, it remains on the current path
expect(div.textContent).toBe('/path/a');
});
assertLog(gate('enableSiblingPrerendering') ? ['Suspend! [/path/b]'] : []);
assertLog([]);

await act(async () => {
resolvePromise();
Expand Down
49 changes: 5 additions & 44 deletions packages/react-dom/src/__tests__/ReactDOMForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,15 +699,7 @@ describe('ReactDOMForm', () => {
// This should suspend because form actions are implicitly wrapped
// in startTransition.
await submit(formRef.current);
assertLog([
'Pending...',
'Suspend! [Updated]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Updated]', 'Loading...']
: []),
]);
assertLog(['Pending...', 'Suspend! [Updated]', 'Loading...']);
expect(container.textContent).toBe('Pending...Initial');

await act(() => resolveText('Updated'));
Expand Down Expand Up @@ -744,15 +736,7 @@ describe('ReactDOMForm', () => {

// Update
await submit(formRef.current);
assertLog([
'Pending...',
'Suspend! [Count: 1]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Count: 1]', 'Loading...']
: []),
]);
assertLog(['Pending...', 'Suspend! [Count: 1]', 'Loading...']);
expect(container.textContent).toBe('Pending...Count: 0');

await act(() => resolveText('Count: 1'));
Expand All @@ -761,15 +745,7 @@ describe('ReactDOMForm', () => {

// Update again
await submit(formRef.current);
assertLog([
'Pending...',
'Suspend! [Count: 2]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Count: 2]', 'Loading...']
: []),
]);
assertLog(['Pending...', 'Suspend! [Count: 2]', 'Loading...']);
expect(container.textContent).toBe('Pending...Count: 1');

await act(() => resolveText('Count: 2'));
Expand Down Expand Up @@ -813,14 +789,7 @@ describe('ReactDOMForm', () => {
assertLog(['Async action started', 'Pending...']);

await act(() => resolveText('Wait'));
assertLog([
'Suspend! [Updated]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Updated]', 'Loading...']
: []),
]);
assertLog(['Suspend! [Updated]', 'Loading...']);
expect(container.textContent).toBe('Pending...Initial');

await act(() => resolveText('Updated'));
Expand Down Expand Up @@ -1506,15 +1475,7 @@ describe('ReactDOMForm', () => {
// Now dispatch inside of a transition. This one does not trigger a
// loading state.
await act(() => startTransition(() => dispatch()));
assertLog([
'Count: 1',
'Suspend! [Count: 2]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Count: 2]', 'Loading...']
: []),
]);
assertLog(['Count: 1', 'Suspend! [Count: 2]', 'Loading...']);
expect(container.textContent).toBe('Count: 1');

await act(() => resolveText('Count: 2'));
Expand Down
4 changes: 2 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1666,7 +1666,7 @@ function mountSyncExternalStore<T>(
}

const rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesBlockingLane(root, rootRenderLanes)) {
if (!includesBlockingLane(rootRenderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
Expand Down Expand Up @@ -1778,7 +1778,7 @@ function updateSyncExternalStore<T>(
);
}

if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
if (!isHydrating && !includesBlockingLane(renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export function includesOnlyTransitions(lanes: Lanes): boolean {
return (lanes & TransitionLanes) === lanes;
}

export function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
export function includesBlockingLane(lanes: Lanes): boolean {
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
Expand Down
14 changes: 13 additions & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ export function performConcurrentWorkOnRoot(
// bug we're still investigating. Once the bug in Scheduler is fixed,
// we can remove this, since we track expiration ourselves.
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesBlockingLane(lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
let exitStatus = shouldTimeSlice
Expand Down Expand Up @@ -1968,6 +1968,18 @@ export function renderDidSuspend(): void {
export function renderDidSuspendDelayIfPossible(): void {
workInProgressRootExitStatus = RootSuspendedWithDelay;

if (
!workInProgressRootDidSkipSuspendedSiblings &&
!includesBlockingLane(workInProgressRootRenderLanes)
) {
// This render may not have originally been scheduled as a prerender, but
// something suspended inside the visible part of the tree, which means we
// won't be able to commit a fallback anyway. Let's proceed as if this were
// a prerender so that we can warm up the siblings without scheduling a
// separate pass.
workInProgressRootIsPrerendering = true;
}

// Check if there are updates that we skipped tree that might have unblocked
// this render.
if (
Expand Down
20 changes: 2 additions & 18 deletions packages/react-reconciler/src/__tests__/ActivitySuspense-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,7 @@ describe('Activity Suspense', () => {
);
});
});
assertLog([
'Open',
'Suspend! [Async]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Open', 'Suspend! [Async]', 'Loading...']
: []),
]);
assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(<span>Closed</span>);
Expand Down Expand Up @@ -284,15 +276,7 @@ describe('Activity Suspense', () => {
);
});
});
assertLog([
'Open',
'Suspend! [Async]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Open', 'Suspend! [Async]', 'Loading...']
: []),
]);
assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,7 @@ describe('act warnings', () => {
root.render(<App showMore={true} />);
});
});
assertLog([
'Suspend! [Async]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Async]', 'Loading...']
: []),
]);
assertLog(['Suspend! [Async]', 'Loading...']);
expect(root).toMatchRenderedOutput('(empty)');

// This is a ping, not a retry, because no fallback is showing.
Expand Down
21 changes: 3 additions & 18 deletions packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ describe('ReactAsyncActions', () => {
'Suspend! [A1]',

...(gate('enableSiblingPrerendering')
? ['Pending: false', 'Suspend! [A1]', 'Suspend! [B1]', 'Suspend! [C1]']
? ['Suspend! [B1]', 'Suspend! [C1]']
: []),
]);
expect(root).toMatchRenderedOutput(
Expand All @@ -322,9 +322,7 @@ describe('ReactAsyncActions', () => {
'A1',
'Suspend! [B1]',

...(gate('enableSiblingPrerendering')
? ['Pending: false', 'A1', 'Suspend! [B1]', 'Suspend! [C1]']
: []),
...(gate('enableSiblingPrerendering') ? ['Suspend! [C1]'] : []),
]);
expect(root).toMatchRenderedOutput(
<>
Expand All @@ -333,16 +331,7 @@ describe('ReactAsyncActions', () => {
</>,
);
await act(() => resolveText('B1'));
assertLog([
'Pending: false',
'A1',
'B1',
'Suspend! [C1]',

...(gate('enableSiblingPrerendering')
? ['Pending: false', 'A1', 'B1', 'Suspend! [C1]']
: []),
]);
assertLog(['Pending: false', 'A1', 'B1', 'Suspend! [C1]']);
expect(root).toMatchRenderedOutput(
<>
<span>Pending: true</span>
Expand Down Expand Up @@ -715,10 +704,6 @@ describe('ReactAsyncActions', () => {
// automatically reverted.
'Pending: false',
'Suspend! [B]',

...(gate('enableSiblingPrerendering')
? ['Pending: false', 'Suspend! [B]']
: []),
]);

// Resolve the transition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<App step={2} />);
});
});
assertLog([
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']
: []),
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');

Expand All @@ -229,16 +220,7 @@ describe('ReactConcurrentErrorRecovery', () => {

// Because we're still suspended on A, we can't show an error boundary. We
// should wait for A to resolve.
assertLog([
'Suspend! [A2]',
'Loading...',
'Error! [B2]',
'Oops!',

...(gate('enableSiblingPrerendering')
? ['Suspend! [A2]', 'Loading...', 'Error! [B2]', 'Oops!']
: []),
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Error! [B2]', 'Oops!']);
// Remain on previous screen.
expect(root).toMatchRenderedOutput('A1B1');

Expand Down Expand Up @@ -299,16 +281,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<App step={2} />);
});
});
assertLog([
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',

...(gate('enableSiblingPrerendering')
? ['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']
: []),
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');

Expand Down Expand Up @@ -364,11 +337,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<AsyncText text="Async" />);
});
});
assertLog([
'Suspend! [Async]',

...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
]);
assertLog(['Suspend! [Async]']);
expect(root).toMatchRenderedOutput(null);

// This also works if the suspended component is wrapped with an error
Expand All @@ -384,11 +353,7 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
assertLog([
'Suspend! [Async]',

...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
]);
assertLog(['Suspend! [Async]']);
expect(root).toMatchRenderedOutput(null);

// Continues rendering once data resolves
Expand Down Expand Up @@ -445,7 +410,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'Suspend! [Async]',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Async]', 'Caught an error: Oops!']
? ['Caught an error: Oops!']
: []),
]);
// The render suspended without committing the error.
Expand All @@ -468,7 +433,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'Suspend! [Async]',

...(gate('enableSiblingPrerendering')
? ['Suspend! [Async]', 'Caught an error: Oops!']
? ['Caught an error: Oops!']
: []),
]);
expect(root).toMatchRenderedOutput(null);
Expand Down
Loading
Loading