diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index ac7fb64d922cd..7b2d1f5a13665 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -457,6 +457,32 @@ export function getOffscreenContainerProps( } } +export function cloneHiddenInstance( + instance: Instance, + type: string, + props: Props, + internalInstanceHandle: Object, +): Instance { + const viewConfig = instance.canonical.viewConfig; + const node = instance.node; + const updatePayload = create( + {style: {display: 'none'}}, + viewConfig.validAttributes, + ); + return { + node: cloneNodeWithNewProps(node, updatePayload), + canonical: instance.canonical, + }; +} + +export function cloneHiddenTextInstance( + instance: Instance, + text: string, + internalInstanceHandle: Object, +): TextInstance { + throw new Error('Not yet implemented.'); +} + export function createContainerChildSet(container: Container): ChildSet { return createChildNodeSet(container); } diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index bc31a1c7c3cae..9e2c1ba7f876c 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -582,6 +582,54 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { children, }; }, + + cloneHiddenInstance( + instance: Instance, + type: string, + props: Props, + internalInstanceHandle: Object, + ): Instance { + const clone = cloneInstance( + instance, + null, + type, + props, + props, + internalInstanceHandle, + true, + null, + ); + clone.hidden = true; + return clone; + }, + + cloneHiddenTextInstance( + instance: TextInstance, + text: string, + internalInstanceHandle: Object, + ): TextInstance { + const clone = { + text: instance.text, + id: instance.id, + parent: instance.parent, + hidden: true, + context: instance.context, + }; + // Hide from unit tests + Object.defineProperty(clone, 'id', { + value: clone.id, + enumerable: false, + }); + Object.defineProperty(clone, 'parent', { + value: clone.parent, + enumerable: false, + }); + Object.defineProperty(clone, 'context', { + value: clone.context, + enumerable: false, + }); + return clone; + }, }; const NoopRenderer = reconciler(hostConfig); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index a687e9d16315d..9c5ca07008552 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -89,6 +89,7 @@ import { enableLazyContextPropagation, enableSuspenseLayoutEffectSemantics, enableSchedulingProfiler, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; @@ -146,7 +147,6 @@ import { registerSuspenseInstanceRetry, supportsHydration, isPrimaryRenderer, - supportsMutation, supportsPersistence, getOffscreenContainerProps, } from './ReactFiberHostConfig'; @@ -744,7 +744,7 @@ function updateOffscreenComponent( workInProgress.updateQueue = spawnedCachePool; } - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // TODO: Optimize this to use the OffscreenComponent fiber instead of // an extra HostComponent fiber. Need to make sure this doesn't break Fabric @@ -760,12 +760,10 @@ function updateOffscreenComponent( renderLanes, ); return offscreenContainer; - } - if (supportsMutation) { + } else { reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } - return null; } function reconcileOffscreenHostContainer( @@ -2383,7 +2381,7 @@ function updateSuspenseFallbackChildren( currentPrimaryChildFragment.treeBaseDuration; } - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // We need to complete it now, because we're going to skip over its normal // complete phase and go straight to rendering the fallback. @@ -2411,7 +2409,7 @@ function updateSuspenseFallbackChildren( primaryChildProps, ); - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // We need to complete it now, because we're going to skip over its normal // complete phase and go straight to rendering the fallback. diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index a1153497c5cde..2e6448d338f84 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -89,6 +89,7 @@ import { enableLazyContextPropagation, enableSuspenseLayoutEffectSemantics, enableSchedulingProfiler, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; @@ -146,7 +147,6 @@ import { registerSuspenseInstanceRetry, supportsHydration, isPrimaryRenderer, - supportsMutation, supportsPersistence, getOffscreenContainerProps, } from './ReactFiberHostConfig'; @@ -744,7 +744,7 @@ function updateOffscreenComponent( workInProgress.updateQueue = spawnedCachePool; } - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // TODO: Optimize this to use the OffscreenComponent fiber instead of // an extra HostComponent fiber. Need to make sure this doesn't break Fabric @@ -760,12 +760,10 @@ function updateOffscreenComponent( renderLanes, ); return offscreenContainer; - } - if (supportsMutation) { + } else { reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } - return null; } function reconcileOffscreenHostContainer( @@ -2383,7 +2381,7 @@ function updateSuspenseFallbackChildren( currentPrimaryChildFragment.treeBaseDuration; } - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // We need to complete it now, because we're going to skip over its normal // complete phase and go straight to rendering the fallback. @@ -2411,7 +2409,7 @@ function updateSuspenseFallbackChildren( primaryChildProps, ); - if (supportsPersistence) { + if (enablePersistentOffscreenHostContainer && supportsPersistence) { // In persistent mode, the offscreen children are wrapped in a host node. // We need to complete it now, because we're going to skip over its normal // complete phase and go straight to rendering the fallback. diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index fea034edaa67f..9eff02536d5f1 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -84,6 +84,8 @@ import { supportsMutation, supportsPersistence, cloneInstance, + cloneHiddenInstance, + cloneHiddenTextInstance, createContainerChildSet, appendChildToContainerChildSet, finalizeContainerChildren, @@ -128,6 +130,7 @@ import { enableProfilerTimer, enableCache, enableSuspenseLayoutEffectSemantics, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import { renderDidSuspend, @@ -198,7 +201,12 @@ let updateHostText; if (supportsMutation) { // Mutation mode - appendAllChildren = function(parent: Instance, workInProgress: Fiber) { + appendAllChildren = function( + parent: Instance, + workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, + ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. let node = workInProgress.child; @@ -286,22 +294,53 @@ if (supportsMutation) { } else if (supportsPersistence) { // Persistent host tree mode - appendAllChildren = function(parent: Instance, workInProgress: Fiber) { + appendAllChildren = function( + parent: Instance, + workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, + ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. let node = workInProgress.child; while (node !== null) { // eslint-disable-next-line no-labels branches: if (node.tag === HostComponent) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const props = node.memoizedProps; + const type = node.type; + instance = cloneHiddenInstance(instance, type, props, node); + } appendInitialChild(parent, instance); } else if (node.tag === HostText) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const text = node.memoizedProps; + instance = cloneHiddenTextInstance(instance, text, node); + } appendInitialChild(parent, instance); } else if (node.tag === HostPortal) { // If we have a portal child, then we don't want to traverse // down its children. Instead, we'll get insertions from each child in // the portal directly. + } else if ( + node.tag === OffscreenComponent && + node.memoizedState !== null + ) { + // The children in this boundary are hidden. Toggle their visibility + // before appending. + const child = node.child; + if (child !== null) { + child.return = node; + } + if (enablePersistentOffscreenHostContainer) { + appendAllChildren(parent, node, false, false); + } else { + appendAllChildren(parent, node, true, true); + } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -327,6 +366,8 @@ if (supportsMutation) { const appendAllChildrenToContainer = function( containerChildSet: ChildSet, workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. @@ -334,15 +375,41 @@ if (supportsMutation) { while (node !== null) { // eslint-disable-next-line no-labels branches: if (node.tag === HostComponent) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const props = node.memoizedProps; + const type = node.type; + instance = cloneHiddenInstance(instance, type, props, node); + } appendChildToContainerChildSet(containerChildSet, instance); } else if (node.tag === HostText) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const text = node.memoizedProps; + instance = cloneHiddenTextInstance(instance, text, node); + } appendChildToContainerChildSet(containerChildSet, instance); } else if (node.tag === HostPortal) { // If we have a portal child, then we don't want to traverse // down its children. Instead, we'll get insertions from each child in // the portal directly. + } else if ( + node.tag === OffscreenComponent && + node.memoizedState !== null + ) { + // The children in this boundary are hidden. Toggle their visibility + // before appending. + const child = node.child; + if (child !== null) { + child.return = node; + } + if (enablePersistentOffscreenHostContainer) { + appendAllChildrenToContainer(containerChildSet, node, false, false); + } else { + appendAllChildrenToContainer(containerChildSet, node, true, true); + } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -376,7 +443,7 @@ if (supportsMutation) { const container = portalOrRoot.containerInfo; const newChildSet = createContainerChildSet(container); // If children might have changed, we have to add them all to the set. - appendAllChildrenToContainer(newChildSet, workInProgress); + appendAllChildrenToContainer(newChildSet, workInProgress, false, false); portalOrRoot.pendingChildren = newChildSet; // Schedule an update on the container to swap out the container. markUpdate(workInProgress); @@ -449,7 +516,7 @@ if (supportsMutation) { markUpdate(workInProgress); } else { // If children might have changed, we have to add them all to the set. - appendAllChildren(newInstance, workInProgress); + appendAllChildren(newInstance, workInProgress, false, false); } }; updateHostText = function( @@ -722,7 +789,7 @@ export function completeSuspendedOffscreenHostContainer( workInProgress, ); - appendAllChildren(instance, workInProgress); + appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; @@ -869,7 +936,7 @@ function completeWork( workInProgress, ); - appendAllChildren(instance, workInProgress); + appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 4658938c9d5d4..b8011b7dcb4ba 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -84,6 +84,8 @@ import { supportsMutation, supportsPersistence, cloneInstance, + cloneHiddenInstance, + cloneHiddenTextInstance, createContainerChildSet, appendChildToContainerChildSet, finalizeContainerChildren, @@ -128,6 +130,7 @@ import { enableProfilerTimer, enableCache, enableSuspenseLayoutEffectSemantics, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import { renderDidSuspend, @@ -198,7 +201,12 @@ let updateHostText; if (supportsMutation) { // Mutation mode - appendAllChildren = function(parent: Instance, workInProgress: Fiber) { + appendAllChildren = function( + parent: Instance, + workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, + ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. let node = workInProgress.child; @@ -286,22 +294,53 @@ if (supportsMutation) { } else if (supportsPersistence) { // Persistent host tree mode - appendAllChildren = function(parent: Instance, workInProgress: Fiber) { + appendAllChildren = function( + parent: Instance, + workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, + ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. let node = workInProgress.child; while (node !== null) { // eslint-disable-next-line no-labels branches: if (node.tag === HostComponent) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const props = node.memoizedProps; + const type = node.type; + instance = cloneHiddenInstance(instance, type, props, node); + } appendInitialChild(parent, instance); } else if (node.tag === HostText) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const text = node.memoizedProps; + instance = cloneHiddenTextInstance(instance, text, node); + } appendInitialChild(parent, instance); } else if (node.tag === HostPortal) { // If we have a portal child, then we don't want to traverse // down its children. Instead, we'll get insertions from each child in // the portal directly. + } else if ( + node.tag === OffscreenComponent && + node.memoizedState !== null + ) { + // The children in this boundary are hidden. Toggle their visibility + // before appending. + const child = node.child; + if (child !== null) { + child.return = node; + } + if (enablePersistentOffscreenHostContainer) { + appendAllChildren(parent, node, false, false); + } else { + appendAllChildren(parent, node, true, true); + } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -327,6 +366,8 @@ if (supportsMutation) { const appendAllChildrenToContainer = function( containerChildSet: ChildSet, workInProgress: Fiber, + needsVisibilityToggle: boolean, + isHidden: boolean, ) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. @@ -334,15 +375,41 @@ if (supportsMutation) { while (node !== null) { // eslint-disable-next-line no-labels branches: if (node.tag === HostComponent) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const props = node.memoizedProps; + const type = node.type; + instance = cloneHiddenInstance(instance, type, props, node); + } appendChildToContainerChildSet(containerChildSet, instance); } else if (node.tag === HostText) { - const instance = node.stateNode; + let instance = node.stateNode; + if (needsVisibilityToggle && isHidden) { + // This child is inside a timed out tree. Hide it. + const text = node.memoizedProps; + instance = cloneHiddenTextInstance(instance, text, node); + } appendChildToContainerChildSet(containerChildSet, instance); } else if (node.tag === HostPortal) { // If we have a portal child, then we don't want to traverse // down its children. Instead, we'll get insertions from each child in // the portal directly. + } else if ( + node.tag === OffscreenComponent && + node.memoizedState !== null + ) { + // The children in this boundary are hidden. Toggle their visibility + // before appending. + const child = node.child; + if (child !== null) { + child.return = node; + } + if (enablePersistentOffscreenHostContainer) { + appendAllChildrenToContainer(containerChildSet, node, false, false); + } else { + appendAllChildrenToContainer(containerChildSet, node, true, true); + } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -376,7 +443,7 @@ if (supportsMutation) { const container = portalOrRoot.containerInfo; const newChildSet = createContainerChildSet(container); // If children might have changed, we have to add them all to the set. - appendAllChildrenToContainer(newChildSet, workInProgress); + appendAllChildrenToContainer(newChildSet, workInProgress, false, false); portalOrRoot.pendingChildren = newChildSet; // Schedule an update on the container to swap out the container. markUpdate(workInProgress); @@ -449,7 +516,7 @@ if (supportsMutation) { markUpdate(workInProgress); } else { // If children might have changed, we have to add them all to the set. - appendAllChildren(newInstance, workInProgress); + appendAllChildren(newInstance, workInProgress, false, false); } }; updateHostText = function( @@ -722,7 +789,7 @@ export function completeSuspendedOffscreenHostContainer( workInProgress, ); - appendAllChildren(instance, workInProgress); + appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; @@ -869,7 +936,7 @@ function completeWork( workInProgress, ); - appendAllChildren(instance, workInProgress); + appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; diff --git a/packages/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.js b/packages/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.js index 2dd44342a399c..824cdeacc73fd 100644 --- a/packages/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.js +++ b/packages/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.js @@ -30,3 +30,5 @@ export const finalizeContainerChildren = shim; export const replaceContainerChildren = shim; export const getOffscreenContainerType = shim; export const getOffscreenContainerProps = shim; +export const cloneHiddenInstance = shim; +export const cloneHiddenTextInstance = shim; diff --git a/packages/react-reconciler/src/ReactFiberThrow.new.js b/packages/react-reconciler/src/ReactFiberThrow.new.js index 79b0ed6bc39ee..67ac620e80d87 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.new.js +++ b/packages/react-reconciler/src/ReactFiberThrow.new.js @@ -44,6 +44,7 @@ import { enableSchedulingProfiler, enableLazyContextPropagation, enableUpdaterTracking, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import {createCapturedValue} from './ReactCapturedValue'; import { @@ -321,7 +322,7 @@ function throwException( // all lifecycle effect tags. sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete); - if (supportsPersistence) { + if (supportsPersistence && enablePersistentOffscreenHostContainer) { // Another legacy Suspense quirk. In persistent mode, if this is the // initial mount, override the props of the host container to hide // its contents. diff --git a/packages/react-reconciler/src/ReactFiberThrow.old.js b/packages/react-reconciler/src/ReactFiberThrow.old.js index 9c2df828f817e..6309e20d9e622 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.old.js +++ b/packages/react-reconciler/src/ReactFiberThrow.old.js @@ -44,6 +44,7 @@ import { enableSchedulingProfiler, enableLazyContextPropagation, enableUpdaterTracking, + enablePersistentOffscreenHostContainer, } from 'shared/ReactFeatureFlags'; import {createCapturedValue} from './ReactCapturedValue'; import { @@ -321,7 +322,7 @@ function throwException( // all lifecycle effect tags. sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete); - if (supportsPersistence) { + if (supportsPersistence && enablePersistentOffscreenHostContainer) { // Another legacy Suspense quirk. In persistent mode, if this is the // initial mount, override the props of the host container to hide // its contents. diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index 9dbaa47eb7110..1cc10a78cb013 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -131,6 +131,8 @@ export const getOffscreenContainerType = $$$hostConfig.getOffscreenContainerType; export const getOffscreenContainerProps = $$$hostConfig.getOffscreenContainerProps; +export const cloneHiddenInstance = $$$hostConfig.cloneHiddenInstance; +export const cloneHiddenTextInstance = $$$hostConfig.cloneHiddenTextInstance; // ------------------- // Hydration diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index a1689f64a5d8e..6df5af24d953e 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -173,3 +173,5 @@ export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = false; + +export const enablePersistentOffscreenHostContainer = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js new file mode 100644 index 0000000000000..63f75e83b7656 --- /dev/null +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +import typeof * as ExportsType from './ReactFeatureFlags.native-fb-dynamic'; +import typeof * as DynamicFlagsType from 'ReactNativeInternalFeatureFlags'; + +// In xplat, these flags are controlled by GKs. Because most GKs have some +// population running in either mode, we should run our tests that way, too, +// +// Use __VARIANT__ to simulate a GK. The tests will be run twice: once +// with the __VARIANT__ set to `true`, and once set to `false`. +// +// TODO: __VARIANT__ isn't supported for React Native flags yet. You can set the +// flag here but it won't be set to `true` in any of our test runs. Need to +// update the test configuration. + +export const enablePersistentOffscreenHostContainer = __VARIANT__; + +// Flow magic to verify the exports of this file match the original version. +// eslint-disable-next-line no-unused-vars +type Check<_X, Y: _X, X: Y = _X> = null; +// eslint-disable-next-line no-unused-expressions +(null: Check); diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 9e52536fbb36d..f6ab60d3ed642 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -10,6 +10,14 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags'; import typeof * as ExportsType from './ReactFeatureFlags.native-fb'; +// Re-export dynamic flags from the internal module. Intentionally using * +// because this import is compiled to a `require` call. +import * as dynamicFlags from 'ReactNativeInternalFeatureFlags'; + +// We destructure each value before re-exporting to avoid a dynamic look-up on +// the exports object every time a flag is read. +export const {enablePersistentOffscreenHostContainer} = dynamicFlags; + // The rest of the flags are static for better dead code elimination. export const enableDebugTracing = false; export const enableSchedulingProfiler = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index b34b583f18861..5bcc6097447ab 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = false; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 7357217b16912..4a897ec77e46f 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = false; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 10a05e52e152a..48df0a41a08b4 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 617ec8f0bc186..481cf6b023ac4 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index 50e1c73f8dd20..48971f129b7b1 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = false; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index d16f6212c5485..c11a7757fa3c6 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -59,6 +59,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; +export const enablePersistentOffscreenHostContainer = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 591193ebb777b..72ae2f2c5b479 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -59,3 +59,4 @@ export const disableSchedulerTimeoutInWorkLoop = __VARIANT__; export const enableLazyContextPropagation = __VARIANT__; export const enableSyncDefaultUpdates = __VARIANT__; export const allowConcurrentByDefault = true; +export const enablePersistentOffscreenHostContainer = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index dadf7972612f5..d514e15e8827d 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -94,6 +94,8 @@ export const allowConcurrentByDefault = true; export const deletedTreeCleanUpLevel = 3; +export const enablePersistentOffscreenHostContainer = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/scripts/flow/config/flowconfig b/scripts/flow/config/flowconfig index ada7f2207be5c..2ace94c2d7cba 100644 --- a/scripts/flow/config/flowconfig +++ b/scripts/flow/config/flowconfig @@ -34,6 +34,7 @@ ./scripts/flow/react-devtools.js ./scripts/flow/react-native-host-hooks.js ./scripts/flow/react-relay-hooks.js +./scripts/flow/xplat.js [lints] untyped-type-import=error diff --git a/scripts/flow/xplat.js b/scripts/flow/xplat.js new file mode 100644 index 0000000000000..c013af3e125d8 --- /dev/null +++ b/scripts/flow/xplat.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +declare module 'ReactNativeInternalFeatureFlags' { + declare export var enablePersistentOffscreenHostContainer: boolean; +} diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index fb64c0e6e07f4..54c0552ba5649 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -79,7 +79,7 @@ const bundles = [ moduleType: ISOMORPHIC, entry: 'react', global: 'React', - externals: [], + externals: ['ReactNativeInternalFeatureFlags'], }, /******* Isomorphic Shared Subset *******/ @@ -105,7 +105,7 @@ const bundles = [ moduleType: ISOMORPHIC, entry: 'react/jsx-runtime', global: 'JSXRuntime', - externals: ['react'], + externals: ['react', 'ReactNativeInternalFeatureFlags'], }, /******* React JSX DEV Runtime *******/ @@ -124,7 +124,7 @@ const bundles = [ moduleType: ISOMORPHIC, entry: 'react/jsx-dev-runtime', global: 'JSXDEVRuntime', - externals: ['react'], + externals: ['react', 'ReactNativeInternalFeatureFlags'], }, /******* React Fetch Browser (experimental, new) *******/ @@ -372,6 +372,7 @@ const bundles = [ 'react', 'ReactFlightNativeRelayServerIntegration', 'JSResourceReferenceImpl', + 'ReactNativeInternalFeatureFlags', ], }, @@ -385,6 +386,7 @@ const bundles = [ 'react', 'ReactFlightNativeRelayClientIntegration', 'JSResourceReferenceImpl', + 'ReactNativeInternalFeatureFlags', ], }, @@ -432,7 +434,7 @@ const bundles = [ moduleType: RENDERER, entry: 'react-native-renderer', global: 'ReactNativeRenderer', - externals: ['react-native'], + externals: ['react-native', 'ReactNativeInternalFeatureFlags'], babel: opts => Object.assign({}, opts, { plugins: opts.plugins.concat([ @@ -462,7 +464,7 @@ const bundles = [ moduleType: RENDERER, entry: 'react-native-renderer/fabric', global: 'ReactFabric', - externals: ['react-native'], + externals: ['react-native', 'ReactNativeInternalFeatureFlags'], babel: opts => Object.assign({}, opts, { plugins: opts.plugins.concat([ @@ -499,7 +501,12 @@ const bundles = [ moduleType: RENDERER, entry: 'react-test-renderer', global: 'ReactTestRenderer', - externals: ['react', 'scheduler', 'scheduler/unstable_mock'], + externals: [ + 'react', + 'scheduler', + 'scheduler/unstable_mock', + 'ReactNativeInternalFeatureFlags', + ], babel: opts => Object.assign({}, opts, { plugins: opts.plugins.concat([ @@ -692,7 +699,7 @@ const bundles = [ moduleType: ISOMORPHIC, entry: 'scheduler', global: 'Scheduler', - externals: [], + externals: ['ReactNativeInternalFeatureFlags'], }, /******* React Scheduler Mock (experimental) *******/ @@ -710,7 +717,7 @@ const bundles = [ moduleType: ISOMORPHIC, entry: 'scheduler/unstable_mock', global: 'SchedulerMock', - externals: [], + externals: ['ReactNativeInternalFeatureFlags'], }, /******* React Scheduler Post Task (experimental) *******/ diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index b56f4231e2ef0..2461e28850cc0 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -21,6 +21,7 @@ const importSideEffects = Object.freeze({ 'react-fetch/node': HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'react-dom': HAS_NO_SIDE_EFFECTS_ON_IMPORT, url: HAS_NO_SIDE_EFFECTS_ON_IMPORT, + ReactNativeInternalFeatureFlags: HAS_NO_SIDE_EFFECTS_ON_IMPORT, }); // Bundles exporting globals that other modules rely on. @@ -31,6 +32,7 @@ const knownGlobals = Object.freeze({ 'react-interactions/events/tap': 'ReactEventsTap', scheduler: 'Scheduler', 'scheduler/unstable_mock': 'SchedulerMock', + ReactNativeInternalFeatureFlags: 'ReactNativeInternalFeatureFlags', }); // Given ['react'] in bundle externals, returns { 'react': 'React' }.