Skip to content

Commit 7dc9745

Browse files
authored
[Flight] Chunks API (#17398)
* Add feature flags * Add Chunk type and constructor * Wire up Chunk support in the reconciler * Update reconciler to reconcile Chunks against the render method This allows the query and args to be updated. * Drop the ref. Chunks cannot have refs anyway. * Add Chunk checks in more missing cases * Rename secondArg * Add test and fix lazy chunks Not really a supported use case but for consistency I guess. * Fix fragment test
1 parent 9354dd2 commit 7dc9745

23 files changed

+556
-60
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
SimpleMemoComponent,
2626
ContextProvider,
2727
ForwardRef,
28+
Chunk,
2829
} from 'shared/ReactWorkTags';
2930

3031
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
@@ -623,7 +624,8 @@ export function inspectHooksOfFiber(
623624
if (
624625
fiber.tag !== FunctionComponent &&
625626
fiber.tag !== SimpleMemoComponent &&
626-
fiber.tag !== ForwardRef
627+
fiber.tag !== ForwardRef &&
628+
fiber.tag !== Chunk
627629
) {
628630
throw new Error(
629631
'Unknown Fiber. Needs to be a function component to inspect hooks.',

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,18 @@ import {
1919
REACT_ELEMENT_TYPE,
2020
REACT_FRAGMENT_TYPE,
2121
REACT_PORTAL_TYPE,
22+
REACT_CHUNK_TYPE,
2223
} from 'shared/ReactSymbols';
2324
import {
2425
FunctionComponent,
2526
ClassComponent,
2627
HostText,
2728
HostPortal,
2829
Fragment,
30+
Chunk,
2931
} from 'shared/ReactWorkTags';
3032
import invariant from 'shared/invariant';
31-
import {warnAboutStringRefs} from 'shared/ReactFeatureFlags';
33+
import {warnAboutStringRefs, enableChunksAPI} from 'shared/ReactFeatureFlags';
3234

3335
import {
3436
createWorkInProgress,
@@ -392,32 +394,47 @@ function ChildReconciler(shouldTrackSideEffects) {
392394
element: ReactElement,
393395
expirationTime: ExpirationTime,
394396
): Fiber {
395-
if (
396-
current !== null &&
397-
(current.elementType === element.type ||
397+
if (current !== null) {
398+
if (
399+
current.elementType === element.type ||
398400
// Keep this check inline so it only runs on the false path:
399-
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false))
400-
) {
401-
// Move based on index
402-
const existing = useFiber(current, element.props, expirationTime);
403-
existing.ref = coerceRef(returnFiber, current, element);
404-
existing.return = returnFiber;
405-
if (__DEV__) {
406-
existing._debugSource = element._source;
407-
existing._debugOwner = element._owner;
401+
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
402+
) {
403+
// Move based on index
404+
const existing = useFiber(current, element.props, expirationTime);
405+
existing.ref = coerceRef(returnFiber, current, element);
406+
existing.return = returnFiber;
407+
if (__DEV__) {
408+
existing._debugSource = element._source;
409+
existing._debugOwner = element._owner;
410+
}
411+
return existing;
412+
} else if (
413+
enableChunksAPI &&
414+
current.tag === Chunk &&
415+
element.type.$$typeof === REACT_CHUNK_TYPE &&
416+
element.type.render === current.type.render
417+
) {
418+
// Same as above but also update the .type field.
419+
const existing = useFiber(current, element.props, expirationTime);
420+
existing.return = returnFiber;
421+
existing.type = element.type;
422+
if (__DEV__) {
423+
existing._debugSource = element._source;
424+
existing._debugOwner = element._owner;
425+
}
426+
return existing;
408427
}
409-
return existing;
410-
} else {
411-
// Insert
412-
const created = createFiberFromElement(
413-
element,
414-
returnFiber.mode,
415-
expirationTime,
416-
);
417-
created.ref = coerceRef(returnFiber, current, element);
418-
created.return = returnFiber;
419-
return created;
420428
}
429+
// Insert
430+
const created = createFiberFromElement(
431+
element,
432+
returnFiber.mode,
433+
expirationTime,
434+
);
435+
created.ref = coerceRef(returnFiber, current, element);
436+
created.return = returnFiber;
437+
return created;
421438
}
422439

423440
function updatePortal(
@@ -1138,34 +1155,67 @@ function ChildReconciler(shouldTrackSideEffects) {
11381155
// TODO: If key === null and child.key === null, then this only applies to
11391156
// the first item in the list.
11401157
if (child.key === key) {
1141-
if (
1142-
child.tag === Fragment
1143-
? element.type === REACT_FRAGMENT_TYPE
1144-
: child.elementType === element.type ||
1158+
switch (child.tag) {
1159+
case Fragment: {
1160+
if (element.type === REACT_FRAGMENT_TYPE) {
1161+
deleteRemainingChildren(returnFiber, child.sibling);
1162+
const existing = useFiber(
1163+
child,
1164+
element.props.children,
1165+
expirationTime,
1166+
);
1167+
existing.return = returnFiber;
1168+
if (__DEV__) {
1169+
existing._debugSource = element._source;
1170+
existing._debugOwner = element._owner;
1171+
}
1172+
return existing;
1173+
}
1174+
break;
1175+
}
1176+
case Chunk:
1177+
if (enableChunksAPI) {
1178+
if (
1179+
element.type.$$typeof === REACT_CHUNK_TYPE &&
1180+
element.type.render === child.type.render
1181+
) {
1182+
deleteRemainingChildren(returnFiber, child.sibling);
1183+
const existing = useFiber(child, element.props, expirationTime);
1184+
existing.type = element.type;
1185+
existing.return = returnFiber;
1186+
if (__DEV__) {
1187+
existing._debugSource = element._source;
1188+
existing._debugOwner = element._owner;
1189+
}
1190+
return existing;
1191+
}
1192+
}
1193+
// We intentionally fallthrough here if enableChunksAPI is not on.
1194+
// eslint-disable-next-lined no-fallthrough
1195+
default: {
1196+
if (
1197+
child.elementType === element.type ||
11451198
// Keep this check inline so it only runs on the false path:
11461199
(__DEV__
11471200
? isCompatibleFamilyForHotReloading(child, element)
11481201
: false)
1149-
) {
1150-
deleteRemainingChildren(returnFiber, child.sibling);
1151-
const existing = useFiber(
1152-
child,
1153-
element.type === REACT_FRAGMENT_TYPE
1154-
? element.props.children
1155-
: element.props,
1156-
expirationTime,
1157-
);
1158-
existing.ref = coerceRef(returnFiber, child, element);
1159-
existing.return = returnFiber;
1160-
if (__DEV__) {
1161-
existing._debugSource = element._source;
1162-
existing._debugOwner = element._owner;
1202+
) {
1203+
deleteRemainingChildren(returnFiber, child.sibling);
1204+
const existing = useFiber(child, element.props, expirationTime);
1205+
existing.ref = coerceRef(returnFiber, child, element);
1206+
existing.return = returnFiber;
1207+
if (__DEV__) {
1208+
existing._debugSource = element._source;
1209+
existing._debugOwner = element._owner;
1210+
}
1211+
return existing;
1212+
}
1213+
break;
11631214
}
1164-
return existing;
1165-
} else {
1166-
deleteRemainingChildren(returnFiber, child);
1167-
break;
11681215
}
1216+
// Didn't match.
1217+
deleteRemainingChildren(returnFiber, child);
1218+
break;
11691219
} else {
11701220
deleteChild(returnFiber, child);
11711221
}

packages/react-reconciler/src/ReactFiber.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
enableFundamentalAPI,
3434
enableUserTimingAPI,
3535
enableScopeAPI,
36+
enableChunksAPI,
3637
} from 'shared/ReactFeatureFlags';
3738
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
3839
import {ConcurrentRoot, BlockingRoot} from 'shared/ReactRootTags';
@@ -58,6 +59,7 @@ import {
5859
LazyComponent,
5960
FundamentalComponent,
6061
ScopeComponent,
62+
Chunk,
6163
} from 'shared/ReactWorkTags';
6264
import getComponentName from 'shared/getComponentName';
6365

@@ -89,6 +91,7 @@ import {
8991
REACT_LAZY_TYPE,
9092
REACT_FUNDAMENTAL_TYPE,
9193
REACT_SCOPE_TYPE,
94+
REACT_CHUNK_TYPE,
9295
} from 'shared/ReactSymbols';
9396

9497
let hasBadMapPolyfill;
@@ -384,6 +387,11 @@ export function resolveLazyComponentTag(Component: Function): WorkTag {
384387
if ($$typeof === REACT_MEMO_TYPE) {
385388
return MemoComponent;
386389
}
390+
if (enableChunksAPI) {
391+
if ($$typeof === REACT_CHUNK_TYPE) {
392+
return Chunk;
393+
}
394+
}
387395
}
388396
return IndeterminateComponent;
389397
}
@@ -666,6 +674,9 @@ export function createFiberFromTypeAndProps(
666674
fiberTag = LazyComponent;
667675
resolvedType = null;
668676
break getTag;
677+
case REACT_CHUNK_TYPE:
678+
fiberTag = Chunk;
679+
break getTag;
669680
case REACT_FUNDAMENTAL_TYPE:
670681
if (enableFundamentalAPI) {
671682
return createFiberFromFundamental(

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
IncompleteClassComponent,
4343
FundamentalComponent,
4444
ScopeComponent,
45+
Chunk,
4546
} from 'shared/ReactWorkTags';
4647
import {
4748
NoEffect,
@@ -64,6 +65,7 @@ import {
6465
enableFundamentalAPI,
6566
warnAboutDefaultPropsOnFunctionComponents,
6667
enableScopeAPI,
68+
enableChunksAPI,
6769
} from 'shared/ReactFeatureFlags';
6870
import invariant from 'shared/invariant';
6971
import shallowEqual from 'shared/shallowEqual';
@@ -689,6 +691,82 @@ function updateFunctionComponent(
689691
return workInProgress.child;
690692
}
691693

694+
function updateChunk(
695+
current: Fiber | null,
696+
workInProgress: Fiber,
697+
chunk: any,
698+
nextProps: any,
699+
renderExpirationTime: ExpirationTime,
700+
) {
701+
// TODO: current can be non-null here even if the component
702+
// hasn't yet mounted. This happens after the first render suspends.
703+
// We'll need to figure out if this is fine or can cause issues.
704+
705+
const render = chunk.render;
706+
const data = chunk.query();
707+
708+
// The rest is a fork of updateFunctionComponent
709+
let nextChildren;
710+
prepareToReadContext(workInProgress, renderExpirationTime);
711+
if (__DEV__) {
712+
ReactCurrentOwner.current = workInProgress;
713+
setCurrentPhase('render');
714+
nextChildren = renderWithHooks(
715+
current,
716+
workInProgress,
717+
render,
718+
nextProps,
719+
data,
720+
renderExpirationTime,
721+
);
722+
if (
723+
debugRenderPhaseSideEffectsForStrictMode &&
724+
workInProgress.mode & StrictMode
725+
) {
726+
// Only double-render components with Hooks
727+
if (workInProgress.memoizedState !== null) {
728+
nextChildren = renderWithHooks(
729+
current,
730+
workInProgress,
731+
render,
732+
nextProps,
733+
data,
734+
renderExpirationTime,
735+
);
736+
}
737+
}
738+
setCurrentPhase(null);
739+
} else {
740+
nextChildren = renderWithHooks(
741+
current,
742+
workInProgress,
743+
render,
744+
nextProps,
745+
data,
746+
renderExpirationTime,
747+
);
748+
}
749+
750+
if (current !== null && !didReceiveUpdate) {
751+
bailoutHooks(current, workInProgress, renderExpirationTime);
752+
return bailoutOnAlreadyFinishedWork(
753+
current,
754+
workInProgress,
755+
renderExpirationTime,
756+
);
757+
}
758+
759+
// React DevTools reads this flag.
760+
workInProgress.effectTag |= PerformedWork;
761+
reconcileChildren(
762+
current,
763+
workInProgress,
764+
nextChildren,
765+
renderExpirationTime,
766+
);
767+
return workInProgress.child;
768+
}
769+
692770
function updateClassComponent(
693771
current: Fiber | null,
694772
workInProgress: Fiber,
@@ -1132,6 +1210,20 @@ function mountLazyComponent(
11321210
);
11331211
return child;
11341212
}
1213+
case Chunk: {
1214+
if (enableChunksAPI) {
1215+
// TODO: Resolve for Hot Reloading.
1216+
child = updateChunk(
1217+
null,
1218+
workInProgress,
1219+
Component,
1220+
props,
1221+
renderExpirationTime,
1222+
);
1223+
return child;
1224+
}
1225+
break;
1226+
}
11351227
}
11361228
let hint = '';
11371229
if (__DEV__) {
@@ -3192,6 +3284,20 @@ function beginWork(
31923284
}
31933285
break;
31943286
}
3287+
case Chunk: {
3288+
if (enableChunksAPI) {
3289+
const chunk = workInProgress.type;
3290+
const props = workInProgress.pendingProps;
3291+
return updateChunk(
3292+
current,
3293+
workInProgress,
3294+
chunk,
3295+
props,
3296+
renderExpirationTime,
3297+
);
3298+
}
3299+
break;
3300+
}
31953301
}
31963302
invariant(
31973303
false,

0 commit comments

Comments
 (0)