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

[Fiber] Use hydration lanes when scheduling hydration work #31751

Merged
merged 2 commits into from
Dec 13, 2024
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 @@ -255,6 +255,39 @@ describe('ReactDOMFizzShellHydration', () => {
},
);

// @gate enableHydrationLaneScheduling
it(
'updating the root at same priority as initial hydration does not ' +
'force a client render',
async () => {
function App() {
return <Text text="Initial" />;
}

// Server render
await resolveText('Initial');
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
assertLog(['Initial']);

await clientAct(async () => {
let root;
startTransition(() => {
root = ReactDOMClient.hydrateRoot(container, <App />);
});
// This has lower priority than the initial hydration, so the update
// won't be processed until after hydration finishes.
startTransition(() => {
root.render(<Text text="Updated" />);
});
});
assertLog(['Initial', 'Updated']);
expect(container.textContent).toBe('Updated');
},
);

it('updating the root while the shell is suspended forces a client render', async () => {
function App() {
return <AsyncText text="Shell" />;
Expand Down
101 changes: 53 additions & 48 deletions packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -995,61 +995,66 @@ export function getBumpedLaneForHydration(
renderLanes: Lanes,
): Lane {
const renderLane = getHighestPriorityLane(renderLanes);

let lane;
if ((renderLane & SyncUpdateLanes) !== NoLane) {
lane = SyncHydrationLane;
} else {
switch (renderLane) {
case SyncLane:
lane = SyncHydrationLane;
break;
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
lane = TransitionHydrationLane;
break;
case IdleLane:
lane = IdleHydrationLane;
break;
default:
// Everything else is already either a hydration lane, or shouldn't
// be retried at a hydration lane.
lane = NoLane;
break;
}
}

const bumpedLane =
(renderLane & SyncUpdateLanes) !== NoLane
? // Unify sync lanes. We don't do this inside getBumpedLaneForHydrationByLane
// because that causes things to flush synchronously when they shouldn't.
// TODO: This is not coherent but that's beacuse the unification is not coherent.
// We need to get merge these into an actual single lane.
SyncHydrationLane
: getBumpedLaneForHydrationByLane(renderLane);
// Check if the lane we chose is suspended. If so, that indicates that we
// already attempted and failed to hydrate at that level. Also check if we're
// already rendering that lane, which is rare but could happen.
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
// TODO: This should move into the caller to decide whether giving up is valid.
if ((bumpedLane & (root.suspendedLanes | renderLanes)) !== NoLane) {
// Give up trying to hydrate and fall back to client render.
return NoLane;
}
return bumpedLane;
}

export function getBumpedLaneForHydrationByLane(lane: Lane): Lane {
switch (lane) {
case SyncLane:
lane = SyncHydrationLane;
break;
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
lane = TransitionHydrationLane;
break;
case IdleLane:
lane = IdleHydrationLane;
break;
default:
// Everything else is already either a hydration lane, or shouldn't
// be retried at a hydration lane.
lane = NoLane;
break;
}
return lane;
}

Expand Down
16 changes: 13 additions & 3 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ import {
} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import isArray from 'shared/isArray';
import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import {
enableSchedulingProfiler,
enableHydrationLaneScheduling,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
getPublicInstance,
Expand Down Expand Up @@ -91,6 +94,7 @@ import {
SelectiveHydrationLane,
getHighestPriorityPendingLanes,
higherPriorityLane,
getBumpedLaneForHydrationByLane,
} from './ReactFiberLane';
import {
scheduleRefresh,
Expand Down Expand Up @@ -322,7 +326,10 @@ export function createHydrationContainer(
// the update to schedule work on the root fiber (and, for legacy roots, to
// enqueue the callback if one is provided).
const current = root.current;
const lane = requestUpdateLane(current);
let lane = requestUpdateLane(current);
if (enableHydrationLaneScheduling) {
lane = getBumpedLaneForHydrationByLane(lane);
}
const update = createUpdate(lane);
update.callback =
callback !== undefined && callback !== null ? callback : null;
Expand Down Expand Up @@ -533,7 +540,10 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
// their priority other than synchronously flush it.
return;
}
const lane = requestUpdateLane(fiber);
let lane = requestUpdateLane(fiber);
if (enableHydrationLaneScheduling) {
lane = getBumpedLaneForHydrationByLane(lane);
}
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export const enableSuspenseAvoidThisFallbackFizz = false;

export const enableCPUSuspense = __EXPERIMENTAL__;

export const enableHydrationLaneScheduling = true;

// Enables useMemoCache hook, intended as a compilation target for
// auto-memoization.
export const enableUseMemoCacheHook = true;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
export const useModernStrictMode = true;
export const enableHydrationLaneScheduling = true;

// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export const useModernStrictMode = true;
export const enableSiblingPrerendering = true;
export const enableUseResourceEffectHook = false;

export const enableHydrationLaneScheduling = true;

// Profiling Only
export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = __PROFILE__;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const enableMoveBefore = false;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableFabricCompleteRootInCommitPhase = false;
export const enableHiddenSubtreeInsertionEffectCleanup = false;
export const enableHydrationLaneScheduling = true;

export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const useModernStrictMode = true;
export const enableFabricCompleteRootInCommitPhase = false;
export const enableSiblingPrerendering = true;
export const enableUseResourceEffectHook = true;
export const enableHydrationLaneScheduling = true;

// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,7 @@ export const enableSiblingPrerendering = true;

export const enableUseResourceEffectHook = false;

export const enableHydrationLaneScheduling = true;

// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export const disableInputAttributeSyncing = false;
export const enableLegacyFBSupport = true;
export const enableLazyContextPropagation = true;

export const enableHydrationLaneScheduling = true;

export const enableComponentPerformanceTrack = false;

// Logs additional User Timing API marks for use with an experimental profiling tool.
Expand Down
Loading