Skip to content

Commit

Permalink
[Flight] Chunks API (#17398)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sebmarkbage authored Dec 18, 2019
1 parent 9354dd2 commit 7dc9745
Show file tree
Hide file tree
Showing 23 changed files with 556 additions and 60 deletions.
4 changes: 3 additions & 1 deletion packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SimpleMemoComponent,
ContextProvider,
ForwardRef,
Chunk,
} from 'shared/ReactWorkTags';

type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
Expand Down Expand Up @@ -623,7 +624,8 @@ export function inspectHooksOfFiber(
if (
fiber.tag !== FunctionComponent &&
fiber.tag !== SimpleMemoComponent &&
fiber.tag !== ForwardRef
fiber.tag !== ForwardRef &&
fiber.tag !== Chunk
) {
throw new Error(
'Unknown Fiber. Needs to be a function component to inspect hooks.',
Expand Down
142 changes: 96 additions & 46 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ import {
REACT_ELEMENT_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_CHUNK_TYPE,
} from 'shared/ReactSymbols';
import {
FunctionComponent,
ClassComponent,
HostText,
HostPortal,
Fragment,
Chunk,
} from 'shared/ReactWorkTags';
import invariant from 'shared/invariant';
import {warnAboutStringRefs} from 'shared/ReactFeatureFlags';
import {warnAboutStringRefs, enableChunksAPI} from 'shared/ReactFeatureFlags';

import {
createWorkInProgress,
Expand Down Expand Up @@ -392,32 +394,47 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
if (
current !== null &&
(current.elementType === element.type ||
if (current !== null) {
if (
current.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false))
) {
// Move based on index
const existing = useFiber(current, element.props, expirationTime);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
) {
// Move based on index
const existing = useFiber(current, element.props, expirationTime);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
} else if (
enableChunksAPI &&
current.tag === Chunk &&
element.type.$$typeof === REACT_CHUNK_TYPE &&
element.type.render === current.type.render
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props, expirationTime);
existing.return = returnFiber;
existing.type = element.type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
return existing;
} else {
// Insert
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, current, element);
created.return = returnFiber;
return created;
}
// Insert
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, current, element);
created.return = returnFiber;
return created;
}

function updatePortal(
Expand Down Expand Up @@ -1138,34 +1155,67 @@ function ChildReconciler(shouldTrackSideEffects) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
: child.elementType === element.type ||
switch (child.tag) {
case Fragment: {
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(
child,
element.props.children,
expirationTime,
);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
case Chunk:
if (enableChunksAPI) {
if (
element.type.$$typeof === REACT_CHUNK_TYPE &&
element.type.render === child.type.render
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props, expirationTime);
existing.type = element.type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
}
// We intentionally fallthrough here if enableChunksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default: {
if (
child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(
child,
element.type === REACT_FRAGMENT_TYPE
? element.props.children
: element.props,
expirationTime,
);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props, expirationTime);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
// Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
enableFundamentalAPI,
enableUserTimingAPI,
enableScopeAPI,
enableChunksAPI,
} from 'shared/ReactFeatureFlags';
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
import {ConcurrentRoot, BlockingRoot} from 'shared/ReactRootTags';
Expand All @@ -58,6 +59,7 @@ import {
LazyComponent,
FundamentalComponent,
ScopeComponent,
Chunk,
} from 'shared/ReactWorkTags';
import getComponentName from 'shared/getComponentName';

Expand Down Expand Up @@ -89,6 +91,7 @@ import {
REACT_LAZY_TYPE,
REACT_FUNDAMENTAL_TYPE,
REACT_SCOPE_TYPE,
REACT_CHUNK_TYPE,
} from 'shared/ReactSymbols';

let hasBadMapPolyfill;
Expand Down Expand Up @@ -384,6 +387,11 @@ export function resolveLazyComponentTag(Component: Function): WorkTag {
if ($$typeof === REACT_MEMO_TYPE) {
return MemoComponent;
}
if (enableChunksAPI) {
if ($$typeof === REACT_CHUNK_TYPE) {
return Chunk;
}
}
}
return IndeterminateComponent;
}
Expand Down Expand Up @@ -666,6 +674,9 @@ export function createFiberFromTypeAndProps(
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
case REACT_CHUNK_TYPE:
fiberTag = Chunk;
break getTag;
case REACT_FUNDAMENTAL_TYPE:
if (enableFundamentalAPI) {
return createFiberFromFundamental(
Expand Down
106 changes: 106 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
IncompleteClassComponent,
FundamentalComponent,
ScopeComponent,
Chunk,
} from 'shared/ReactWorkTags';
import {
NoEffect,
Expand All @@ -64,6 +65,7 @@ import {
enableFundamentalAPI,
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
enableChunksAPI,
} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import shallowEqual from 'shared/shallowEqual';
Expand Down Expand Up @@ -689,6 +691,82 @@ function updateFunctionComponent(
return workInProgress.child;
}

function updateChunk(
current: Fiber | null,
workInProgress: Fiber,
chunk: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.

const render = chunk.render;
const data = chunk.query();

// The rest is a fork of updateFunctionComponent
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setCurrentPhase('render');
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
data,
renderExpirationTime,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
// Only double-render components with Hooks
if (workInProgress.memoizedState !== null) {
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
data,
renderExpirationTime,
);
}
}
setCurrentPhase(null);
} else {
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
data,
renderExpirationTime,
);
}

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderExpirationTime);
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}

function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Expand Down Expand Up @@ -1132,6 +1210,20 @@ function mountLazyComponent(
);
return child;
}
case Chunk: {
if (enableChunksAPI) {
// TODO: Resolve for Hot Reloading.
child = updateChunk(
null,
workInProgress,
Component,
props,
renderExpirationTime,
);
return child;
}
break;
}
}
let hint = '';
if (__DEV__) {
Expand Down Expand Up @@ -3192,6 +3284,20 @@ function beginWork(
}
break;
}
case Chunk: {
if (enableChunksAPI) {
const chunk = workInProgress.type;
const props = workInProgress.pendingProps;
return updateChunk(
current,
workInProgress,
chunk,
props,
renderExpirationTime,
);
}
break;
}
}
invariant(
false,
Expand Down
Loading

0 comments on commit 7dc9745

Please sign in to comment.