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

[Flight] Chunks API #17398

Merged
merged 9 commits into from
Dec 18, 2019
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
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
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
) {
// 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
gaearon marked this conversation as resolved.
Show resolved Hide resolved
: 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,
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
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) {
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
gaearon marked this conversation as resolved.
Show resolved Hide resolved
}
invariant(
false,
Expand Down
Loading