From 2fb2dbb0ee0f581b5683db65e25012437b1533ee Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 3 Aug 2021 18:56:10 -0400 Subject: [PATCH 1/2] Re-add old Fabric Offscreen impl behind flag There's a chance that #21960 will affect layout in a way that we don't expect, so I'm adding back the old implementation so we can toggle the feature with a flag. The flag should read from the ReactNativeFeatureFlags shim so that we can change it at runtime. I'll do that separately. --- .../src/ReactFabricHostConfig.js | 26 ++++++ .../src/createReactNoop.js | 48 ++++++++++ .../src/ReactFiberBeginWork.new.js | 12 ++- .../src/ReactFiberBeginWork.old.js | 12 ++- .../src/ReactFiberCompleteWork.new.js | 87 ++++++++++++++++--- .../src/ReactFiberCompleteWork.old.js | 87 ++++++++++++++++--- .../ReactFiberHostConfigWithNoPersistence.js | 2 + .../src/ReactFiberThrow.new.js | 3 +- .../src/ReactFiberThrow.old.js | 3 +- .../src/forks/ReactFiberHostConfig.custom.js | 2 + packages/shared/ReactFeatureFlags.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 2 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.testing.js | 1 + .../forks/ReactFeatureFlags.testing.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 2 + 20 files changed, 259 insertions(+), 36 deletions(-) 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.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 9e52536fbb36d..8d0a2256ec7d2 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -60,6 +60,8 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; +// TODO: Import this from internal ReactNativeFeatureFlags instead +export const enablePersistentOffscreenHostContainer = __EXPERIMENTAL__; // 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.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; From 113785492534289949777461dd88de871ed7ad70 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 3 Aug 2021 20:40:05 -0400 Subject: [PATCH 2/2] Import dynamic RN flags from external module Internal feature flags that we wish to control with a GK can now be imported from an external module, which I've called "ReactNativeInternalFeatureFlags". We'll need to add this module to the downstream repo. We can't yet use this in our tests, because we don't have a test configuration that runs against the React Native feature flags fork. We should set up that up the same way we did for www. --- .../ReactFeatureFlags.native-fb-dynamic.js | 29 +++++++++++++++++++ .../forks/ReactFeatureFlags.native-fb.js | 10 +++++-- scripts/flow/config/flowconfig | 1 + scripts/flow/xplat.js | 12 ++++++++ scripts/rollup/bundles.js | 23 ++++++++++----- scripts/rollup/modules.js | 2 ++ 6 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js create mode 100644 scripts/flow/xplat.js 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 8d0a2256ec7d2..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; @@ -60,8 +68,6 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; -// TODO: Import this from internal ReactNativeFeatureFlags instead -export const enablePersistentOffscreenHostContainer = __EXPERIMENTAL__; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars 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' }.