react/packages/react-test-renderer/src/ReactTestHostConfig.js

334 lines
7.9 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import isArray from 'shared/isArray';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export type Type = string;
export type Props = Object;
export type Container = {
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
children: Array<Instance | TextInstance>,
createNodeMock: Function,
tag: 'CONTAINER',
};
export type Instance = {
type: string,
props: Object,
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
isHidden: boolean,
children: Array<Instance | TextInstance>,
internalInstanceHandle: Object,
rootContainerInstance: Container,
tag: 'INSTANCE',
};
export type TextInstance = {
text: string,
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
isHidden: boolean,
tag: 'TEXT',
};
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = Instance | TextInstance;
export type HostContext = Object;
export type UpdatePayload = Object;
export type ChildSet = void; // Unused
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type EventResponder = any;
export type RendererInspectionConfig = $ReadOnly<{}>;
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
Experimental test selector API (#18607) Adds several new experimental APIs to aid with automated testing. Each of the methods below accepts an array of "selectors" that identifies a path (or paths) through a React tree. There are four basic selector types: * Component: Matches Fibers with the specified React component type * Role: Matches Host Instances matching the (explicit or implicit) accessibility role. * Test name: Matches Host Instances with a data-testname attribute. * Text: Matches Host Instances that directly contain the specified text. * There is also a special lookahead selector type that enables further matching within a path (without actually including the path in the result). This selector type was inspired by the :has() CSS pseudo-class. It enables e.g. matching a <section> that contained a specific header text, then finding a like button within that <section>. API * findAllNodes(): Finds all Host Instances (e.g. HTMLElement) within a host subtree that match the specified selector criteria. * getFindAllNodesFailureDescription(): Returns an error string describing the matched and unmatched portions of the selector query. * findBoundingRects(): For all React components within a host subtree that match the specified selector criteria, return a set of bounding boxes that covers the bounds of the nearest (shallowed) Host Instances within those trees. * observeVisibleRects(): For all React components within a host subtree that match the specified selector criteria, observe if it’s bounding rect is visible in the viewport and is not occluded. * focusWithin(): For all React components within a host subtree that match the specified selector criteria, set focus within the first focusable Host Instance (as if you started before this component in the tree and moved focus forwards one step).
2020-05-06 01:37:46 +08:00
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors';
Add `supportsMicrotasks` to the host config (#20809) * Add `supportsMicrotasks` to the host config Only certain renderers support scheduling a microtask, so we need a renderer specific flag that we can toggle. That way it's off for some renderers and on for others. I copied the approach we use for the other optional parts of the host config, like persistent mode and test selectors. Why isn't the feature flag sufficient? The feature flag modules, confusingly, are not renderer-specific, at least when running the our tests against the source files. They are meant to correspond to a release channel, not a renderer, but we got confused at some point and haven't cleaned it up. For example, when we run `yarn test`, Jest loads the flags from the default `ReactFeatureFlags.js` module, even when we import the React Native renderer — but in the actual builds, we load a different feature flag module, `ReactFeatureFlags.native-oss.js.` There's no way in our current Jest load a different host config for each renderer, because they all just import the same module. We should solve this by creating separate Jest project for each renderer, so that the flags loaded when running against source are the same ones that we use in the compiled bundles. The feature flag (`enableDiscreteMicrotasks`) still exists — it's used to set the React DOM host config's `supportsMicrotasks` flag to `true`. (Same for React Noop) The important part is that turning on the feature flag does *not* affect the other renderers, like React Native. The host config will likely outlive the feature flag, too, since the feature flag only exists so we can gradually roll it out and measure the impact in production; once we do, we'll remove it. Whereas the host config flag may continue to be used to disable the discrete microtask behavior for RN, because RN will likely use a native (non-JavaScript) API to schedule its tasks. * Add `supportsMicrotask` to react-reconciler README
2021-02-13 05:13:49 +08:00
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMicrotasks';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoResources';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoSingletons';
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
Inline fbjs/lib/emptyObject (#13055) * Inline fbjs/lib/emptyObject * Explicit naming * Compare to undefined * Another approach for detecting whether we can mutate Each renderer would have its own local LegacyRefsObject function. While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object? Then we can mutate it. If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor), or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing" refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore. * Clearer naming * Add test case for strings refs across renderers * Use a shared empty object for refs by reading it from React * Remove string refs from ReactART test It's not currently possible to resetModules() between several renderers without also resetting the `React` module. However, that leads to losing the referential identity of the empty ref object, and thus subsequent checks in the renderers for whether it is pooled fail (and cause assignments to a frozen object). This has always been the case, but we used to work around it by shimming fbjs/lib/emptyObject in tests and preserving its referential identity. This won't work anymore because we've inlined it. And preserving referential identity of React itself wouldn't be great because it could be confusing during testing (although we might want to revisit this in the future by moving its stateful parts into a separate package). For now, I'm removing string ref usage from this test because only this is the only place in our tests where we hit this problem, and it's only related to string refs, and not just ref mechanism in general. * Simplify the condition
2018-06-19 20:41:42 +08:00
const NO_CONTEXT = {};
const UPDATE_SIGNAL = {};
const nodeToInstanceMap = new WeakMap();
Inline fbjs/lib/emptyObject (#13055) * Inline fbjs/lib/emptyObject * Explicit naming * Compare to undefined * Another approach for detecting whether we can mutate Each renderer would have its own local LegacyRefsObject function. While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object? Then we can mutate it. If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor), or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing" refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore. * Clearer naming * Add test case for strings refs across renderers * Use a shared empty object for refs by reading it from React * Remove string refs from ReactART test It's not currently possible to resetModules() between several renderers without also resetting the `React` module. However, that leads to losing the referential identity of the empty ref object, and thus subsequent checks in the renderers for whether it is pooled fail (and cause assignments to a frozen object). This has always been the case, but we used to work around it by shimming fbjs/lib/emptyObject in tests and preserving its referential identity. This won't work anymore because we've inlined it. And preserving referential identity of React itself wouldn't be great because it could be confusing during testing (although we might want to revisit this in the future by moving its stateful parts into a separate package). For now, I'm removing string ref usage from this test because only this is the only place in our tests where we hit this problem, and it's only related to string refs, and not just ref mechanism in general. * Simplify the condition
2018-06-19 20:41:42 +08:00
if (__DEV__) {
Object.freeze(NO_CONTEXT);
Object.freeze(UPDATE_SIGNAL);
}
export function getPublicInstance(inst: Instance | TextInstance): $FlowFixMe {
switch (inst.tag) {
case 'INSTANCE':
const createNodeMock = inst.rootContainerInstance.createNodeMock;
const mockNode = createNodeMock({
type: inst.type,
props: inst.props,
});
if (typeof mockNode === 'object' && mockNode !== null) {
nodeToInstanceMap.set(mockNode, inst);
}
return mockNode;
default:
return inst;
}
}
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export function appendChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
if (__DEV__) {
if (!isArray(parentInstance.children)) {
console.error(
'An invalid container has been provided. ' +
'This may indicate that another renderer is being used in addition to the test renderer. ' +
'(For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) ' +
'This is not supported.',
);
}
}
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
}
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export function insertBefore(
parentInstance: Instance | Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
const beforeIndex = parentInstance.children.indexOf(beforeChild);
parentInstance.children.splice(beforeIndex, 0, child);
}
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export function removeChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
}
export function clearContainer(container: Container): void {
container.children.splice(0);
}
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
Inline fbjs/lib/emptyObject (#13055) * Inline fbjs/lib/emptyObject * Explicit naming * Compare to undefined * Another approach for detecting whether we can mutate Each renderer would have its own local LegacyRefsObject function. While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object? Then we can mutate it. If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor), or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing" refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore. * Clearer naming * Add test case for strings refs across renderers * Use a shared empty object for refs by reading it from React * Remove string refs from ReactART test It's not currently possible to resetModules() between several renderers without also resetting the `React` module. However, that leads to losing the referential identity of the empty ref object, and thus subsequent checks in the renderers for whether it is pooled fail (and cause assignments to a frozen object). This has always been the case, but we used to work around it by shimming fbjs/lib/emptyObject in tests and preserving its referential identity. This won't work anymore because we've inlined it. And preserving referential identity of React itself wouldn't be great because it could be confusing during testing (although we might want to revisit this in the future by moving its stateful parts into a separate package). For now, I'm removing string ref usage from this test because only this is the only place in our tests where we hit this problem, and it's only related to string refs, and not just ref mechanism in general. * Simplify the condition
2018-06-19 20:41:42 +08:00
return NO_CONTEXT;
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
}
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
Inline fbjs/lib/emptyObject (#13055) * Inline fbjs/lib/emptyObject * Explicit naming * Compare to undefined * Another approach for detecting whether we can mutate Each renderer would have its own local LegacyRefsObject function. While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object? Then we can mutate it. If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor), or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing" refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore. * Clearer naming * Add test case for strings refs across renderers * Use a shared empty object for refs by reading it from React * Remove string refs from ReactART test It's not currently possible to resetModules() between several renderers without also resetting the `React` module. However, that leads to losing the referential identity of the empty ref object, and thus subsequent checks in the renderers for whether it is pooled fail (and cause assignments to a frozen object). This has always been the case, but we used to work around it by shimming fbjs/lib/emptyObject in tests and preserving its referential identity. This won't work anymore because we've inlined it. And preserving referential identity of React itself wouldn't be great because it could be confusing during testing (although we might want to revisit this in the future by moving its stateful parts into a separate package). For now, I'm removing string ref usage from this test because only this is the only place in our tests where we hit this problem, and it's only related to string refs, and not just ref mechanism in general. * Simplify the condition
2018-06-19 20:41:42 +08:00
return NO_CONTEXT;
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
}
export function prepareForCommit(containerInfo: Container): null | Object {
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
// noop
return null;
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
}
export function resetAfterCommit(containerInfo: Container): void {
// noop
}
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
return {
type,
props,
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
isHidden: false,
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
children: [],
internalInstanceHandle,
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
rootContainerInstance,
tag: 'INSTANCE',
};
}
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
}
export function finalizeInitialChildren(
testElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
): boolean {
return false;
}
export function prepareUpdate(
testElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: Object,
): null | {...} {
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
return UPDATE_SIGNAL;
}
export function shouldSetTextContent(type: string, props: Props): boolean {
return false;
}
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
return {
text,
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
isHidden: false,
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
tag: 'TEXT',
};
}
export function getCurrentEventPriority(): * {
return DefaultEventPriority;
}
export const isPrimaryRenderer = false;
export const warnsIfNotActing = true;
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
export const scheduleTimeout = setTimeout;
export const cancelTimeout = clearTimeout;
Add `supportsMicrotasks` to the host config (#20809) * Add `supportsMicrotasks` to the host config Only certain renderers support scheduling a microtask, so we need a renderer specific flag that we can toggle. That way it's off for some renderers and on for others. I copied the approach we use for the other optional parts of the host config, like persistent mode and test selectors. Why isn't the feature flag sufficient? The feature flag modules, confusingly, are not renderer-specific, at least when running the our tests against the source files. They are meant to correspond to a release channel, not a renderer, but we got confused at some point and haven't cleaned it up. For example, when we run `yarn test`, Jest loads the flags from the default `ReactFeatureFlags.js` module, even when we import the React Native renderer — but in the actual builds, we load a different feature flag module, `ReactFeatureFlags.native-oss.js.` There's no way in our current Jest load a different host config for each renderer, because they all just import the same module. We should solve this by creating separate Jest project for each renderer, so that the flags loaded when running against source are the same ones that we use in the compiled bundles. The feature flag (`enableDiscreteMicrotasks`) still exists — it's used to set the React DOM host config's `supportsMicrotasks` flag to `true`. (Same for React Noop) The important part is that turning on the feature flag does *not* affect the other renderers, like React Native. The host config will likely outlive the feature flag, too, since the feature flag only exists so we can gradually roll it out and measure the impact in production; once we do, we'll remove it. Whereas the host config flag may continue to be used to disable the discrete microtask behavior for RN, because RN will likely use a native (non-JavaScript) API to schedule its tasks. * Add `supportsMicrotask` to react-reconciler README
2021-02-13 05:13:49 +08:00
export const noTimeout = -1;
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
// -------------------
// Mutation
// -------------------
export const supportsMutation = true;
export function commitUpdate(
instance: Instance,
updatePayload: {...},
Resolve host configs at build time (#12792) * Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes
2018-05-19 18:29:11 +08:00
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
}
export function commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
}
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
}
export function resetTextContent(testElement: Instance): void {
// noop
}
export const appendChildToContainer = appendChild;
export const insertInContainerBefore = insertBefore;
export const removeChildFromContainer = removeChild;
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
export function hideInstance(instance: Instance): void {
instance.isHidden = true;
}
export function hideTextInstance(textInstance: TextInstance): void {
textInstance.isHidden = true;
}
export function unhideInstance(instance: Instance, props: Props): void {
instance.isHidden = false;
}
export function unhideTextInstance(
textInstance: TextInstance,
text: string,
): void {
textInstance.isHidden = false;
}
export function getInstanceFromNode(mockNode: Object): Object | null {
const instance = nodeToInstanceMap.get(mockNode);
if (instance !== undefined) {
return instance.internalInstanceHandle;
}
return null;
}
export function beforeActiveInstanceBlur(internalInstanceHandle: Object) {
// noop
}
export function afterActiveInstanceBlur() {
// noop
}
export function preparePortalMount(portalInstance: Instance): void {
// noop
}
export function prepareScopeUpdate(scopeInstance: Object, inst: Object): void {
nodeToInstanceMap.set(scopeInstance, inst);
}
export function getInstanceFromScope(scopeInstance: Object): null | Object {
return nodeToInstanceMap.get(scopeInstance) || null;
}
export function detachDeletedInstance(node: Instance): void {
// noop
}
Add onRecoverableError option to hydrateRoot, createRoot (#23207) * [RFC] Add onHydrationError option to hydrateRoot This is not the final API but I'm pushing it for discussion purposes. When an error is thrown during hydration, we fallback to client rendering, without triggering an error boundary. This is good because, in many cases, the UI will recover and the user won't even notice that something has gone wrong behind the scenes. However, we shouldn't recover from these errors silently, because the underlying cause might be pretty serious. Server-client mismatches are not supposed to happen, even if UI doesn't break from the users perspective. Ignoring them could lead to worse problems later. De-opting from server to client rendering could also be a significant performance regression, depending on the scope of the UI it affects. So we need a way to log when hydration errors occur. This adds a new option for `hydrateRoot` called `onHydrationError`. It's symmetrical to the server renderer's `onError` option, and serves the same purpose. When no option is provided, the default behavior is to schedule a browser task and rethrow the error. This will trigger the normal browser behavior for errors, including dispatching an error event. If the app already has error monitoring, this likely will just work as expected without additional configuration. However, we can also expose additional metadata about these errors, like which Suspense boundaries were affected by the de-opt to client rendering. (I have not exposed any metadata in this commit; API needs more design work.) There are other situations besides hydration where we recover from an error without surfacing it to the user, or notifying an error boundary. For example, if an error occurs during a concurrent render, it could be due to a data race, so we try again synchronously in case that fixes it. We should probably expose a way to log these types of errors, too. (Also not implemented in this commit.) * Log all recoverable errors This expands the scope of onHydrationError to include all errors that are not surfaced to the UI (an error boundary). In addition to errors that occur during hydration, this also includes errors that recoverable by de-opting to synchronous rendering. Typically (or really, by definition) these errors are the result of a concurrent data race; blocking the main thread fixes them by prevents subsequent races. The logic for de-opting to synchronous rendering already existed. The only thing that has changed is that we now log the errors instead of silently proceeding. The logging API has been renamed from onHydrationError to onRecoverableError. * Don't log recoverable errors until commit phase If the render is interrupted and restarts, we don't want to log the errors multiple times. This change only affects errors that are recovered by de-opting to synchronous rendering; we'll have to do something else for errors during hydration, since they use a different recovery path. * Only log hydration error if client render succeeds Similar to previous step. When an error occurs during hydration, we only want to log it if falling back to client rendering _succeeds_. If client rendering fails, the error will get reported to the nearest error boundary, so there's no need for a duplicate log. To implement this, I added a list of errors to the hydration context. If the Suspense boundary successfully completes, they are added to the main recoverable errors queue (the one I added in the previous step.) * Log error with queueMicrotask instead of Scheduler If onRecoverableError is not provided, we default to rethrowing the error in a separate task. Originally, I scheduled the task with idle priority, but @sebmarkbage made the good point that if there are multiple errors logs, we want to preserve the original order. So I've switched it to a microtask. The priority can be lowered in userspace by scheduling an additional task inside onRecoverableError. * Only use host config method for default behavior Redefines the contract of the host config's logRecoverableError method to be a default implementation for onRecoverableError if a user-provided one is not provided when the root is created. * Log with reportError instead of rethrowing In modern browsers, reportError will dispatch an error event, emulating an uncaught JavaScript error. We can do this instead of rethrowing recoverable errors in a microtask, which is nice because it avoids any subtle ordering issues. In older browsers and test environments, we'll fall back to console.error. * Naming nits queueRecoverableHydrationErrors -> upgradeHydrationErrorsToRecoverable
2022-02-04 23:57:33 +08:00
export function logRecoverableError(error: mixed): void {
// noop
}
export function requestPostPaintCallback(callback: (time: number) => void) {
// noop
}
export function prepareRendererToRender(container: Container): void {
// noop
}
export function resetRendererAfterRender(): void {
// noop
}