From 1c7055ddbbdae81224f998e42832d882fde06686 Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 8 Dec 2022 11:22:35 -0800 Subject: [PATCH 001/269] [DiffTrain] Add github url for the commit to the commit message (#25845) Currently we just append the ref for the commit, let's make it clickable for easier debugging in syncs. --- .github/workflows/commit_artifacts.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/commit_artifacts.yml b/.github/workflows/commit_artifacts.yml index 9505643dc3fe..05744cb5cfce 100644 --- a/.github/workflows/commit_artifacts.yml +++ b/.github/workflows/commit_artifacts.yml @@ -158,7 +158,8 @@ jobs: commit_message: | ${{ github.event.head_commit.message }} - DiffTrain build for `${{ github.sha }}` + DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }}) + [View git log for this commit](https://github.com/facebook/react/commits/${{ github.sha }}) branch: builds/facebook-www commit_user_name: ${{ github.actor }} commit_user_email: ${{ github.actor }}@users.noreply.github.com From b14d7fa4b88dad5f0017d084e462952c700aa2ad Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Fri, 9 Dec 2022 14:43:52 +0000 Subject: [PATCH 002/269] Add support for setNativeProps to Fabric (#25737) Add support for `setNativeProps` in Fabric to make migration to the new architecture easier. The React Native part of this has already landed in the core and iOS in https://github.com/facebook/react-native/commit/1d3fa40c59b234f21f516db85c322ec0ed0311e0. It is still recommended to move away from `setNativeProps` because the API will not work with future features. --- .../src/ReactFabricHostConfig.js | 16 +++++++++++----- .../InitializeNativeFabricUIManager.js | 2 ++ .../ReactFabricHostComponent-test.internal.js | 14 ++++---------- scripts/flow/react-native-host-hooks.js | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 078bf1f11ac6..b6d370882ae4 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -18,7 +18,10 @@ import type { TouchedViewDataAtPoint, } from './ReactNativeTypes'; -import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils'; +import { + mountSafeCallback_NOT_REALLY_SAFE, + warnForStyleProps, +} from './NativeMethodsMixinUtils'; import {create, diff} from './ReactNativeAttributePayload'; import {dispatchEvent} from './ReactFabricEventEmitter'; @@ -52,6 +55,7 @@ const { unstable_DefaultEventPriority: FabricDefaultPriority, unstable_DiscreteEventPriority: FabricDiscretePriority, unstable_getCurrentEventPriority: fabricGetCurrentEventPriority, + setNativeProps, } = nativeFabricUIManager; const {get: getViewConfigForType} = ReactNativeViewConfigRegistry; @@ -208,12 +212,14 @@ class ReactFabricHostComponent { setNativeProps(nativeProps: Object) { if (__DEV__) { - console.error( - 'Warning: setNativeProps is not currently supported in Fabric', - ); + warnForStyleProps(nativeProps, this.viewConfig.validAttributes); } + const updatePayload = create(nativeProps, this.viewConfig.validAttributes); - return; + const {stateNode} = this._internalInstanceHandle; + if (stateNode != null && updatePayload != null) { + setNativeProps(stateNode.node, updatePayload); + } } // This API (addEventListener, removeEventListener) attempts to adhere to the diff --git a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js index abb2883d387e..ab4fc291d6d6 100644 --- a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js +++ b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js @@ -117,6 +117,8 @@ const RCTFabricUIManager = { dispatchCommand: jest.fn(), + setNativeProps: jest.fn(), + sendAccessibilityEvent: jest.fn(), registerEventHandler: jest.fn(function registerEventHandler(callback) {}), diff --git a/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js index 337a4976bbe8..51e056d1c8bc 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js @@ -38,7 +38,7 @@ function mockRenderKeys(keyLists) { const mockContainerTag = 11; const MockView = createReactNativeComponentClass('RCTMockView', () => ({ - validAttributes: {}, + validAttributes: {foo: true}, uiViewClassName: 'RCTMockView', })); @@ -200,21 +200,15 @@ describe('measureLayout', () => { }); describe('setNativeProps', () => { - test('setNativeProps(...) emits a warning', () => { + test('setNativeProps(...) invokes setNativeProps on Fabric UIManager', () => { const { UIManager, } = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'); const [[fooRef]] = mockRenderKeys([['foo']]); + fooRef.setNativeProps({foo: 'baz'}); - expect(() => { - fooRef.setNativeProps({}); - }).toErrorDev( - ['Warning: setNativeProps is not currently supported in Fabric'], - { - withoutStack: true, - }, - ); expect(UIManager.updateView).not.toBeCalled(); + expect(nativeFabricUIManager.setNativeProps).toHaveBeenCalledTimes(1); }); }); diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index 584f24ee084c..e3c98114935f 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -186,7 +186,7 @@ declare var nativeFabricUIManager: { payload: Object, ) => void, ) => void, - + setNativeProps: (node: Object, nativeProps: Object) => Object, dispatchCommand: (node: Object, command: string, args: Array) => void, sendAccessibilityEvent: (node: Object, eventTypeName: string) => void, From 996e4c0d56dabab382ca932cd5b8517e63020999 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Mon, 12 Dec 2022 14:00:16 +0000 Subject: [PATCH 003/269] Offscreen add attach (#25603) `Offscreen.attach` is imperative API to signal to Offscreen that its updates should be high priority and effects should be mounted. Coupled with `Offscreen.detach` it gives ability to manually control Offscreen. Unlike with mode `visible` and `hidden`, it is developers job to make sure contents of Offscreen are not visible to users. `Offscreen.attach` only works if mode is `manual`. Example uses: ```jsx let offscreenRef = useRef(null); // ------ // Offscreen is attached by default. // For example user scrolls away and Offscreen subtree is not visible anymore. offscreenRef.current.detach(); // User scrolls back and Offscreen subtree is visible again. offscreenRef.current.attach(); ``` Co-authored-by: Andrew Clark --- packages/react-reconciler/src/ReactFiber.js | 9 +- .../src/ReactFiberBeginWork.js | 5 +- .../src/ReactFiberCommitWork.js | 64 +++- .../src/ReactFiberOffscreenComponent.js | 4 +- .../src/__tests__/ReactOffscreen-test.js | 333 ++++++++++++++++-- 5 files changed, 372 insertions(+), 43 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 78c0794d432c..f1e473ebf0b9 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -107,7 +107,10 @@ import { REACT_TRACING_MARKER_TYPE, } from 'shared/ReactSymbols'; import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent'; -import {detachOffscreenInstance} from './ReactFiberCommitWork'; +import { + detachOffscreenInstance, + attachOffscreenInstance, +} from './ReactFiberCommitWork'; import {getHostContext} from './ReactFiberHostContext'; export type {Fiber}; @@ -750,11 +753,13 @@ export function createFiberFromOffscreen( fiber.lanes = lanes; const primaryChildInstance: OffscreenInstance = { _visibility: OffscreenVisible, + _pendingVisibility: OffscreenVisible, _pendingMarkers: null, _retryCache: null, _transitions: null, _current: null, detach: () => detachOffscreenInstance(primaryChildInstance), + attach: () => attachOffscreenInstance(primaryChildInstance), }; fiber.stateNode = primaryChildInstance; return fiber; @@ -773,11 +778,13 @@ export function createFiberFromLegacyHidden( // the offscreen implementation, which depends on a state node const instance: OffscreenInstance = { _visibility: OffscreenVisible, + _pendingVisibility: OffscreenVisible, _pendingMarkers: null, _transitions: null, _retryCache: null, _current: null, detach: () => detachOffscreenInstance(instance), + attach: () => attachOffscreenInstance(instance), }; fiber.stateNode = instance; return fiber; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 78372d176291..7b3f525d2dcb 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -677,6 +677,8 @@ function updateOffscreenComponent( ) { const nextProps: OffscreenProps = workInProgress.pendingProps; const nextChildren = nextProps.children; + const nextIsDetached = + (workInProgress.stateNode._pendingVisibility & OffscreenDetached) !== 0; const prevState: OffscreenState | null = current !== null ? current.memoizedState : null; @@ -687,8 +689,7 @@ function updateOffscreenComponent( nextProps.mode === 'hidden' || (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding') || - // TODO: remove read from stateNode. - workInProgress.stateNode._visibility & OffscreenDetached + nextIsDetached ) { // Rendering a hidden tree. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index e394f5671a73..eec6e1eb0142 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -17,6 +17,7 @@ import type { } from './ReactFiberHostConfig'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane'; +import {NoTimestamp, SyncLane} from './ReactFiberLane'; import type {SuspenseState} from './ReactFiberSuspenseComponent'; import type {UpdateQueue} from './ReactFiberClassUpdateQueue'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks'; @@ -152,7 +153,6 @@ import { clearSingleton, acquireSingletonInstance, releaseSingletonInstance, - scheduleMicrotask, } from './ReactFiberHostConfig'; import { captureCommitPhaseError, @@ -169,7 +169,6 @@ import { setIsRunningInsertionEffect, getExecutionContext, CommitContext, - RenderContext, NoContext, } from './ReactFiberWorkLoop'; import { @@ -205,6 +204,8 @@ import { TransitionRoot, TransitionTracingMarker, } from './ReactFiberTracingMarkerComponent'; +import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates'; let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null; if (__DEV__) { @@ -2407,24 +2408,44 @@ function getRetryCache(finishedWork) { } export function detachOffscreenInstance(instance: OffscreenInstance): void { - const currentOffscreenFiber = instance._current; - if (currentOffscreenFiber === null) { + const fiber = instance._current; + if (fiber === null) { throw new Error( 'Calling Offscreen.detach before instance handle has been set.', ); } - const executionContext = getExecutionContext(); - if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { - scheduleMicrotask(() => { - instance._visibility |= OffscreenDetached; - disappearLayoutEffects(currentOffscreenFiber); - disconnectPassiveEffect(currentOffscreenFiber); - }); - } else { - instance._visibility |= OffscreenDetached; - disappearLayoutEffects(currentOffscreenFiber); - disconnectPassiveEffect(currentOffscreenFiber); + if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) { + // The instance is already detached, this is a noop. + return; + } + + // TODO: There is an opportunity to optimise this by not entering commit phase + // and unmounting effects directly. + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + instance._pendingVisibility |= OffscreenDetached; + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } +} + +export function attachOffscreenInstance(instance: OffscreenInstance): void { + const fiber = instance._current; + if (fiber === null) { + throw new Error( + 'Calling Offscreen.detach before instance handle has been set.', + ); + } + + if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) { + // The instance is already attached, this is a noop. + return; + } + + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + instance._pendingVisibility &= ~OffscreenDetached; + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); } } @@ -2857,12 +2878,19 @@ function commitMutationEffectsOnFiber( } commitReconciliationEffects(finishedWork); + + const offscreenInstance: OffscreenInstance = finishedWork.stateNode; + // TODO: Add explicit effect flag to set _current. - finishedWork.stateNode._current = finishedWork; + offscreenInstance._current = finishedWork; - if (flags & Visibility) { - const offscreenInstance: OffscreenInstance = finishedWork.stateNode; + // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is + // to support batching of `attach` and `detach` calls. + offscreenInstance._visibility &= ~OffscreenDetached; + offscreenInstance._visibility |= + offscreenInstance._pendingVisibility & OffscreenDetached; + if (flags & Visibility) { // Track the current state on the Offscreen instance so we can // read it during an event if (isHidden) { diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index e6445fd66840..d8833779195d 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -50,6 +50,7 @@ export const OffscreenDetached = /* */ 0b010; export const OffscreenPassiveEffectsConnected = /* */ 0b100; export type OffscreenInstance = { + _pendingVisibility: OffscreenVisibility, _visibility: OffscreenVisibility, _pendingMarkers: Set | null, _transitions: Set | null, @@ -59,8 +60,7 @@ export type OffscreenInstance = { // Represents the current Offscreen fiber _current: Fiber | null, detach: () => void, - - // TODO: attach + attach: () => void, }; export function isOffscreenManual(offscreenFiber: Fiber): boolean { diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index d83026561082..d723c44a5d30 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -31,7 +31,24 @@ describe('ReactOffscreen', () => { function Text(props) { Scheduler.unstable_yieldValue(props.text); - return ; + return {props.children}; + } + + function LoggedText({text, children}) { + useEffect(() => { + Scheduler.unstable_yieldValue(`mount ${text}`); + return () => { + Scheduler.unstable_yieldValue(`unmount ${text}`); + }; + }); + + useLayoutEffect(() => { + Scheduler.unstable_yieldValue(`mount layout ${text}`); + return () => { + Scheduler.unstable_yieldValue(`unmount layout ${text}`); + }; + }); + return {children}; } // @gate enableLegacyHidden @@ -1520,7 +1537,6 @@ describe('ReactOffscreen', () => { ); expect(offscreenRef.current).not.toBeNull(); - expect(offscreenRef.current.detach).not.toBeNull(); // Offscreen is attached by default. State updates from offscreen are **not defered**. await act(async () => { @@ -1538,8 +1554,9 @@ describe('ReactOffscreen', () => { ); }); - // detaching offscreen. - offscreenRef.current.detach(); + await act(async () => { + offscreenRef.current.detach(); + }); // Offscreen is detached. State updates from offscreen are **defered**. await act(async () => { @@ -1561,6 +1578,26 @@ describe('ReactOffscreen', () => { , ); + + await act(async () => { + offscreenRef.current.attach(); + }); + + // Offscreen is attached. State updates from offscreen are **not defered**. + await act(async () => { + updateChildState(3); + updateHighPriorityComponentState(3); + expect(Scheduler).toFlushUntilNextPaint([ + 'HighPriorityComponent 3', + 'Child 3', + ]); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + }); }); // @gate enableOffscreen @@ -1569,6 +1606,7 @@ describe('ReactOffscreen', () => { let updateHighPriorityComponentState; let offscreenRef; let nextRenderTriggerDetach = false; + let nextRenderTriggerAttach = false; function Child() { const [state, _stateUpdate] = useState(0); @@ -1583,11 +1621,16 @@ describe('ReactOffscreen', () => { const text = 'HighPriorityComponent ' + state; useLayoutEffect(() => { if (nextRenderTriggerDetach) { - offscreenRef.current.detach(); _stateUpdate(state + 1); updateChildState(state + 1); + offscreenRef.current.detach(); nextRenderTriggerDetach = false; } + + if (nextRenderTriggerAttach) { + offscreenRef.current.attach(); + nextRenderTriggerAttach = false; + } }); return ( <> @@ -1620,8 +1663,8 @@ describe('ReactOffscreen', () => { nextRenderTriggerDetach = true; - // Offscreen is attached. State updates from offscreen are **not defered**. - // Offscreen is detached inside useLayoutEffect; + // Offscreen is attached and gets detached inside useLayoutEffect. + // State updates from offscreen are **defered**. await act(async () => { updateChildState(1); updateHighPriorityComponentState(1); @@ -1629,36 +1672,41 @@ describe('ReactOffscreen', () => { 'HighPriorityComponent 1', 'Child 1', 'HighPriorityComponent 2', - 'Child 2', ]); expect(root).toMatchRenderedOutput( <> - + , ); }); + expect(Scheduler).toHaveYielded(['Child 2']); + expect(root).toMatchRenderedOutput( + <> + + + , + ); + + nextRenderTriggerAttach = true; + // Offscreen is detached. State updates from offscreen are **defered**. + // Offscreen is attached inside useLayoutEffect; await act(async () => { updateChildState(3); updateHighPriorityComponentState(3); - expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 3']); + expect(Scheduler).toFlushUntilNextPaint([ + 'HighPriorityComponent 3', + 'Child 3', + ]); expect(root).toMatchRenderedOutput( <> - + , ); }); - - expect(Scheduler).toHaveYielded(['Child 3']); - expect(root).toMatchRenderedOutput( - <> - - - , - ); }); }); @@ -1771,5 +1819,250 @@ describe('ReactOffscreen', () => { expect(offscreenRef.current._current === firstFiber).toBeFalsy(); }); - // TODO: When attach/detach methods are implemented. Add tests for nested Offscreen case. + // @gate enableOffscreen + it('does not mount tree until attach is called', async () => { + let offscreenRef; + let spanRef; + + function Child() { + spanRef = useRef(null); + useEffect(() => { + Scheduler.unstable_yieldValue('Mount Child'); + return () => { + Scheduler.unstable_yieldValue('Unmount Child'); + }; + }); + useLayoutEffect(() => { + Scheduler.unstable_yieldValue('Mount Layout Child'); + return () => { + Scheduler.unstable_yieldValue('Unmount Layout Child'); + }; + }); + + return Child; + } + + function App() { + return ( + (offscreenRef = el)}> + + + ); + } + + const root = ReactNoop.createRoot(); + + await act(async () => { + root.render(); + }); + + expect(offscreenRef).not.toBeNull(); + expect(spanRef.current).not.toBeNull(); + expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']); + + await act(async () => { + offscreenRef.detach(); + }); + + expect(spanRef.current).toBeNull(); + expect(Scheduler).toHaveYielded(['Unmount Layout Child', 'Unmount Child']); + + // Calling attach on already attached Offscreen. + await act(async () => { + offscreenRef.detach(); + }); + + expect(Scheduler).toHaveYielded([]); + + await act(async () => { + offscreenRef.attach(); + }); + + expect(spanRef.current).not.toBeNull(); + expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']); + + // Calling attach on already attached Offscreen + offscreenRef.attach(); + + expect(Scheduler).toHaveYielded([]); + }); + + // @gate enableOffscreen + it('handles nested manual offscreens', async () => { + let outerOffscreen; + let innerOffscreen; + + function App() { + return ( + + (outerOffscreen = el)}> + + (innerOffscreen = el)}> + + + + + + ); + } + + const root = ReactNoop.createRoot(); + + await act(async () => { + root.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'outer', + 'middle', + 'inner', + 'mount layout inner', + 'mount layout middle', + 'mount layout outer', + 'mount inner', + 'mount middle', + 'mount outer', + ]); + + expect(outerOffscreen).not.toBeNull(); + expect(innerOffscreen).not.toBeNull(); + + await act(async () => { + outerOffscreen.detach(); + }); + + expect(innerOffscreen).toBeNull(); + + expect(Scheduler).toHaveYielded([ + 'unmount layout middle', + 'unmount layout inner', + 'unmount middle', + 'unmount inner', + ]); + + await act(async () => { + outerOffscreen.attach(); + }); + + expect(Scheduler).toHaveYielded([ + 'mount layout inner', + 'mount layout middle', + 'mount inner', + 'mount middle', + ]); + + await act(async () => { + innerOffscreen.detach(); + }); + + expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']); + + // Calling detach on already detached Offscreen. + await act(async () => { + innerOffscreen.detach(); + }); + + expect(Scheduler).toHaveYielded([]); + + await act(async () => { + innerOffscreen.attach(); + }); + + expect(Scheduler).toHaveYielded(['mount layout inner', 'mount inner']); + + await act(async () => { + innerOffscreen.detach(); + outerOffscreen.attach(); + }); + + expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']); + }); + + // @gate enableOffscreen + it('batches multiple attach and detach calls scheduled from an event handler', async () => { + function Child() { + useEffect(() => { + Scheduler.unstable_yieldValue('attach child'); + return () => { + Scheduler.unstable_yieldValue('detach child'); + }; + }, []); + return 'child'; + } + + const offscreen = React.createRef(null); + function App() { + return ( + + + + ); + } + + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + + expect(Scheduler).toHaveYielded(['attach child']); + + await act(async () => { + const instance = offscreen.current; + // Detach then immediately attach the instance. + instance.detach(); + instance.attach(); + }); + + expect(Scheduler).toHaveYielded([]); + + await act(async () => { + const instance = offscreen.current; + instance.detach(); + }); + + expect(Scheduler).toHaveYielded(['detach child']); + + await act(async () => { + const instance = offscreen.current; + // Attach then immediately detach. + instance.attach(); + instance.detach(); + }); + + expect(Scheduler).toHaveYielded([]); + }); + + // @gate enableOffscreen + it('batches multiple attach and detach calls scheduled from an effect', async () => { + function Child() { + useEffect(() => { + Scheduler.unstable_yieldValue('attach child'); + return () => { + Scheduler.unstable_yieldValue('detach child'); + }; + }, []); + return 'child'; + } + + function App() { + const offscreen = useRef(null); + useLayoutEffect(() => { + const instance = offscreen.current; + // Detach then immediately attach the instance. + instance.detach(); + instance.attach(); + }, []); + return ( + + + + ); + } + + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + expect(Scheduler).toHaveYielded(['attach child']); + }); }); From 9c09c1cd62ef862df91bcb4df4271917629d6963 Mon Sep 17 00:00:00 2001 From: lauren Date: Mon, 12 Dec 2022 09:39:57 -0800 Subject: [PATCH 004/269] Revert "Fork ReactDOMSharedInternals for www (#25791)" (#25864) We did some cleanup internally of our ReactDOM module, so this fork should be safe to remove now. Will land this only after our internal diff lands. --- .../shared/forks/ReactDOMSharedInternals.www.js | 16 ---------------- scripts/rollup/forks.js | 8 -------- 2 files changed, 24 deletions(-) delete mode 100644 packages/shared/forks/ReactDOMSharedInternals.www.js diff --git a/packages/shared/forks/ReactDOMSharedInternals.www.js b/packages/shared/forks/ReactDOMSharedInternals.www.js deleted file mode 100644 index 41f52fca4c6c..000000000000 --- a/packages/shared/forks/ReactDOMSharedInternals.www.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 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 - */ - -// $FlowIgnore[cannot-resolve-module] provided by www -const ReactDOM = require('ReactDOMComet'); - -const ReactDOMSharedInternals = - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - -export default ReactDOMSharedInternals; diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index db046be29ac2..93833be1467b 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -70,14 +70,6 @@ const forks = Object.freeze({ if (entry === 'react-dom' || entry === 'react-dom/server-rendering-stub') { return './packages/react-dom/src/ReactDOMSharedInternals.js'; } - switch (bundleType) { - case FB_WWW_DEV: - case FB_WWW_PROD: - case FB_WWW_PROFILING: - return './packages/shared/forks/ReactDOMSharedInternals.www.js'; - default: - break; - } if ( !entry.startsWith('react-dom/') && dependencies.indexOf('react-dom') === -1 From 4dda96a4071d2cc4bbcc444438cfacee991f07e2 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Tue, 13 Dec 2022 10:44:48 -0500 Subject: [PATCH 005/269] [react-www] remove forked bundle (#25866) *NOTE:* re-apply of 645ae2686b157c9f80193e1ada75b7e00ef49acf now that www is updated. The `enableNewReconciler` was gone with 420f0b7fa1fcc609fc7b438c4599d0f76fab4bc0, this removes the bundle config. --- dangerfile.js | 1 - scripts/rollup/bundles.js | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/dangerfile.js b/dangerfile.js index b0c95d27b0f3..2322e8565447 100644 --- a/dangerfile.js +++ b/dangerfile.js @@ -46,7 +46,6 @@ const CRITICAL_ARTIFACT_PATHS = new Set([ 'oss-experimental/react-dom/cjs/react-dom.production.min.js', 'facebook-www/ReactDOM-prod.classic.js', 'facebook-www/ReactDOM-prod.modern.js', - 'facebook-www/ReactDOMForked-prod.classic.js', ]); const kilobyteFormatter = new Intl.NumberFormat('en', { diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 271e5770b87e..08160a0c86ac 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -163,17 +163,6 @@ const bundles = [ externals: ['react'], }, - /******* React DOM - www - Uses forked reconciler *******/ - { - moduleType: RENDERER, - bundleTypes: [FB_WWW_DEV, FB_WWW_PROD, FB_WWW_PROFILING], - entry: 'react-dom', - global: 'ReactDOMForked', - minifyWithProdErrorCodes: true, - wrapWithModuleBoundaries: true, - externals: ['react'], - }, - /******* Test Utils *******/ { moduleType: RENDERER_UTILS, From 84a0a171ea0ecd25e287bd3d3dd30e932beb4677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 14 Dec 2022 15:08:29 -0500 Subject: [PATCH 006/269] Rename experimental useEvent to useEffectEvent (#25881) We originally had grand plans for using this Event concept for more but now it's only meant to be used in combination with effects. It's an Event in the FRP terms, that is triggered from an Effect. Technically it can also be from another function that itself is triggered from an existing side-effect but that's kind of an advanced case. The canonical case is an effect that triggers an event: ```js const onHappened = useEffectEvent(() => ...); useEffect(() => { onHappened(); }, []); ``` --- .../ESLintRuleExhaustiveDeps-test.js | 8 +-- .../__tests__/ESLintRulesOfHooks-test.js | 56 +++++++-------- .../src/ExhaustiveDeps.js | 21 +++--- .../src/RulesOfHooks.js | 36 +++++----- .../src/__tests__/ReactDOMFizzServer-test.js | 24 +++---- .../src/ReactFiberCommitWork.js | 8 +-- .../react-reconciler/src/ReactFiberHooks.js | 68 +++++++++--------- .../src/ReactInternalTypes.js | 6 +- ...seEvent-test.js => useEffectEvent-test.js} | 70 +++++++++---------- packages/react-server/src/ReactFizzHooks.js | 14 ++-- packages/react/index.classic.fb.js | 2 +- packages/react/index.experimental.js | 2 +- packages/react/index.js | 2 +- packages/react/index.modern.fb.js | 2 +- packages/react/src/React.js | 4 +- packages/react/src/ReactHooks.js | 4 +- packages/shared/ReactFeatureFlags.js | 2 +- .../forks/ReactFeatureFlags.native-fb.js | 2 +- .../forks/ReactFeatureFlags.native-oss.js | 2 +- .../forks/ReactFeatureFlags.test-renderer.js | 2 +- .../ReactFeatureFlags.test-renderer.native.js | 2 +- .../ReactFeatureFlags.test-renderer.www.js | 2 +- .../shared/forks/ReactFeatureFlags.testing.js | 2 +- .../forks/ReactFeatureFlags.testing.www.js | 2 +- .../shared/forks/ReactFeatureFlags.www.js | 2 +- scripts/error-codes/codes.json | 2 +- 26 files changed, 176 insertions(+), 171 deletions(-) rename packages/react-reconciler/src/__tests__/{useEvent-test.js => useEffectEvent-test.js} (92%) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 2de2daa85d65..914c2d526107 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -7636,7 +7636,7 @@ if (__EXPERIMENTAL__) { { code: normalizeIndent` function MyComponent({ theme }) { - const onStuff = useEvent(() => { + const onStuff = useEffectEvent(() => { showNotification(theme); }); useEffect(() => { @@ -7652,7 +7652,7 @@ if (__EXPERIMENTAL__) { { code: normalizeIndent` function MyComponent({ theme }) { - const onStuff = useEvent(() => { + const onStuff = useEffectEvent(() => { showNotification(theme); }); useEffect(() => { @@ -7663,14 +7663,14 @@ if (__EXPERIMENTAL__) { errors: [ { message: - 'Functions returned from `useEvent` must not be included in the dependency array. ' + + 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + 'Remove `onStuff` from the list.', suggestions: [ { desc: 'Remove the dependency `onStuff`', output: normalizeIndent` function MyComponent({ theme }) { - const onStuff = useEvent(() => { + const onStuff = useEffectEvent(() => { showNotification(theme); }); useEffect(() => { diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 3148ee877ee4..aa2bcf9846d3 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -1050,9 +1050,9 @@ if (__EXPERIMENTAL__) { ...tests.valid, { code: normalizeIndent` - // Valid because functions created with useEvent can be called in a useEffect. + // Valid because functions created with useEffectEvent can be called in a useEffect. function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); useEffect(() => { @@ -1063,9 +1063,9 @@ if (__EXPERIMENTAL__) { }, { code: normalizeIndent` - // Valid because functions created with useEvent can be called in closures. + // Valid because functions created with useEffectEvent can be called in closures. function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); return onClick()}>; @@ -1074,9 +1074,9 @@ if (__EXPERIMENTAL__) { }, { code: normalizeIndent` - // Valid because functions created with useEvent can be called in closures. + // Valid because functions created with useEffectEvent can be called in closures. function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); const onClick2 = () => { onClick() }; @@ -1090,13 +1090,13 @@ if (__EXPERIMENTAL__) { }, { code: normalizeIndent` - // Valid because functions created with useEvent can be passed by reference in useEffect - // and useEvent. + // Valid because functions created with useEffectEvent can be passed by reference in useEffect + // and useEffectEvent. function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); - const onClick2 = useEvent(() => { + const onClick2 = useEffectEvent(() => { debounce(onClick); }); useEffect(() => { @@ -1110,7 +1110,7 @@ if (__EXPERIMENTAL__) { { code: normalizeIndent` const MyComponent = ({theme}) => { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); return onClick()}>; @@ -1121,10 +1121,10 @@ if (__EXPERIMENTAL__) { code: normalizeIndent` function MyComponent({ theme }) { const notificationService = useNotifications(); - const showNotification = useEvent((text) => { + const showNotification = useEffectEvent((text) => { notificationService.notify(theme, text); }); - const onClick = useEvent((text) => { + const onClick = useEffectEvent((text) => { showNotification(text); }); return onClick(text)} /> @@ -1137,7 +1137,7 @@ if (__EXPERIMENTAL__) { useEffect(() => { onClick(); }); - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); } @@ -1188,64 +1188,64 @@ if (__EXPERIMENTAL__) { { code: normalizeIndent` function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); return ; } `, - errors: [useEventError('onClick')], + errors: [useEffectEventError('onClick')], }, { code: normalizeIndent` // This should error even though it shares an identifier name with the below function MyComponent({theme}) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme) }); return } - // The useEvent function shares an identifier name with the above + // The useEffectEvent function shares an identifier name with the above function MyOtherComponent({theme}) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme) }); return onClick()} /> } `, - errors: [{...useEventError('onClick'), line: 7}], + errors: [{...useEffectEventError('onClick'), line: 7}], }, { code: normalizeIndent` const MyComponent = ({ theme }) => { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); return ; } `, - errors: [useEventError('onClick')], + errors: [useEffectEventError('onClick')], }, { code: normalizeIndent` // Invalid because onClick is being aliased to foo but not invoked function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(theme); }); let foo = onClick; return } `, - errors: [{...useEventError('onClick'), line: 7}], + errors: [{...useEffectEventError('onClick'), line: 7}], }, { code: normalizeIndent` // Should error because it's being passed down to JSX, although it's been referenced once // in an effect function MyComponent({ theme }) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { showNotification(them); }); useEffect(() => { @@ -1254,7 +1254,7 @@ if (__EXPERIMENTAL__) { return } `, - errors: [useEventError('onClick')], + errors: [useEffectEventError('onClick')], }, { code: normalizeIndent` @@ -1360,10 +1360,10 @@ function classError(hook) { }; } -function useEventError(fn) { +function useEffectEventError(fn) { return { message: - `\`${fn}\` is a function created with React Hook "useEvent", and can only be called from ` + + `\`${fn}\` is a function created with React Hook "useEffectEvent", and can only be called from ` + 'the same component. They cannot be assigned to variables or passed down.', }; } diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js index 0885d268367f..fd5bcd1c95ab 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js @@ -74,7 +74,7 @@ export default { const stateVariables = new WeakSet(); const stableKnownValueCache = new WeakMap(); const functionWithoutCapturedValueCache = new WeakMap(); - const useEventVariables = new WeakSet(); + const useEffectEventVariables = new WeakSet(); function memoizeWithWeakMap(fn, map) { return function(arg) { if (map.has(arg)) { @@ -158,7 +158,7 @@ export default { // ^^^ true for this reference // const ref = useRef() // ^^^ true for this reference - // const onStuff = useEvent(() => {}) + // const onStuff = useEffectEvent(() => {}) // ^^^ true for this reference // False for everything else. function isStableKnownHookValue(resolved) { @@ -226,13 +226,16 @@ export default { if (name === 'useRef' && id.type === 'Identifier') { // useRef() return value is stable. return true; - } else if (isUseEventIdentifier(callee) && id.type === 'Identifier') { + } else if ( + isUseEffectEventIdentifier(callee) && + id.type === 'Identifier' + ) { for (const ref of resolved.references) { if (ref !== id) { - useEventVariables.add(ref.identifier); + useEffectEventVariables.add(ref.identifier); } } - // useEvent() return value is always unstable. + // useEffectEvent() return value is always unstable. return true; } else if (name === 'useState' || name === 'useReducer') { // Only consider second value in initializing tuple stable. @@ -645,11 +648,11 @@ export default { }); return; } - if (useEventVariables.has(declaredDependencyNode)) { + if (useEffectEventVariables.has(declaredDependencyNode)) { reportProblem({ node: declaredDependencyNode, message: - 'Functions returned from `useEvent` must not be included in the dependency array. ' + + 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + `Remove \`${context.getSource( declaredDependencyNode, )}\` from the list.`, @@ -1851,9 +1854,9 @@ function isAncestorNodeOf(a, b) { return a.range[0] <= b.range[0] && a.range[1] >= b.range[1]; } -function isUseEventIdentifier(node) { +function isUseEffectEventIdentifier(node) { if (__EXPERIMENTAL__) { - return node.type === 'Identifier' && node.name === 'useEvent'; + return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 9c915b775a41..ded525e08545 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -103,9 +103,9 @@ function isInsideComponentOrHook(node) { return false; } -function isUseEventIdentifier(node) { +function isUseEffectEventIdentifier(node) { if (__EXPERIMENTAL__) { - return node.type === 'Identifier' && node.name === 'useEvent'; + return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } @@ -130,12 +130,12 @@ export default { let lastEffect = null; const codePathReactHooksMapStack = []; const codePathSegmentStack = []; - const useEventFunctions = new WeakSet(); + const useEffectEventFunctions = new WeakSet(); - // For a given scope, iterate through the references and add all useEvent definitions. We can - // do this in non-Program nodes because we can rely on the assumption that useEvent functions + // For a given scope, iterate through the references and add all useEffectEvent definitions. We can + // do this in non-Program nodes because we can rely on the assumption that useEffectEvent functions // can only be declared within a component or hook at its top level. - function recordAllUseEventFunctions(scope) { + function recordAllUseEffectEventFunctions(scope) { for (const reference of scope.references) { const parent = reference.identifier.parent; if ( @@ -143,11 +143,11 @@ export default { parent.init && parent.init.type === 'CallExpression' && parent.init.callee && - isUseEventIdentifier(parent.init.callee) + isUseEffectEventIdentifier(parent.init.callee) ) { for (const ref of reference.resolved.references) { if (ref !== reference) { - useEventFunctions.add(ref.identifier); + useEffectEventFunctions.add(ref.identifier); } } } @@ -571,12 +571,12 @@ export default { reactHooks.push(node.callee); } - // useEvent: useEvent functions can be passed by reference within useEffect as well as in - // another useEvent + // useEffectEvent: useEffectEvent functions can be passed by reference within useEffect as well as in + // another useEffectEvent if ( node.callee.type === 'Identifier' && (node.callee.name === 'useEffect' || - isUseEventIdentifier(node.callee)) && + isUseEffectEventIdentifier(node.callee)) && node.arguments.length > 0 ) { // Denote that we have traversed into a useEffect call, and stash the CallExpr for @@ -586,11 +586,11 @@ export default { }, Identifier(node) { - // This identifier resolves to a useEvent function, but isn't being referenced in an + // This identifier resolves to a useEffectEvent function, but isn't being referenced in an // effect or another event function. It isn't being called either. if ( lastEffect == null && - useEventFunctions.has(node) && + useEffectEventFunctions.has(node) && node.parent.type !== 'CallExpression' ) { context.report({ @@ -598,7 +598,7 @@ export default { message: `\`${context.getSource( node, - )}\` is a function created with React Hook "useEvent", and can only be called from ` + + )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` + 'the same component. They cannot be assigned to variables or passed down.', }); } @@ -611,16 +611,16 @@ export default { }, FunctionDeclaration(node) { - // function MyComponent() { const onClick = useEvent(...) } + // function MyComponent() { const onClick = useEffectEvent(...) } if (isInsideComponentOrHook(node)) { - recordAllUseEventFunctions(context.getScope()); + recordAllUseEffectEventFunctions(context.getScope()); } }, ArrowFunctionExpression(node) { - // const MyComponent = () => { const onClick = useEvent(...) } + // const MyComponent = () => { const onClick = useEffectEvent(...) } if (isInsideComponentOrHook(node)) { - recordAllUseEventFunctions(context.getScope()); + recordAllUseEffectEventFunctions(context.getScope()); } }, }; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 79455a7225df..e314cbae13d1 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -5459,13 +5459,13 @@ describe('ReactDOMFizzServer', () => { }); }); - describe('useEvent', () => { - // @gate enableUseEventHook - it('can server render a component with useEvent', async () => { + describe('useEffectEvent', () => { + // @gate enableUseEffectEventHook + it('can server render a component with useEffectEvent', async () => { const ref = React.createRef(); function App() { const [count, setCount] = React.useState(0); - const onClick = React.experimental_useEvent(() => { + const onClick = React.experimental_useEffectEvent(() => { setCount(c => c + 1); }); return ( @@ -5491,11 +5491,11 @@ describe('ReactDOMFizzServer', () => { expect(getVisibleChildren(container)).toEqual(); }); - // @gate enableUseEventHook - it('throws if useEvent is called during a server render', async () => { + // @gate enableUseEffectEventHook + it('throws if useEffectEvent is called during a server render', async () => { const logs = []; function App() { - const onRender = React.experimental_useEvent(() => { + const onRender = React.experimental_useEffectEvent(() => { logs.push('rendered'); }); onRender(); @@ -5518,16 +5518,16 @@ describe('ReactDOMFizzServer', () => { } expect(logs).toEqual([]); expect(caughtError.message).toContain( - "A function wrapped in useEvent can't be called during rendering.", + "A function wrapped in useEffectEvent can't be called during rendering.", ); expect(reportedServerErrors).toEqual([caughtError]); }); - // @gate enableUseEventHook - it('does not guarantee useEvent return values during server rendering are distinct', async () => { + // @gate enableUseEffectEventHook + it('does not guarantee useEffectEvent return values during server rendering are distinct', async () => { function App() { - const onClick1 = React.experimental_useEvent(() => {}); - const onClick2 = React.experimental_useEvent(() => {}); + const onClick1 = React.experimental_useEffectEvent(() => {}); + const onClick2 = React.experimental_useEffectEvent(() => {}); if (onClick1 === onClick2) { return
; } else { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index eec6e1eb0142..363e6408711c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -50,7 +50,7 @@ import { enableUpdaterTracking, enableCache, enableTransitionTracing, - enableUseEventHook, + enableUseEffectEventHook, enableFloat, enableLegacyHidden, enableHostSingletons, @@ -454,9 +454,9 @@ function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) { switch (finishedWork.tag) { case FunctionComponent: { - if (enableUseEventHook) { + if (enableUseEffectEventHook) { if ((flags & Update) !== NoFlags) { - commitUseEventMount(finishedWork); + commitUseEffectEventMount(finishedWork); } } break; @@ -706,7 +706,7 @@ function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) { } } -function commitUseEventMount(finishedWork: Fiber) { +function commitUseEffectEventMount(finishedWork: Fiber) { const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); const eventPayloads = updateQueue !== null ? updateQueue.events : null; if (eventPayloads !== null) { diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index cf3d34f4c403..eebd50915f31 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -38,7 +38,7 @@ import { enableTransitionTracing, enableUseHook, enableUseMemoCacheHook, - enableUseEventHook, + enableUseEffectEventHook, enableLegacyCache, debugRenderPhaseSideEffectsForStrictMode, } from 'shared/ReactFeatureFlags'; @@ -2051,7 +2051,7 @@ function updateEffect( updateEffectImpl(PassiveEffect, HookPassive, create, deps); } -function useEventImpl) => Return>( +function useEffectEventImpl) => Return>( payload: EventFunctionPayload, ) { currentlyRenderingFiber.flags |= UpdateEffect; @@ -2080,7 +2080,7 @@ function mountEvent) => Return>( return function eventFn() { if (isInvalidExecutionContextForEventFunction()) { throw new Error( - "A function wrapped in useEvent can't be called during rendering.", + "A function wrapped in useEffectEvent can't be called during rendering.", ); } return ref.impl.apply(undefined, arguments); @@ -2092,12 +2092,12 @@ function updateEvent) => Return>( ): F { const hook = updateWorkInProgressHook(); const ref = hook.memoizedState; - useEventImpl({ref, nextImpl: callback}); + useEffectEventImpl({ref, nextImpl: callback}); // $FlowIgnore[incompatible-return] return function eventFn() { if (isInvalidExecutionContextForEventFunction()) { throw new Error( - "A function wrapped in useEvent can't be called during rendering.", + "A function wrapped in useEffectEvent can't be called during rendering.", ); } return ref.impl.apply(undefined, arguments); @@ -2780,8 +2780,8 @@ if (enableUseHook) { if (enableUseMemoCacheHook) { (ContextOnlyDispatcher: Dispatcher).useMemoCache = throwInvalidHookError; } -if (enableUseEventHook) { - (ContextOnlyDispatcher: Dispatcher).useEvent = throwInvalidHookError; +if (enableUseEffectEventHook) { + (ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError; } const HooksDispatcherOnMount: Dispatcher = { @@ -2814,8 +2814,8 @@ if (enableUseHook) { if (enableUseMemoCacheHook) { (HooksDispatcherOnMount: Dispatcher).useMemoCache = useMemoCache; } -if (enableUseEventHook) { - (HooksDispatcherOnMount: Dispatcher).useEvent = mountEvent; +if (enableUseEffectEventHook) { + (HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent; } const HooksDispatcherOnUpdate: Dispatcher = { readContext, @@ -2846,8 +2846,8 @@ if (enableUseMemoCacheHook) { if (enableUseHook) { (HooksDispatcherOnUpdate: Dispatcher).use = use; } -if (enableUseEventHook) { - (HooksDispatcherOnUpdate: Dispatcher).useEvent = updateEvent; +if (enableUseEffectEventHook) { + (HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent; } const HooksDispatcherOnRerender: Dispatcher = { @@ -2879,8 +2879,8 @@ if (enableUseHook) { if (enableUseMemoCacheHook) { (HooksDispatcherOnRerender: Dispatcher).useMemoCache = useMemoCache; } -if (enableUseEventHook) { - (HooksDispatcherOnRerender: Dispatcher).useEvent = updateEvent; +if (enableUseEffectEventHook) { + (HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent; } let HooksDispatcherOnMountInDEV: Dispatcher | null = null; @@ -3059,13 +3059,13 @@ if (__DEV__) { if (enableUseMemoCacheHook) { (HooksDispatcherOnMountInDEV: Dispatcher).useMemoCache = useMemoCache; } - if (enableUseEventHook) { - (HooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (HooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; mountHookTypesDev(); return mountEvent(callback); }; @@ -3214,13 +3214,13 @@ if (__DEV__) { if (enableUseMemoCacheHook) { (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useMemoCache = useMemoCache; } - if (enableUseEventHook) { - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; updateHookTypesDev(); return mountEvent(callback); }; @@ -3369,13 +3369,13 @@ if (__DEV__) { if (enableUseMemoCacheHook) { (HooksDispatcherOnUpdateInDEV: Dispatcher).useMemoCache = useMemoCache; } - if (enableUseEventHook) { - (HooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (HooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; updateHookTypesDev(); return updateEvent(callback); }; @@ -3525,13 +3525,13 @@ if (__DEV__) { if (enableUseMemoCacheHook) { (HooksDispatcherOnRerenderInDEV: Dispatcher).useMemoCache = useMemoCache; } - if (enableUseEventHook) { - (HooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (HooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; updateHookTypesDev(); return updateEvent(callback); }; @@ -3707,13 +3707,13 @@ if (__DEV__) { return useMemoCache(size); }; } - if (enableUseEventHook) { - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; warnInvalidHookAccess(); mountHookTypesDev(); return mountEvent(callback); @@ -3890,13 +3890,13 @@ if (__DEV__) { return useMemoCache(size); }; } - if (enableUseEventHook) { - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; warnInvalidHookAccess(); updateHookTypesDev(); return updateEvent(callback); @@ -4074,13 +4074,13 @@ if (__DEV__) { return useMemoCache(size); }; } - if (enableUseEventHook) { - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent< + if (enableUseEffectEventHook) { + (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent = function useEffectEvent< Args, Return, F: (...Array) => Return, >(callback: F): F { - currentHookNameInDev = 'useEvent'; + currentHookNameInDev = 'useEffectEvent'; warnInvalidHookAccess(); updateHookTypesDev(); return updateEvent(callback); diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 4974f64d9a5c..f2a1eed3780b 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -44,7 +44,7 @@ export type HookType = | 'useContext' | 'useRef' | 'useEffect' - | 'useEvent' + | 'useEffectEvent' | 'useInsertionEffect' | 'useLayoutEffect' | 'useCallback' @@ -379,7 +379,9 @@ export type Dispatcher = { create: () => (() => void) | void, deps: Array | void | null, ): void, - useEvent?: ) => Return>(callback: F) => F, + useEffectEvent?: ) => Return>( + callback: F, + ) => F, useInsertionEffect( create: () => (() => void) | void, deps: Array | void | null, diff --git a/packages/react-reconciler/src/__tests__/useEvent-test.js b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js similarity index 92% rename from packages/react-reconciler/src/__tests__/useEvent-test.js rename to packages/react-reconciler/src/__tests__/useEffectEvent-test.js index fecf99679d26..5ebfcf562684 100644 --- a/packages/react-reconciler/src/__tests__/useEvent-test.js +++ b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js @@ -14,7 +14,7 @@ import {useInsertionEffect} from 'react'; -describe('useEvent', () => { +describe('useEffectEvent', () => { let React; let ReactNoop; let Scheduler; @@ -22,7 +22,7 @@ describe('useEvent', () => { let createContext; let useContext; let useState; - let useEvent; + let useEffectEvent; let useEffect; let useLayoutEffect; let useMemo; @@ -36,7 +36,7 @@ describe('useEvent', () => { createContext = React.createContext; useContext = React.useContext; useState = React.useState; - useEvent = React.experimental_useEvent; + useEffectEvent = React.experimental_useEffectEvent; useEffect = React.useEffect; useLayoutEffect = React.useLayoutEffect; useMemo = React.useMemo; @@ -51,7 +51,7 @@ describe('useEvent', () => { return ; } - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('memoizes basic case correctly', () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -64,7 +64,7 @@ describe('useEvent', () => { function Counter({incrementBy}) { const [count, updateCount] = useState(0); - const onClick = useEvent(() => updateCount(c => c + incrementBy)); + const onClick = useEffectEvent(() => updateCount(c => c + incrementBy)); return ( <> @@ -117,7 +117,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('can be defined more than once', () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -133,8 +133,8 @@ describe('useEvent', () => { function Counter({incrementBy}) { const [count, updateCount] = useState(0); - const onClick = useEvent(() => updateCount(c => c + incrementBy)); - const onMouseEnter = useEvent(() => { + const onClick = useEffectEvent(() => updateCount(c => c + incrementBy)); + const onMouseEnter = useEffectEvent(() => { updateCount(c => c * incrementBy); }); @@ -173,7 +173,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('does not preserve `this` in event functions', () => { class GreetButton extends React.PureComponent { greet = () => { @@ -193,7 +193,7 @@ describe('useEvent', () => { }, }; const [greeting, updateGreeting] = useState('Seb says ' + hello); - const onClick = useEvent(person.greet); + const onClick = useEffectEvent(person.greet); return ( <> @@ -222,7 +222,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('throws when called in render', () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -239,7 +239,7 @@ describe('useEvent', () => { function Counter({incrementBy}) { const [count, updateCount] = useState(0); - const onClick = useEvent(() => updateCount(c => c + incrementBy)); + const onClick = useEffectEvent(() => updateCount(c => c + incrementBy)); return ( <> @@ -251,7 +251,7 @@ describe('useEvent', () => { ReactNoop.render(); expect(Scheduler).toFlushAndThrow( - "A function wrapped in useEvent can't be called during rendering.", + "A function wrapped in useEffectEvent can't be called during rendering.", ); // If something throws, we try one more time synchronously in case the error was @@ -259,7 +259,7 @@ describe('useEvent', () => { expect(Scheduler).toHaveYielded(['Count: 0', 'Count: 0']); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it("useLayoutEffect shouldn't re-fire when event handlers change", () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -272,7 +272,7 @@ describe('useEvent', () => { function Counter({incrementBy}) { const [count, updateCount] = useState(0); - const increment = useEvent(amount => + const increment = useEffectEvent(amount => updateCount(c => c + (amount || incrementBy)), ); @@ -349,7 +349,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it("useEffect shouldn't re-fire when event handlers change", () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -362,7 +362,7 @@ describe('useEvent', () => { function Counter({incrementBy}) { const [count, updateCount] = useState(0); - const increment = useEvent(amount => + const increment = useEffectEvent(amount => updateCount(c => c + (amount || incrementBy)), ); @@ -438,7 +438,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('is stable in a custom hook', () => { class IncrementButton extends React.PureComponent { increment = () => { @@ -451,7 +451,7 @@ describe('useEvent', () => { function useCount(incrementBy) { const [count, updateCount] = useState(0); - const increment = useEvent(amount => + const increment = useEffectEvent(amount => updateCount(c => c + (amount || incrementBy)), ); @@ -533,7 +533,7 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('is mutated before all other effects', () => { function Counter({value}) { useInsertionEffect(() => { @@ -543,7 +543,7 @@ describe('useEvent', () => { // This is defined after the insertion effect, but it should // update the event fn _before_ the insertion effect fires. - const increment = useEvent(() => { + const increment = useEffectEvent(() => { Scheduler.unstable_yieldValue('Event value: ' + value); }); @@ -557,17 +557,17 @@ describe('useEvent', () => { expect(Scheduler).toHaveYielded(['Effect value: 2', 'Event value: 2']); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it("doesn't provide a stable identity", () => { function Counter({shouldRender, value}) { - const onClick = useEvent(() => { + const onClick = useEffectEvent(() => { Scheduler.unstable_yieldValue( 'onClick, shouldRender=' + shouldRender + ', value=' + value, ); }); // onClick doesn't have a stable function identity so this effect will fire on every render. - // In a real app useEvent functions should *not* be passed as a dependency, this is for + // In a real app useEffectEvent functions should *not* be passed as a dependency, this is for // testing purposes only. useEffect(() => { onClick(); @@ -596,13 +596,13 @@ describe('useEvent', () => { ]); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('event handlers always see the latest committed value', async () => { let committedEventHandler = null; function App({value}) { - const event = useEvent(() => { - return 'Value seen by useEvent: ' + value; + const event = useEffectEvent(() => { + return 'Value seen by useEffectEvent: ' + value; }); // Set up an effect that registers the event handler with an external @@ -619,7 +619,7 @@ describe('useEvent', () => { }, // Note that we've intentionally omitted the event from the dependency // array. But it will still be able to see the latest `value`. This is the - // key feature of useEvent that makes it different from a regular closure. + // key feature of useEffectEvent that makes it different from a regular closure. [], ); return 'Latest rendered value ' + value; @@ -632,7 +632,7 @@ describe('useEvent', () => { }); expect(Scheduler).toHaveYielded(['Commit new event handler']); expect(root).toMatchRenderedOutput('Latest rendered value 1'); - expect(committedEventHandler()).toBe('Value seen by useEvent: 1'); + expect(committedEventHandler()).toBe('Value seen by useEffectEvent: 1'); // Update await act(async () => { @@ -643,10 +643,10 @@ describe('useEvent', () => { expect(Scheduler).toHaveYielded([]); // But the event handler should still be able to see the latest value. expect(root).toMatchRenderedOutput('Latest rendered value 2'); - expect(committedEventHandler()).toBe('Value seen by useEvent: 2'); + expect(committedEventHandler()).toBe('Value seen by useEffectEvent: 2'); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('integration: implements docs chat room example', () => { function createConnection() { let connectedCallback; @@ -675,7 +675,7 @@ describe('useEvent', () => { } function ChatRoom({roomId, theme}) { - const onConnected = useEvent(() => { + const onConnected = useEffectEvent(() => { Scheduler.unstable_yieldValue('Connected! theme: ' + theme); }); @@ -735,7 +735,7 @@ describe('useEvent', () => { expect(Scheduler).toHaveYielded(['Connected! theme: dark']); }); - // @gate enableUseEventHook + // @gate enableUseEffectEventHook it('integration: implements the docs logVisit example', () => { class AddToCartButton extends React.PureComponent { addToCart = () => { @@ -760,10 +760,10 @@ describe('useEvent', () => { function Page({url}) { const {items, updateItems} = useContext(ShoppingCartContext); - const onClick = useEvent(() => updateItems([...items, 1])); + const onClick = useEffectEvent(() => updateItems([...items, 1])); const numberOfItems = items.length; - const onVisit = useEvent(visitedUrl => { + const onVisit = useEffectEvent(visitedUrl => { Scheduler.unstable_yieldValue( 'url: ' + visitedUrl + ', numberOfItems: ' + numberOfItems, ); diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index b8c1e4c92773..5a2305eb6289 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -32,7 +32,7 @@ import {makeId} from './ReactServerFormatConfig'; import { enableCache, enableUseHook, - enableUseEventHook, + enableUseEffectEventHook, enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; @@ -507,17 +507,17 @@ export function useCallback( return useMemo(() => callback, deps); } -function throwOnUseEventCall() { +function throwOnUseEffectEventCall() { throw new Error( - "A function wrapped in useEvent can't be called during rendering.", + "A function wrapped in useEffectEvent can't be called during rendering.", ); } -export function useEvent) => Return>( +export function useEffectEvent) => Return>( callback: F, ): F { // $FlowIgnore[incompatible-return] - return throwOnUseEventCall; + return throwOnUseEffectEventCall; } // TODO Decide on how to implement this hook for server rendering. @@ -651,8 +651,8 @@ export const HooksDispatcher: Dispatcher = { if (enableCache) { HooksDispatcher.useCacheRefresh = useCacheRefresh; } -if (enableUseEventHook) { - HooksDispatcher.useEvent = useEvent; +if (enableUseEffectEventHook) { + HooksDispatcher.useEffectEvent = useEffectEvent; } if (enableUseMemoCacheHook) { HooksDispatcher.useMemoCache = useMemoCache; diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js index 04b905efa759..48caeb5e1389 100644 --- a/packages/react/index.classic.fb.js +++ b/packages/react/index.classic.fb.js @@ -52,7 +52,7 @@ export { useDeferredValue, useDeferredValue as unstable_useDeferredValue, // TODO: Remove once call sights updated to useDeferredValue useEffect, - experimental_useEvent, + experimental_useEffectEvent, useImperativeHandle, useLayoutEffect, useInsertionEffect, diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index a5b16fcadfc7..876aa3a76175 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -44,7 +44,7 @@ export { useDebugValue, useDeferredValue, useEffect, - experimental_useEvent, + experimental_useEffectEvent, useImperativeHandle, useInsertionEffect, useLayoutEffect, diff --git a/packages/react/index.js b/packages/react/index.js index bee9b71e48fe..548c1fe1711a 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -72,7 +72,7 @@ export { useDebugValue, useDeferredValue, useEffect, - experimental_useEvent, + experimental_useEffectEvent, useImperativeHandle, useInsertionEffect, useLayoutEffect, diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js index 2c446d7bd7f9..68adc9a22464 100644 --- a/packages/react/index.modern.fb.js +++ b/packages/react/index.modern.fb.js @@ -50,7 +50,7 @@ export { useDeferredValue, useDeferredValue as unstable_useDeferredValue, // TODO: Remove once call sights updated to useDeferredValue useEffect, - experimental_useEvent, + experimental_useEffectEvent, useImperativeHandle, useInsertionEffect, useLayoutEffect, diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 399c62f0dc58..38d40ece0503 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -42,7 +42,7 @@ import { useCallback, useContext, useEffect, - useEvent, + useEffectEvent, useImperativeHandle, useDebugValue, useInsertionEffect, @@ -105,7 +105,7 @@ export { useCallback, useContext, useEffect, - useEvent as experimental_useEvent, + useEffectEvent as experimental_useEffectEvent, useImperativeHandle, useDebugValue, useInsertionEffect, diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index a98fdb9c26cb..716e40b4f7ad 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -235,8 +235,8 @@ export function useMemoCache(size: number): Array { return dispatcher.useMemoCache(size); } -export function useEvent(callback: T): void { +export function useEffectEvent(callback: T): void { const dispatcher = resolveDispatcher(); // $FlowFixMe This is unstable, thus optional - return dispatcher.useEvent(callback); + return dispatcher.useEffectEvent(callback); } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index d3c128ae77bb..8fcd13894c33 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -121,7 +121,7 @@ export const enableUseHook = true; // auto-memoization. export const enableUseMemoCacheHook = __EXPERIMENTAL__; -export const enableUseEventHook = __EXPERIMENTAL__; +export const enableUseEffectEventHook = __EXPERIMENTAL__; // Test in www before enabling in open source. // Enables DOM-server to stream its instruction set as data-attributes diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 8a6f0dc811f9..6d84e7661242 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -54,7 +54,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = true; export const enableUseHook = true; export const enableUseMemoCacheHook = true; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index d2e8863e9128..9b5c301b7d5b 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -44,7 +44,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index c3a096acef8c..45561ee4c5fe 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -44,7 +44,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 2f88033de445..dace39942cf2 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -52,7 +52,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const createRootStrictEffectsByDefault = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index de05d1a8c1d9..47ed0529927c 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -44,7 +44,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = true; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index a239d108dc00..bfcce69fe730 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -44,7 +44,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = true; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 441b7647bd4d..e45ef9d1af50 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -44,7 +44,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = true; export const enableUseHook = true; export const enableUseMemoCacheHook = false; -export const enableUseEventHook = false; +export const enableUseEffectEventHook = false; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = true; export const enableClientRenderFallbackOnTextMismatch = true; export const enableComponentStackLocations = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 1a06367d129e..89fae798d37b 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -53,7 +53,7 @@ export const enableCPUSuspense = true; export const enableFloat = true; export const enableUseHook = true; export const enableUseMemoCacheHook = true; -export const enableUseEventHook = true; +export const enableUseEffectEventHook = true; export const enableHostSingletons = true; // Logs additional User Timing API marks for use with an experimental profiling tool. diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 022d42e724a8..52fa873e633c 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -425,7 +425,7 @@ "437": "the \"precedence\" prop for links to stylesheets expects to receive a string but received something of type \"%s\" instead.", "438": "An unsupported type was passed to use(): %s", "439": "We didn't expect to see a forward reference. This is a bug in the React Server.", - "440": "A function wrapped in useEvent can't be called during rendering.", + "440": "A function wrapped in useEffectEvent can't be called during rendering.", "441": "An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.", "442": "The current renderer does not support Resources. This error is likely caused by a bug in React. Please file an issue.", "443": "acquireResource encountered a resource type it did not expect: \"%s\". this is a bug in React.", From 7efa9e59707b341f10fab79724e0fca373187925 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Thu, 15 Dec 2022 11:47:07 -0800 Subject: [PATCH 007/269] Fix unwinding context during selective hydration (#25876) This PR includes the previously reverted #25695 and #25754, and the fix for the regression test added in #25867. Tested internally with a previous failed test, and it's passing now. Co-authored-by: Andrew Clark --- ...MServerSelectiveHydration-test.internal.js | 76 ++++++++++++++- .../src/ReactFiberBeginWork.js | 32 +++++-- .../src/ReactFiberWorkLoop.js | 96 +++++++++++++------ 3 files changed, 164 insertions(+), 40 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index 3469c4faf068..f4483e9bca8f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -1496,12 +1496,10 @@ describe('ReactDOMServerSelectiveHydration', () => { // Start rendering. This will force the first boundary to hydrate // by scheduling it at one higher pri than Idle. expect(Scheduler).toFlushAndYieldThrough([ - // An update was scheduled to force hydrate the boundary, but React will - // continue rendering at Idle until the next time React yields. This is - // fine though because it will switch to the hydration level when it - // re-enters the work loop. 'App', - 'AA', + + // Start hydrating A + 'A', ]); // Hover over A which (could) schedule at one higher pri than Idle. @@ -1772,4 +1770,72 @@ describe('ReactDOMServerSelectiveHydration', () => { document.body.removeChild(container); }); + + // @gate experimental || www + it('regression test: can unwind context on selective hydration interruption', async () => { + const Context = React.createContext('DefaultContext'); + + function ContextReader(props) { + const value = React.useContext(Context); + Scheduler.unstable_yieldValue(value); + return null; + } + + function Child({text}) { + Scheduler.unstable_yieldValue(text); + return {text}; + } + const ChildWithBoundary = React.memo(function({text}) { + return ( + + + + ); + }); + + function App({a}) { + Scheduler.unstable_yieldValue('App'); + React.useEffect(() => { + Scheduler.unstable_yieldValue('Commit'); + }); + return ( + <> + + + + + + ); + } + const finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App', 'A', 'DefaultContext']); + const container = document.createElement('div'); + container.innerHTML = finalHTML; + document.body.appendChild(container); + + const spanA = container.getElementsByTagName('span')[0]; + + await act(async () => { + const root = ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushAndYieldThrough([ + 'App', + 'DefaultContext', + 'Commit', + ]); + + TODO_scheduleIdleDOMSchedulerTask(() => { + root.render(); + }); + expect(Scheduler).toFlushAndYieldThrough(['App', 'A']); + + dispatchClickEvent(spanA); + expect(Scheduler).toHaveYielded(['A']); + expect(Scheduler).toFlushAndYield([ + 'App', + 'AA', + 'DefaultContext', + 'Commit', + ]); + }); + }); }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 7b3f525d2dcb..53ed65925b97 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -282,6 +282,14 @@ import { const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; +// A special exception that's used to unwind the stack when an update flows +// into a dehydrated boundary. +export const SelectiveHydrationException: mixed = new Error( + "This is not a real error. It's an implementation detail of React's " + + "selective hydration feature. If this leaks into userspace, it's a bug in " + + 'React. Please file an issue.', +); + let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; @@ -2861,6 +2869,16 @@ function updateDehydratedSuspenseComponent( attemptHydrationAtLane, eventTime, ); + + // Throw a special object that signals to the work loop that it should + // interrupt the current render. + // + // Because we're inside a React-only execution stack, we don't + // strictly need to throw here — we could instead modify some internal + // work loop state. But using an exception means we don't need to + // check for this case on every iteration of the work loop. So doing + // it this way moves the check out of the fast path. + throw SelectiveHydrationException; } else { // We have already tried to ping at a higher priority than we're rendering with // so if we got here, we must have failed to hydrate at those levels. We must @@ -2871,15 +2889,17 @@ function updateDehydratedSuspenseComponent( } } - // If we have scheduled higher pri work above, this will just abort the render - // since we now have higher priority work. We'll try to infinitely suspend until - // we yield. TODO: We could probably just force yielding earlier instead. - renderDidSuspendDelayIfPossible(); - // If we rendered synchronously, we won't yield so have to render something. - // This will cause us to delete any existing content. + // If we did not selectively hydrate, we'll continue rendering without + // hydrating. Mark this tree as suspended to prevent it from committing + // outside a transition. + // + // This path should only happen if the hydration lane already suspended. + // Currently, it also happens during sync updates because there is no + // hydration lane for sync updates. // TODO: We should ideally have a sync hydration lane that we can apply to do // a pass where we hydrate this subtree in place using the previous Context and then // reapply the update afterwards. + renderDidSuspendDelayIfPossible(); return retrySuspenseComponentWithoutHydrating( current, workInProgress, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 0c86cbb32402..2a1166cc43c1 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -175,6 +175,7 @@ import { } from './ReactEventPriorities'; import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; import { + SelectiveHydrationException, beginWork as originalBeginWork, replayFunctionComponent, } from './ReactFiberBeginWork'; @@ -316,13 +317,14 @@ let workInProgress: Fiber | null = null; // The lanes we're rendering let workInProgressRootRenderLanes: Lanes = NoLanes; -opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5; +opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6; const NotSuspended: SuspendedReason = 0; const SuspendedOnError: SuspendedReason = 1; const SuspendedOnData: SuspendedReason = 2; const SuspendedOnImmediate: SuspendedReason = 3; const SuspendedOnDeprecatedThrowPromise: SuspendedReason = 4; const SuspendedAndReadyToUnwind: SuspendedReason = 5; +const SuspendedOnHydration: SuspendedReason = 6; // When this is true, the work-in-progress fiber just suspended (or errored) and // we've yet to unwind the stack. In some cases, we may yield to the main thread @@ -1701,6 +1703,31 @@ export function getRenderLanes(): Lanes { return renderLanes; } +function resetWorkInProgressStack() { + if (workInProgress === null) return; + let interruptedWork; + if (workInProgressSuspendedReason === NotSuspended) { + // Normal case. Work-in-progress hasn't started yet. Unwind all + // its parents. + interruptedWork = workInProgress.return; + } else { + // Work-in-progress is in suspended state. Reset the work loop and unwind + // both the suspended fiber and all its parents. + resetSuspendedWorkLoopOnUnwind(); + interruptedWork = workInProgress; + } + while (interruptedWork !== null) { + const current = interruptedWork.alternate; + unwindInterruptedWork( + current, + interruptedWork, + workInProgressRootRenderLanes, + ); + interruptedWork = interruptedWork.return; + } + workInProgress = null; +} + function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { root.finishedWork = null; root.finishedLanes = NoLanes; @@ -1714,28 +1741,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { cancelTimeout(timeoutHandle); } - if (workInProgress !== null) { - let interruptedWork; - if (workInProgressSuspendedReason === NotSuspended) { - // Normal case. Work-in-progress hasn't started yet. Unwind all - // its parents. - interruptedWork = workInProgress.return; - } else { - // Work-in-progress is in suspended state. Reset the work loop and unwind - // both the suspended fiber and all its parents. - resetSuspendedWorkLoopOnUnwind(); - interruptedWork = workInProgress; - } - while (interruptedWork !== null) { - const current = interruptedWork.alternate; - unwindInterruptedWork( - current, - interruptedWork, - workInProgressRootRenderLanes, - ); - interruptedWork = interruptedWork.return; - } - } + resetWorkInProgressStack(); workInProgressRoot = root; const rootWorkInProgress = createWorkInProgress(root.current, null); workInProgress = rootWorkInProgress; @@ -1797,6 +1803,17 @@ function handleThrow(root, thrownValue): void { workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves() ? SuspendedOnData : SuspendedOnImmediate; + } else if (thrownValue === SelectiveHydrationException) { + // An update flowed into a dehydrated boundary. Before we can apply the + // update, we need to finish hydrating. Interrupt the work-in-progress + // render so we can restart at the hydration lane. + // + // The ideal implementation would be able to switch contexts without + // unwinding the current stack. + // + // We could name this something more general but as of now it's the only + // case where we think this should happen. + workInProgressSuspendedReason = SuspendedOnHydration; } else { // This is a regular error. const isWakeable = @@ -2038,7 +2055,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { markRenderStarted(lanes); } - do { + outer: do { try { if ( workInProgressSuspendedReason !== NotSuspended && @@ -2054,11 +2071,23 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { // function and fork the behavior some other way. const unitOfWork = workInProgress; const thrownValue = workInProgressThrownValue; - workInProgressSuspendedReason = NotSuspended; - workInProgressThrownValue = null; - unwindSuspendedUnitOfWork(unitOfWork, thrownValue); - - // Continue with the normal work loop. + switch (workInProgressSuspendedReason) { + case SuspendedOnHydration: { + // Selective hydration. An update flowed into a dehydrated tree. + // Interrupt the current render so the work loop can switch to the + // hydration lane. + resetWorkInProgressStack(); + workInProgressRootExitStatus = RootDidNotComplete; + break outer; + } + default: { + // Continue with the normal work loop. + workInProgressSuspendedReason = NotSuspended; + workInProgressThrownValue = null; + unwindSuspendedUnitOfWork(unitOfWork, thrownValue); + break; + } + } } workLoopSync(); break; @@ -2216,6 +2245,14 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { unwindSuspendedUnitOfWork(unitOfWork, thrownValue); break; } + case SuspendedOnHydration: { + // Selective hydration. An update flowed into a dehydrated tree. + // Interrupt the current render so the work loop can switch to the + // hydration lane. + resetWorkInProgressStack(); + workInProgressRootExitStatus = RootDidNotComplete; + break outer; + } default: { throw new Error( 'Unexpected SuspendedReason. This is a bug in React.', @@ -3741,6 +3778,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { if ( didSuspendOrErrorWhileHydratingDEV() || originalError === SuspenseException || + originalError === SelectiveHydrationException || (originalError !== null && typeof originalError === 'object' && typeof originalError.then === 'function') From fabef7a6b71798fe2477720e59d090a0e74e0009 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Thu, 15 Dec 2022 12:23:53 -0800 Subject: [PATCH 008/269] Resubmit Add HydrationSyncLane (#25878) Depends on #25876 Resubmit #25711 again(previously reverted in #25812), and added the fix for unwinding in selective hydration during a hydration on the sync lane. --- .../src/__tests__/TimelineProfiler-test.js | 474 ++++++++---------- .../src/__tests__/preprocessData-test.js | 176 ++++--- ...MServerSelectiveHydration-test.internal.js | 177 +++++++ .../react-reconciler/src/ReactFiberLane.js | 82 +-- .../src/ReactFiberWorkLoop.js | 15 +- .../__tests__/DebugTracing-test.internal.js | 17 +- 6 files changed, 543 insertions(+), 398 deletions(-) diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js index bc69af0ead1f..180c1a572668 100644 --- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js +++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js @@ -126,37 +126,35 @@ describe('Timeline profiler', () => { setPerformanceMock(null); }); - // @reactVersion >=18.0 it('should mark sync render without suspends or state updates', () => { renderHelper(
); expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark concurrent render without suspends or state updates', () => { renderRootHelper(
); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -164,22 +162,21 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark render yields', async () => { function Bar() { Scheduler.unstable_yieldValue('Bar'); @@ -199,17 +196,16 @@ describe('Timeline profiler', () => { expect(Scheduler).toFlushAndYieldThrough(['Foo']); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-64", - "--render-start-64", - "--component-render-start-Foo", - "--component-render-stop", - "--render-yield", - ] - `); + Array [ + "--schedule-render-128", + "--render-start-128", + "--component-render-start-Foo", + "--component-render-stop", + "--render-yield", + ] + `); }); - // @reactVersion >=18.0 it('should mark sync render with suspense that resolves', async () => { const fakeSuspensePromise = Promise.resolve(true); function Example() { @@ -224,19 +220,19 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--component-render-start-Example", "--component-render-stop", - "--suspense-suspend-0-Example-mount-1-", + "--suspense-suspend-0-Example-mount-2-", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] @@ -252,7 +248,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark sync render with suspense that rejects', async () => { const fakeSuspensePromise = Promise.reject(new Error('error')); function Example() { @@ -267,19 +262,19 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--component-render-start-Example", "--component-render-stop", - "--suspense-suspend-0-Example-mount-1-", + "--suspense-suspend-0-Example-mount-2-", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] @@ -291,7 +286,6 @@ describe('Timeline profiler', () => { expect(clearedMarks).toContain(`--suspense-rejected-0-Example`); }); - // @reactVersion >=18.0 it('should mark concurrent render with suspense that resolves', async () => { const fakeSuspensePromise = Promise.resolve(true); function Example() { @@ -305,10 +299,10 @@ describe('Timeline profiler', () => { ); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -316,18 +310,18 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", - "--suspense-suspend-0-Example-mount-16-", + "--suspense-suspend-0-Example-mount-32-", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] @@ -343,7 +337,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark concurrent render with suspense that rejects', async () => { const fakeSuspensePromise = Promise.reject(new Error('error')); function Example() { @@ -357,10 +350,10 @@ describe('Timeline profiler', () => { ); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -368,18 +361,18 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", - "--suspense-suspend-0-Example-mount-16-", + "--suspense-suspend-0-Example-mount-32-", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] @@ -395,7 +388,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark cascading class component state updates', () => { class Example extends React.Component { state = {didMount: false}; @@ -410,10 +402,10 @@ describe('Timeline profiler', () => { renderRootHelper(); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -421,36 +413,35 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", - "--schedule-state-update-1-Example", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", + "--schedule-state-update-2-Example", "--layout-effects-stop", - "--render-start-1", + "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark cascading class component force updates', () => { class Example extends React.Component { componentDidMount() { @@ -464,10 +455,10 @@ describe('Timeline profiler', () => { renderRootHelper(); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -475,36 +466,35 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", - "--schedule-forced-update-1-Example", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", + "--schedule-forced-update-2-Example", "--layout-effects-stop", - "--render-start-1", + "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark render phase state updates for class component', () => { class Example extends React.Component { state = {didRender: false}; @@ -519,10 +509,10 @@ describe('Timeline profiler', () => { renderRootHelper(); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -540,25 +530,24 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", - "--schedule-state-update-16-Example", + "--schedule-state-update-32-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark render phase force updates for class component', () => { let forced = false; class Example extends React.Component { @@ -574,10 +563,10 @@ describe('Timeline profiler', () => { renderRootHelper(); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -595,25 +584,24 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", - "--schedule-forced-update-16-Example", + "--schedule-forced-update-32-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark cascading layout updates', () => { function Example() { const [didMount, setDidMount] = React.useState(false); @@ -626,10 +614,10 @@ describe('Timeline profiler', () => { renderRootHelper(); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -637,38 +625,37 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--component-layout-effect-mount-start-Example", - "--schedule-state-update-1-Example", + "--schedule-state-update-2-Example", "--component-layout-effect-mount-stop", "--layout-effects-stop", - "--render-start-1", + "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark cascading passive updates', () => { function Example() { const [didMount, setDidMount] = React.useState(false); @@ -684,41 +671,40 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-16", - "--render-start-16", + "--schedule-render-32", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", - "--passive-effects-start-16", + "--passive-effects-start-32", "--component-passive-effect-mount-start-Example", - "--schedule-state-update-16-Example", + "--schedule-state-update-32-Example", "--component-passive-effect-mount-stop", "--passive-effects-stop", - "--render-start-16", + "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark render phase updates', () => { function Example() { const [didRender, setDidRender] = React.useState(false); @@ -734,26 +720,25 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-16", - "--render-start-16", + "--schedule-render-32", + "--render-start-32", "--component-render-start-Example", - "--schedule-state-update-16-Example", + "--schedule-state-update-32-Example", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark sync render that throws', async () => { spyOn(console, 'error'); @@ -782,8 +767,8 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", @@ -791,32 +776,31 @@ describe('Timeline profiler', () => { "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", - "--schedule-state-update-1-ErrorBoundary", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", + "--schedule-state-update-2-ErrorBoundary", "--layout-effects-stop", "--commit-stop", - "--render-start-1", + "--render-start-2", "--component-render-start-ErrorBoundary", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark concurrent render that throws', async () => { spyOn(console, 'error'); @@ -845,10 +829,10 @@ describe('Timeline profiler', () => { ); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); clearPendingMarks(); @@ -856,7 +840,7 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--render-start-16", + "--render-start-32", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", @@ -864,7 +848,7 @@ describe('Timeline profiler', () => { "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", - "--render-start-16", + "--render-start-32", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", @@ -872,32 +856,31 @@ describe('Timeline profiler', () => { "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", - "--schedule-state-update-1-ErrorBoundary", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", + "--schedule-state-update-2-ErrorBoundary", "--layout-effects-stop", - "--render-start-1", + "--render-start-2", "--component-render-start-ErrorBoundary", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('should mark passive and layout effects', async () => { function ComponentWithEffects() { React.useLayoutEffect(() => { @@ -947,18 +930,18 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-16", - "--render-start-16", + "--schedule-render-32", + "--render-start-32", "--component-render-start-ComponentWithEffects", "--component-render-stop", "--render-stop", - "--commit-start-16", + "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-16", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-32", "--component-layout-effect-mount-start-ComponentWithEffects", "--component-layout-effect-mount-stop", "--component-layout-effect-mount-start-ComponentWithEffects", @@ -977,17 +960,17 @@ describe('Timeline profiler', () => { ]); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--passive-effects-start-16", - "--component-passive-effect-mount-start-ComponentWithEffects", - "--component-passive-effect-mount-stop", - "--component-passive-effect-mount-start-ComponentWithEffects", - "--component-passive-effect-mount-stop", - "--component-passive-effect-mount-start-ComponentWithEffects", - "--component-passive-effect-mount-stop", - "--passive-effects-stop", - ] - `); + Array [ + "--passive-effects-start-32", + "--component-passive-effect-mount-start-ComponentWithEffects", + "--component-passive-effect-mount-stop", + "--component-passive-effect-mount-start-ComponentWithEffects", + "--component-passive-effect-mount-stop", + "--component-passive-effect-mount-start-ComponentWithEffects", + "--component-passive-effect-mount-stop", + "--passive-effects-stop", + ] + `); clearPendingMarks(); @@ -1005,22 +988,22 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--component-layout-effect-unmount-start-ComponentWithEffects", "--component-layout-effect-unmount-stop", "--component-layout-effect-unmount-start-ComponentWithEffects", "--component-layout-effect-unmount-stop", - "--layout-effects-start-1", + "--layout-effects-start-2", "--layout-effects-stop", - "--passive-effects-start-1", + "--passive-effects-start-2", "--component-passive-effect-unmount-start-ComponentWithEffects", "--component-passive-effect-unmount-stop", "--component-passive-effect-unmount-start-ComponentWithEffects", @@ -1034,39 +1017,36 @@ describe('Timeline profiler', () => { }); describe('lane labels', () => { - // @reactVersion >=18.0 it('regression test SyncLane', () => { renderHelper(
); expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-render-1", - "--render-start-1", + "--schedule-render-2", + "--render-start-2", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('regression test DefaultLane', () => { renderRootHelper(
); expect(clearedMarks).toMatchInlineSnapshot(` - Array [ - "--schedule-render-16", - ] - `); + Array [ + "--schedule-render-32", + ] + `); }); - // @reactVersion >=18.0 it('regression test InputDiscreteLane', async () => { const targetRef = React.createRef(null); @@ -1090,25 +1070,24 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-state-update-1-App", - "--render-start-1", + "--schedule-state-update-2-App", + "--render-start-2", "--component-render-start-App", "--component-render-stop", "--render-stop", - "--commit-start-1", + "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-1", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] `); }); - // @reactVersion >=18.0 it('regression test InputContinuousLane', async () => { const targetRef = React.createRef(null); @@ -1131,18 +1110,18 @@ describe('Timeline profiler', () => { expect(clearedMarks).toMatchInlineSnapshot(` Array [ - "--schedule-state-update-4-App", - "--render-start-4", + "--schedule-state-update-8-App", + "--render-start-8", "--component-render-start-App", "--component-render-stop", "--render-stop", - "--commit-start-4", + "--commit-start-8", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", - "--layout-effects-start-4", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--layout-effects-start-8", "--layout-effects-stop", "--commit-stop", ] @@ -1205,7 +1184,6 @@ describe('Timeline profiler', () => { utils.act(() => store.profilerStore.startProfiling()); }); - // @reactVersion >=18.0 it('should mark sync render without suspends or state updates', () => { renderHelper(
); @@ -1213,7 +1191,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1222,7 +1200,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark concurrent render without suspends or state updates', () => { utils.act(() => renderRootHelper(
)); @@ -1230,7 +1207,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1239,7 +1216,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark concurrent render without suspends or state updates', () => { let updaterFn; @@ -1272,7 +1248,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000001000", "timestamp": 10, "type": "schedule-state-update", "warning": null, @@ -1281,7 +1257,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000001000000", + "lanes": "0b0000000000000000000000010000000", "timestamp": 10, "type": "schedule-state-update", "warning": null, @@ -1290,7 +1266,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000001000000", + "lanes": "0b0000000000000000000000010000000", "timestamp": 10, "type": "schedule-state-update", "warning": null, @@ -1299,7 +1275,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-state-update", "warning": null, @@ -1586,7 +1562,6 @@ describe('Timeline profiler', () => { expect(timelineData.componentMeasures).toHaveLength(2); }); - // @reactVersion >=18.0 it('should mark cascading class component state updates', () => { class Example extends React.Component { state = {didMount: false}; @@ -1629,7 +1604,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1638,7 +1613,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 20, "type": "schedule-state-update", "warning": null, @@ -1647,7 +1622,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark cascading class component force updates', () => { let forced = false; class Example extends React.Component { @@ -1689,14 +1663,14 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, }, Object { "componentName": "Example", - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 20, "type": "schedule-force-update", "warning": null, @@ -1705,7 +1679,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark render phase state updates for class component', () => { class Example extends React.Component { state = {didRender: false}; @@ -1758,7 +1731,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1767,7 +1740,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-state-update", "warning": null, @@ -1776,7 +1749,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark render phase force updates for class component', () => { let forced = false; class Example extends React.Component { @@ -1828,14 +1800,14 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, }, Object { "componentName": "Example", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 20, "type": "schedule-force-update", "warning": null, @@ -1844,7 +1816,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark cascading layout updates', () => { function Example() { const [didMount, setDidMount] = React.useState(false); @@ -1891,7 +1862,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1900,7 +1871,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 21, "type": "schedule-state-update", "warning": null, @@ -1909,7 +1880,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark cascading passive updates', () => { function Example() { const [didMount, setDidMount] = React.useState(false); @@ -1955,7 +1925,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -1964,7 +1934,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 21, "type": "schedule-state-update", "warning": null, @@ -1973,7 +1943,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark render phase updates', () => { function Example() { const [didRender, setDidRender] = React.useState(false); @@ -2005,7 +1974,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2014,7 +1983,7 @@ describe('Timeline profiler', () => { "componentName": "Example", "componentStack": " in Example (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 20, "type": "schedule-state-update", "warning": null, @@ -2023,7 +1992,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark sync render that throws', async () => { spyOn(console, 'error'); @@ -2090,7 +2058,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2099,7 +2067,7 @@ describe('Timeline profiler', () => { "componentName": "ErrorBoundary", "componentStack": " in ErrorBoundary (at **)", - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 20, "type": "schedule-state-update", "warning": null, @@ -2119,7 +2087,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark concurrent render that throws', async () => { spyOn(console, 'error'); @@ -2204,7 +2171,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2213,7 +2180,7 @@ describe('Timeline profiler', () => { "componentName": "ErrorBoundary", "componentStack": " in ErrorBoundary (at **)", - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 30, "type": "schedule-state-update", "warning": null, @@ -2240,7 +2207,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >=18.0 it('should mark passive and layout effects', async () => { function ComponentWithEffects() { React.useLayoutEffect(() => { @@ -2395,7 +2361,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render-idle", }, @@ -2403,7 +2369,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render", }, @@ -2411,7 +2377,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "commit", }, @@ -2419,7 +2385,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "layout-effects", }, @@ -2427,7 +2393,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "passive-effects", }, @@ -2437,7 +2403,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render-idle", }, @@ -2445,7 +2411,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render", }, @@ -2453,7 +2419,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "commit", }, @@ -2461,7 +2427,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "layout-effects", }, @@ -2469,7 +2435,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "passive-effects", }, @@ -2505,7 +2471,7 @@ describe('Timeline profiler', () => { expect(timelineData.schedulingEvents).toMatchInlineSnapshot(` Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2515,7 +2481,7 @@ describe('Timeline profiler', () => { "componentStack": " in Child (at **) in CommponentWithChildren (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-state-update", "warning": null, diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js index 6c38ce6c9953..218783657bc8 100644 --- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js +++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js @@ -613,7 +613,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >= 18.0 it('should process a sample legacy render sequence', async () => { utils.legacyRender(
, document.createElement('div')); @@ -629,7 +628,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.01, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.006, "type": "render-idle", }, @@ -637,7 +636,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.001, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.006, "type": "render", }, @@ -645,7 +644,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.008, "type": "commit", }, @@ -653,7 +652,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.014, "type": "layout-effects", }, @@ -714,12 +713,13 @@ describe('Timeline profiler', () => { 30 => "Offscreen", }, "laneToReactMeasureMap": Map { - 0 => Array [ + 0 => Array [], + 1 => Array [ Object { "batchUID": 0, "depth": 0, "duration": 0.01, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.006, "type": "render-idle", }, @@ -727,7 +727,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.001, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.006, "type": "render", }, @@ -735,7 +735,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.008, "type": "commit", }, @@ -743,12 +743,11 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.014, "type": "layout-effects", }, ], - 1 => Array [], 2 => Array [], 3 => Array [], 4 => Array [], @@ -785,7 +784,7 @@ describe('Timeline profiler', () => { "reactVersion": "", "schedulingEvents": Array [ Object { - "lanes": "0b0000000000000000000000000000000", + "lanes": "0b0000000000000000000000000000001", "timestamp": 0.005, "type": "schedule-render", "warning": null, @@ -800,7 +799,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >= 18.0 it('should process a sample createRoot render sequence', async () => { function App() { const [didMount, setDidMount] = React.useState(false); @@ -827,7 +825,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.012, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.006, "type": "render-idle", }, @@ -835,7 +833,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.006, "type": "render", }, @@ -843,7 +841,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.01, "type": "commit", }, @@ -851,7 +849,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.016, "type": "layout-effects", }, @@ -859,7 +857,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.004, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.019, "type": "passive-effects", }, @@ -869,7 +867,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.012, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.024, "type": "render-idle", }, @@ -877,7 +875,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.024, "type": "render", }, @@ -885,7 +883,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.028, "type": "commit", }, @@ -893,7 +891,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.034, "type": "layout-effects", }, @@ -901,7 +899,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.037, "type": "passive-effects", }, @@ -995,12 +993,13 @@ describe('Timeline profiler', () => { 1 => Array [], 2 => Array [], 3 => Array [], - 4 => Array [ + 4 => Array [], + 5 => Array [ Object { "batchUID": 0, "depth": 0, "duration": 0.012, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.006, "type": "render-idle", }, @@ -1008,7 +1007,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.006, "type": "render", }, @@ -1016,7 +1015,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.01, "type": "commit", }, @@ -1024,7 +1023,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.016, "type": "layout-effects", }, @@ -1032,7 +1031,7 @@ describe('Timeline profiler', () => { "batchUID": 0, "depth": 0, "duration": 0.004, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.019, "type": "passive-effects", }, @@ -1040,7 +1039,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.012, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.024, "type": "render-idle", }, @@ -1048,7 +1047,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.024, "type": "render", }, @@ -1056,7 +1055,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.008, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.028, "type": "commit", }, @@ -1064,7 +1063,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0.001, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.034, "type": "layout-effects", }, @@ -1072,12 +1071,11 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0.003, - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.037, "type": "passive-effects", }, ], - 5 => Array [], 6 => Array [], 7 => Array [], 8 => Array [], @@ -1110,14 +1108,14 @@ describe('Timeline profiler', () => { "reactVersion": "", "schedulingEvents": Array [ Object { - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.005, "type": "schedule-render", "warning": null, }, Object { "componentName": "App", - "lanes": "0b0000000000000000000000000000100", + "lanes": "0b0000000000000000000000000000101", "timestamp": 0.021, "type": "schedule-state-update", "warning": null, @@ -1947,7 +1945,6 @@ describe('Timeline profiler', () => { global.IS_REACT_ACT_ENVIRONMENT = true; }); - // @reactVersion >= 18.0 it('should process a sample legacy render sequence', async () => { utils.legacyRender(
, document.createElement('div')); utils.act(() => store.profilerStore.stopProfiling()); @@ -1963,7 +1960,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render-idle", }, @@ -1971,7 +1968,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render", }, @@ -1979,7 +1976,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "commit", }, @@ -1987,7 +1984,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "layout-effects", }, @@ -1998,13 +1995,13 @@ describe('Timeline profiler', () => { "flamechart": Array [], "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { - 1 => "Sync", - 2 => "InputContinuousHydration", - 4 => "InputContinuous", - 8 => "DefaultHydration", - 16 => "Default", - 32 => "TransitionHydration", - 64 => "Transition", + 1 => "SyncHydrationLane", + 2 => "Sync", + 4 => "InputContinuousHydration", + 8 => "InputContinuous", + 16 => "DefaultHydration", + 32 => "Default", + 64 => "TransitionHydration", 128 => "Transition", 256 => "Transition", 512 => "Transition", @@ -2020,7 +2017,7 @@ describe('Timeline profiler', () => { 524288 => "Transition", 1048576 => "Transition", 2097152 => "Transition", - 4194304 => "Retry", + 4194304 => "Transition", 8388608 => "Retry", 16777216 => "Retry", 33554432 => "Retry", @@ -2031,12 +2028,13 @@ describe('Timeline profiler', () => { 1073741824 => "Offscreen", }, "laneToReactMeasureMap": Map { - 1 => Array [ + 1 => Array [], + 2 => Array [ Object { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render-idle", }, @@ -2044,7 +2042,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "render", }, @@ -2052,7 +2050,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "commit", }, @@ -2060,12 +2058,11 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "layout-effects", }, ], - 2 => Array [], 4 => Array [], 8 => Array [], 16 => Array [], @@ -2102,7 +2099,7 @@ describe('Timeline profiler', () => { "reactVersion": "", "schedulingEvents": Array [ Object { - "lanes": "0b0000000000000000000000000000001", + "lanes": "0b0000000000000000000000000000010", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2117,7 +2114,6 @@ describe('Timeline profiler', () => { `); }); - // @reactVersion >= 18.0 it('should process a sample createRoot render sequence', async () => { function App() { const [didMount, setDidMount] = React.useState(false); @@ -2153,7 +2149,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render-idle", }, @@ -2161,7 +2157,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render", }, @@ -2169,7 +2165,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "commit", }, @@ -2177,7 +2173,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "layout-effects", }, @@ -2185,7 +2181,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "passive-effects", }, @@ -2195,7 +2191,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render-idle", }, @@ -2203,7 +2199,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render", }, @@ -2211,7 +2207,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "commit", }, @@ -2219,7 +2215,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "layout-effects", }, @@ -2227,7 +2223,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "passive-effects", }, @@ -2267,13 +2263,13 @@ describe('Timeline profiler', () => { "flamechart": Array [], "internalModuleSourceToRanges": Map {}, "laneToLabelMap": Map { - 1 => "Sync", - 2 => "InputContinuousHydration", - 4 => "InputContinuous", - 8 => "DefaultHydration", - 16 => "Default", - 32 => "TransitionHydration", - 64 => "Transition", + 1 => "SyncHydrationLane", + 2 => "Sync", + 4 => "InputContinuousHydration", + 8 => "InputContinuous", + 16 => "DefaultHydration", + 32 => "Default", + 64 => "TransitionHydration", 128 => "Transition", 256 => "Transition", 512 => "Transition", @@ -2289,7 +2285,7 @@ describe('Timeline profiler', () => { 524288 => "Transition", 1048576 => "Transition", 2097152 => "Transition", - 4194304 => "Retry", + 4194304 => "Transition", 8388608 => "Retry", 16777216 => "Retry", 33554432 => "Retry", @@ -2304,12 +2300,13 @@ describe('Timeline profiler', () => { 2 => Array [], 4 => Array [], 8 => Array [], - 16 => Array [ + 16 => Array [], + 32 => Array [ Object { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render-idle", }, @@ -2317,7 +2314,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render", }, @@ -2325,7 +2322,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "commit", }, @@ -2333,7 +2330,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "layout-effects", }, @@ -2341,7 +2338,7 @@ describe('Timeline profiler', () => { "batchUID": 1, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "passive-effects", }, @@ -2349,7 +2346,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render-idle", }, @@ -2357,7 +2354,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "render", }, @@ -2365,7 +2362,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "commit", }, @@ -2373,7 +2370,7 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 1, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "layout-effects", }, @@ -2381,12 +2378,11 @@ describe('Timeline profiler', () => { "batchUID": 2, "depth": 0, "duration": 0, - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "passive-effects", }, ], - 32 => Array [], 64 => Array [], 128 => Array [], 256 => Array [], @@ -2419,7 +2415,7 @@ describe('Timeline profiler', () => { "reactVersion": "", "schedulingEvents": Array [ Object { - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-render", "warning": null, @@ -2428,7 +2424,7 @@ describe('Timeline profiler', () => { "componentName": "App", "componentStack": " in App (at **)", - "lanes": "0b0000000000000000000000000010000", + "lanes": "0b0000000000000000000000000100000", "timestamp": 10, "type": "schedule-state-update", "warning": null, diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index f4483e9bca8f..c6da651a813a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -21,6 +21,7 @@ let Suspense; let act; let IdleEventPriority; +let ContinuousEventPriority; function dispatchMouseHoverEvent(to, from) { if (!to) { @@ -110,6 +111,18 @@ function TODO_scheduleIdleDOMSchedulerTask(fn) { }); } +function TODO_scheduleContinuousSchedulerTask(fn) { + ReactDOM.unstable_runWithPriority(ContinuousEventPriority, () => { + const prevEvent = window.event; + window.event = {type: 'message'}; + try { + fn(); + } finally { + window.event = prevEvent; + } + }); +} + describe('ReactDOMServerSelectiveHydration', () => { beforeEach(() => { jest.resetModuleRegistry(); @@ -125,6 +138,8 @@ describe('ReactDOMServerSelectiveHydration', () => { Suspense = React.Suspense; IdleEventPriority = require('react-reconciler/constants').IdleEventPriority; + ContinuousEventPriority = require('react-reconciler/constants') + .ContinuousEventPriority; }); it('hydrates the target boundary synchronously during a click', async () => { @@ -1771,6 +1786,106 @@ describe('ReactDOMServerSelectiveHydration', () => { document.body.removeChild(container); }); + it('can force hydration in response to sync update', () => { + function Child({text}) { + Scheduler.unstable_yieldValue(`Child ${text}`); + return (spanRef = ref)}>{text}; + } + function App({text}) { + Scheduler.unstable_yieldValue(`App ${text}`); + return ( +
+ + + +
+ ); + } + + let spanRef; + const finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App A', 'Child A']); + const container = document.createElement('div'); + document.body.appendChild(container); + container.innerHTML = finalHTML; + const initialSpan = container.getElementsByTagName('span')[0]; + const root = ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushUntilNextPaint(['App A']); + + ReactDOM.flushSync(() => { + root.render(); + }); + expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); + expect(initialSpan).toBe(spanRef); + }); + + // @gate experimental || www + it('can force hydration in response to continuous update', () => { + function Child({text}) { + Scheduler.unstable_yieldValue(`Child ${text}`); + return (spanRef = ref)}>{text}; + } + function App({text}) { + Scheduler.unstable_yieldValue(`App ${text}`); + return ( +
+ + + +
+ ); + } + + let spanRef; + const finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App A', 'Child A']); + const container = document.createElement('div'); + document.body.appendChild(container); + container.innerHTML = finalHTML; + const initialSpan = container.getElementsByTagName('span')[0]; + const root = ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushUntilNextPaint(['App A']); + + TODO_scheduleContinuousSchedulerTask(() => { + root.render(); + }); + expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']); + expect(initialSpan).toBe(spanRef); + }); + + it('can force hydration in response to default update', () => { + function Child({text}) { + Scheduler.unstable_yieldValue(`Child ${text}`); + return (spanRef = ref)}>{text}; + } + function App({text}) { + Scheduler.unstable_yieldValue(`App ${text}`); + return ( +
+ + + +
+ ); + } + + let spanRef; + const finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App A', 'Child A']); + const container = document.createElement('div'); + document.body.appendChild(container); + container.innerHTML = finalHTML; + const initialSpan = container.getElementsByTagName('span')[0]; + const root = ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushUntilNextPaint(['App A']); + + ReactDOM.unstable_batchedUpdates(() => { + root.render(); + }); + expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']); + expect(initialSpan).toBe(spanRef); + }); + // @gate experimental || www it('regression test: can unwind context on selective hydration interruption', async () => { const Context = React.createContext('DefaultContext'); @@ -1838,4 +1953,66 @@ describe('ReactDOMServerSelectiveHydration', () => { ]); }); }); + + it('regression test: can unwind context on selective hydration interruption for sync updates', async () => { + const Context = React.createContext('DefaultContext'); + + function ContextReader(props) { + const value = React.useContext(Context); + Scheduler.unstable_yieldValue(value); + return null; + } + + function Child({text}) { + Scheduler.unstable_yieldValue(text); + return {text}; + } + const ChildWithBoundary = React.memo(function({text}) { + return ( + + + + ); + }); + + function App({a}) { + Scheduler.unstable_yieldValue('App'); + React.useEffect(() => { + Scheduler.unstable_yieldValue('Commit'); + }); + return ( + <> + + + + + + ); + } + const finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App', 'A', 'DefaultContext']); + const container = document.createElement('div'); + container.innerHTML = finalHTML; + + await act(async () => { + const root = ReactDOMClient.hydrateRoot(container, ); + expect(Scheduler).toFlushAndYieldThrough([ + 'App', + 'DefaultContext', + 'Commit', + ]); + + ReactDOM.flushSync(() => { + root.render(); + }); + expect(Scheduler).toHaveYielded([ + 'App', + 'A', + 'App', + 'AA', + 'DefaultContext', + 'Commit', + ]); + }); + }); }); diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 7ff44ac9a56b..97a28f88099e 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -36,39 +36,39 @@ export const TotalLanes = 31; export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000; -export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; - -export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010; -export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100; - -export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000; -export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000; - -const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000; -const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000; -const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000; -const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000; -const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000; -const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000; -const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000; -const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000; -const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000; -const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000; -const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000; -const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000; -const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000; -const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000; -const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000; -const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000; -const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000; -const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000; - -const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000; -const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000; -const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000; -const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000; -const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000; -const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000; +export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001; +export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010; + +export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100; +export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000; + +export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000; +export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000; + +const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000; +const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000; +const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000; +const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000; +const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000; +const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000; +const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000; +const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000; +const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000; +const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000; +const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000; +const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000; +const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000; +const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000; +const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000; +const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000; +const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000; +const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000; + +const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000; +const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000; +const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000; +const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000; +const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000; export const SomeRetryLane: Lane = RetryLane1; @@ -85,6 +85,9 @@ export const OffscreenLane: Lane = /* */ 0b1000000000000000000 // It should be kept in sync with the Lanes values above. export function getLabelForLane(lane: Lane): string | void { if (enableSchedulingProfiler) { + if (lane & SyncHydrationLane) { + return 'SyncHydrationLane'; + } if (lane & SyncLane) { return 'Sync'; } @@ -131,6 +134,8 @@ let nextRetryLane: Lane = RetryLane1; function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { switch (getHighestPriorityLane(lanes)) { + case SyncHydrationLane: + return SyncHydrationLane; case SyncLane: return SyncLane; case InputContinuousHydrationLane: @@ -164,7 +169,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { case RetryLane2: case RetryLane3: case RetryLane4: - case RetryLane5: return lanes & RetryLanes; case SelectiveHydrationLane: return SelectiveHydrationLane; @@ -327,6 +331,7 @@ export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number { function computeExpirationTime(lane: Lane, currentTime: number) { switch (lane) { + case SyncHydrationLane: case SyncLane: case InputContinuousHydrationLane: case InputContinuousLane: @@ -364,7 +369,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) { case RetryLane2: case RetryLane3: case RetryLane4: - case RetryLane5: // TODO: Retries should be allowed to expire if they are CPU bound for // too long, but when I made this change it caused a spike in browser // crashes. There must be some other underlying bug; not super urgent but @@ -459,7 +463,7 @@ export function getLanesToRetrySynchronouslyOnError( } export function includesSyncLane(lanes: Lanes): boolean { - return (lanes & SyncLane) !== NoLanes; + return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes; } export function includesNonIdleWork(lanes: Lanes): boolean { @@ -469,6 +473,8 @@ export function includesOnlyRetries(lanes: Lanes): boolean { return (lanes & RetryLanes) === lanes; } export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean { + // TODO: Should hydration lanes be included here? This function is only + // used in `updateDeferredValueImpl`. const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane; return (lanes & UrgentLanes) === NoLanes; } @@ -749,6 +755,9 @@ export function getBumpedLaneForHydration( let lane; switch (renderLane) { + case SyncLane: + lane = SyncHydrationLane; + break; case InputContinuousLane: lane = InputContinuousHydrationLane; break; @@ -775,7 +784,6 @@ export function getBumpedLaneForHydration( case RetryLane2: case RetryLane3: case RetryLane4: - case RetryLane5: lane = TransitionHydrationLane; break; case IdleLane: diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 2a1166cc43c1..0adee6a3b333 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -138,7 +138,7 @@ import { NoTimestamp, claimNextTransitionLane, claimNextRetryLane, - includesSomeLane, + includesSyncLane, isSubsetOfLanes, mergeLanes, removeLanes, @@ -915,7 +915,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // TODO: Temporary until we confirm this warning is not fired. if ( existingCallbackNode == null && - existingCallbackPriority !== SyncLane + !includesSyncLane(existingCallbackPriority) ) { console.error( 'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.', @@ -933,7 +933,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // Schedule a new callback. let newCallbackNode; - if (newCallbackPriority === SyncLane) { + if (includesSyncLane(newCallbackPriority)) { // Special case: Sync React callbacks are scheduled on a special // internal queue if (root.tag === LegacyRoot) { @@ -1477,7 +1477,7 @@ function performSyncWorkOnRoot(root) { flushPassiveEffects(); let lanes = getNextLanes(root, NoLanes); - if (!includesSomeLane(lanes, SyncLane)) { + if (!includesSyncLane(lanes)) { // There's no remaining sync work left. ensureRootIsScheduled(root, now()); return null; @@ -2931,16 +2931,13 @@ function commitRootImpl( // TODO: We can optimize this by not scheduling the callback earlier. Since we // currently schedule the callback in multiple places, will wait until those // are consolidated. - if ( - includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && - root.tag !== LegacyRoot - ) { + if (includesSyncLane(pendingPassiveEffectsLanes) && root.tag !== LegacyRoot) { flushPassiveEffects(); } // Read this again, since a passive effect might have updated it remainingLanes = root.pendingLanes; - if (includesSomeLane(remainingLanes, (SyncLane: Lane))) { + if (includesSyncLane(remainingLanes)) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { markNestedUpdateScheduled(); } diff --git a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js index cd235e7cee30..0d4cf546c6d6 100644 --- a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js +++ b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js @@ -16,8 +16,9 @@ describe('DebugTracing', () => { let logs; - const DEFAULT_LANE_STRING = '0b0000000000000000000000000010000'; - const RETRY_LANE_STRING = '0b0000000010000000000000000000000'; + const SYNC_LANE_STRING = '0b0000000000000000000000000000010'; + const DEFAULT_LANE_STRING = '0b0000000000000000000000000100000'; + const RETRY_LANE_STRING = '0b0000000100000000000000000000000'; global.IS_REACT_ACT_ENVIRONMENT = true; @@ -87,9 +88,9 @@ describe('DebugTracing', () => { ); expect(logs).toEqual([ - 'group: ⚛️ render (0b0000000000000000000000000000001)', + `group: ⚛️ render (${SYNC_LANE_STRING})`, 'log: ⚛️ Example suspended', - 'groupEnd: ⚛️ render (0b0000000000000000000000000000001)', + `groupEnd: ⚛️ render (${SYNC_LANE_STRING})`, ]); logs.splice(0); @@ -121,9 +122,9 @@ describe('DebugTracing', () => { ); expect(logs).toEqual([ - 'group: ⚛️ render (0b0000000000000000000000000000001)', + `group: ⚛️ render (${SYNC_LANE_STRING})`, 'log: ', - 'groupEnd: ⚛️ render (0b0000000000000000000000000000001)', + `groupEnd: ⚛️ render (${SYNC_LANE_STRING})`, ]); logs.splice(0); @@ -237,7 +238,7 @@ describe('DebugTracing', () => { expect(logs).toEqual([ `group: ⚛️ commit (${DEFAULT_LANE_STRING})`, `group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`, - 'log: ⚛️ Example updated state (0b0000000000000000000000000000001)', + `log: ⚛️ Example updated state (${SYNC_LANE_STRING})`, `groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`, `groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`, ]); @@ -295,7 +296,7 @@ describe('DebugTracing', () => { expect(logs).toEqual([ `group: ⚛️ commit (${DEFAULT_LANE_STRING})`, `group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`, - 'log: ⚛️ Example updated state (0b0000000000000000000000000000001)', + `log: ⚛️ Example updated state (${SYNC_LANE_STRING})`, `groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`, `groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`, ]); From 2b1fb91a55deb9b7b60452cb57184c2f182a42fd Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Tue, 20 Dec 2022 14:27:01 -0500 Subject: [PATCH 009/269] ESLint upgrade to use hermes-eslint (#25915) Hermes parser is the preferred parser for Flow code going forward. We need to upgrade to this parser to support new Flow syntax like function `this` context type annotations or `ObjectType['prop']` syntax. Unfortunately, there's quite a few upgrades here to make it work somehow (dependencies between the changes) - ~Upgrade `eslint` to `8.*`~ reverted this as the React eslint plugin tests depend on the older version and there's a [yarn bug](https://github.com/yarnpkg/yarn/issues/6285) that prevents `devDependencies` and `peerDependencies` to different versions. - Remove `eslint-config-fbjs` preset dependency and inline the rules, imho this makes it a lot clearer what the rules are. - Remove the turned off `jsx-a11y/*` rules and it's dependency instead of inlining those from the `fbjs` config. - Update parser and dependency from `babel-eslint` to `hermes-eslint`. - `ft-flow/no-unused-expressions` rule replaces `no-unused-expressions` which now allows standalone type asserts, e.g. `(foo: number);` - Bunch of globals added to the eslint config - Disabled `no-redeclare`, seems like the eslint upgrade started making this more precise and warn against re-defined globals like `__EXPERIMENTAL__` (in rollup scripts) or `fetch` (when importing fetch from node-fetch). - Minor lint fixes like duplicate keys in objects. --- .eslintrc.js | 263 +++++++++- package.json | 8 +- .../eslint-plugin-react-hooks/package.json | 4 +- packages/react-art/src/ReactARTHostConfig.js | 2 + .../ReactFlightClientHostConfig.custom.js | 8 +- .../flow-typed/jest.js | 13 +- .../npm/react-test-renderer_v16.x.x.js | 3 +- .../profilerChangeDescriptions-test.js | 2 +- .../src/__tests__/profilingHostRoot-test.js | 2 +- .../react-devtools-shell/src/e2e/devtools.js | 9 + .../src/multi/devtools.js | 9 + .../react-devtools-shell/src/multi/left.js | 7 + .../react-devtools-shell/src/multi/right.js | 7 + .../src/import-worker/importFile.js | 2 - .../src/client/ReactDOMSelection.js | 4 +- .../src/test-utils/ReactTestUtils.js | 4 +- packages/react-native-renderer/fabric.js | 1 - packages/react-native-renderer/index.js | 2 +- .../src/ReactFabricHostConfig.js | 1 - .../src/ReactNativeFiberHostComponent.js | 1 - .../ResponderEventPlugin-test.internal.js | 1 - .../src/ReactFiberCacheComponent.js | 2 +- .../src/ReactFiberWorkLoop.js | 1 + .../src/ReactInternalTypes.js | 8 +- .../ReactFlightServerBundlerConfigCustom.js | 8 +- .../npm/umd/scheduler.development.js | 2 +- .../npm/umd/scheduler.production.min.js | 2 +- .../npm/umd/scheduler.profiling.min.js | 2 +- scripts/flow/react-devtools.js | 2 + scripts/rollup/validate/eslintrc.cjs.js | 2 +- scripts/rollup/validate/eslintrc.cjs2015.js | 2 +- scripts/rollup/validate/eslintrc.esm.js | 2 +- scripts/rollup/validate/eslintrc.fb.js | 2 +- scripts/rollup/validate/eslintrc.rn.js | 2 +- scripts/rollup/validate/eslintrc.umd.js | 2 +- yarn.lock | 472 ++++++++++-------- 36 files changed, 604 insertions(+), 260 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c14105b0c08a..b5205817a4c9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,15 +8,18 @@ const { const restrictedGlobals = require('confusing-browser-globals'); const OFF = 0; +const WARNING = 1; const ERROR = 2; module.exports = { - extends: ['fbjs', 'prettier'], + extends: ['prettier'], // Stop ESLint from looking for a configuration file in parent folders root: true, plugins: [ + 'babel', + 'ft-flow', 'jest', 'no-for-of-loops', 'no-function-declare-after-return', @@ -24,7 +27,7 @@ module.exports = { 'react-internal', ], - parser: 'babel-eslint', + parser: 'hermes-eslint', parserOptions: { ecmaVersion: 9, sourceType: 'script', @@ -33,6 +36,190 @@ module.exports = { // We're stricter than the default config, mostly. We'll override a few rules // and then enable some React specific ones. rules: { + 'ft-flow/array-style-complex-type': [OFF, 'verbose'], + 'ft-flow/array-style-simple-type': [OFF, 'verbose'], // TODO should be WARNING + 'ft-flow/boolean-style': ERROR, + 'ft-flow/no-dupe-keys': ERROR, + 'ft-flow/no-primitive-constructor-types': ERROR, + 'ft-flow/no-types-missing-file-annotation': OFF, // TODO should be ERROR + 'ft-flow/no-unused-expressions': ERROR, + // 'ft-flow/no-weak-types': WARNING, + // 'ft-flow/require-valid-file-annotation': ERROR, + + 'no-cond-assign': OFF, + 'no-constant-condition': OFF, + 'no-control-regex': OFF, + 'no-debugger': ERROR, + 'no-dupe-args': ERROR, + 'no-dupe-keys': ERROR, + 'no-duplicate-case': WARNING, + 'no-empty-character-class': WARNING, + 'no-empty': OFF, + 'no-ex-assign': WARNING, + 'no-extra-boolean-cast': WARNING, + 'no-func-assign': ERROR, + 'no-invalid-regexp': WARNING, + 'no-irregular-whitespace': WARNING, + 'no-negated-in-lhs': ERROR, + 'no-obj-calls': ERROR, + 'no-regex-spaces': WARNING, + 'no-sparse-arrays': ERROR, + 'no-unreachable': ERROR, + 'use-isnan': ERROR, + 'valid-jsdoc': OFF, + 'block-scoped-var': OFF, + complexity: OFF, + 'default-case': OFF, + 'guard-for-in': OFF, + 'no-alert': OFF, + 'no-caller': ERROR, + 'no-case-declarations': OFF, + 'no-div-regex': OFF, + 'no-else-return': OFF, + 'no-empty-pattern': WARNING, + 'no-eq-null': OFF, + 'no-eval': ERROR, + 'no-extend-native': WARNING, + 'no-extra-bind': WARNING, + 'no-fallthrough': WARNING, + 'no-implicit-coercion': OFF, + 'no-implied-eval': ERROR, + 'no-invalid-this': OFF, + 'no-iterator': OFF, + 'no-labels': [ERROR, {allowLoop: true, allowSwitch: true}], + 'no-lone-blocks': WARNING, + 'no-loop-func': OFF, + 'no-magic-numbers': OFF, + 'no-multi-str': ERROR, + 'no-native-reassign': [ERROR, {exceptions: ['Map', 'Set']}], + 'no-new-func': ERROR, + 'no-new': WARNING, + 'no-new-wrappers': WARNING, + 'no-octal-escape': WARNING, + 'no-octal': WARNING, + 'no-param-reassign': OFF, + 'no-process-env': OFF, + 'no-proto': ERROR, + 'no-redeclare': OFF, // TODO should be WARNING? + 'no-return-assign': OFF, + 'no-script-url': ERROR, + 'no-self-compare': WARNING, + 'no-sequences': WARNING, + 'no-throw-literal': ERROR, + 'no-useless-call': WARNING, + 'no-void': OFF, + 'no-warning-comments': OFF, + 'no-with': OFF, + radix: WARNING, + 'vars-on-top': OFF, + yoda: OFF, + 'init-declarations': OFF, + 'no-catch-shadow': ERROR, + 'no-delete-var': ERROR, + 'no-label-var': WARNING, + 'no-shadow-restricted-names': WARNING, + 'no-undef-init': OFF, + 'no-undef': ERROR, + 'no-undefined': OFF, + 'callback-return': OFF, + 'global-require': OFF, + 'handle-callback-err': OFF, + 'no-mixed-requires': OFF, + 'no-new-require': OFF, + 'no-path-concat': OFF, + 'no-process-exit': OFF, + 'no-restricted-modules': OFF, + 'no-sync': OFF, + camelcase: [OFF, {properties: 'always'}], + 'consistent-this': [OFF, 'self'], + 'func-names': OFF, + 'func-style': [OFF, 'declaration'], + 'id-length': OFF, + 'id-match': OFF, + 'max-depth': OFF, + 'max-nested-callbacks': OFF, + 'max-params': OFF, + 'max-statements': OFF, + 'new-cap': OFF, + 'newline-after-var': OFF, + 'no-array-constructor': ERROR, + 'no-continue': OFF, + 'no-inline-comments': OFF, + 'no-lonely-if': OFF, + 'no-negated-condition': OFF, + 'no-nested-ternary': OFF, + 'no-new-object': WARNING, + 'no-plusplus': OFF, + 'no-ternary': OFF, + 'no-underscore-dangle': OFF, + 'no-unneeded-ternary': WARNING, + 'one-var': [WARNING, {initialized: 'never'}], + 'operator-assignment': [WARNING, 'always'], + 'require-jsdoc': OFF, + 'sort-vars': OFF, + 'spaced-comment': [ + OFF, + 'always', + {exceptions: ['jshint', 'jslint', 'eslint', 'global']}, + ], + 'constructor-super': ERROR, + 'no-class-assign': WARNING, + 'no-const-assign': ERROR, + 'no-dupe-class-members': ERROR, + 'no-this-before-super': ERROR, + 'object-shorthand': OFF, + 'prefer-const': OFF, + 'prefer-spread': OFF, + 'prefer-reflect': OFF, + 'prefer-template': OFF, + 'require-yield': OFF, + 'babel/generator-star-spacing': OFF, + 'babel/new-cap': OFF, + 'babel/array-bracket-spacing': OFF, + 'babel/object-curly-spacing': OFF, + 'babel/object-shorthand': OFF, + 'babel/arrow-parens': OFF, + 'babel/no-await-in-loop': OFF, + 'babel/flow-object-type': OFF, + 'react/display-name': OFF, + 'react/forbid-prop-types': OFF, + 'react/jsx-closing-bracket-location': OFF, + 'react/jsx-curly-spacing': OFF, + 'react/jsx-equals-spacing': WARNING, + 'react/jsx-filename-extension': OFF, + 'react/jsx-first-prop-new-line': OFF, + 'react/jsx-handler-names': OFF, + 'react/jsx-indent': OFF, + 'react/jsx-indent-props': OFF, + 'react/jsx-key': OFF, + 'react/jsx-max-props-per-line': OFF, + 'react/jsx-no-bind': OFF, + 'react/jsx-no-duplicate-props': ERROR, + 'react/jsx-no-literals': OFF, + 'react/jsx-no-target-blank': OFF, + 'react/jsx-pascal-case': OFF, + 'react/jsx-sort-props': OFF, + 'react/jsx-uses-vars': ERROR, + 'react/no-comment-textnodes': OFF, + 'react/no-danger': OFF, + 'react/no-deprecated': OFF, + 'react/no-did-mount-set-state': OFF, + 'react/no-did-update-set-state': OFF, + 'react/no-direct-mutation-state': OFF, + 'react/no-multi-comp': OFF, + 'react/no-render-return-value': OFF, + 'react/no-set-state': OFF, + 'react/no-string-refs': OFF, + 'react/no-unknown-property': OFF, + 'react/prefer-es6-class': OFF, + 'react/prefer-stateless-function': OFF, + 'react/prop-types': OFF, + 'react/require-extension': OFF, + 'react/require-optimization': OFF, + 'react/require-render-return': OFF, + 'react/sort-comp': OFF, + 'react/sort-prop-types': OFF, + 'accessor-pairs': OFF, 'brace-style': [ERROR, '1tbs'], 'consistent-return': OFF, @@ -51,7 +238,6 @@ module.exports = { 'no-restricted-globals': [ERROR].concat(restrictedGlobals), 'no-restricted-syntax': [ERROR, 'WithStatement'], 'no-shadow': ERROR, - 'no-unused-expressions': ERROR, 'no-unused-vars': [ERROR, {args: 'none'}], 'no-use-before-define': OFF, 'no-useless-concat': OFF, @@ -74,8 +260,6 @@ module.exports = { // deal. But I turned it off because loading the plugin causes some obscure // syntax error and it didn't seem worth investigating. 'max-len': OFF, - // Prettier forces semicolons in a few places - 'flowtype/object-type-delimiter': OFF, // React & JSX // Our transforms set this automatically @@ -166,7 +350,7 @@ module.exports = { // We apply these settings to the source files that get compiled. // They can use all features including JSX (but shouldn't use `var`). files: esNextPaths, - parser: 'babel-eslint', + parser: 'hermes-eslint', parserOptions: { ecmaVersion: 8, sourceType: 'module', @@ -204,14 +388,6 @@ module.exports = { ERROR, {isProductionUserAppCode: false}, ], - - // Disable accessibility checks - 'jsx-a11y/aria-role': OFF, - 'jsx-a11y/no-noninteractive-element-interactions': OFF, - 'jsx-a11y/no-static-element-interactions': OFF, - 'jsx-a11y/role-has-required-aria-props': OFF, - 'jsx-a11y/no-noninteractive-tabindex': OFF, - 'jsx-a11y/tabindex-no-positive': OFF, }, }, { @@ -253,10 +429,69 @@ module.exports = { }, ], + env: { + browser: true, + es6: true, + node: true, + jest: true, + jasmine: true, + }, + globals: { + $Call: 'readonly', + $ElementType: 'readonly', + $Flow$ModuleRef: 'readonly', + $FlowFixMe: 'readonly', + $Keys: 'readonly', + $NonMaybeType: 'readonly', + $PropertyType: 'readonly', + $ReadOnly: 'readonly', + $ReadOnlyArray: 'readonly', + $Shape: 'readonly', + AnimationFrameID: 'readonly', + Class: 'readonly', + ClientRect: 'readonly', + CopyInspectedElementPath: 'readonly', + DOMHighResTimeStamp: 'readonly', + EventListener: 'readonly', + Iterable: 'readonly', + Iterator: 'readonly', + JSONValue: 'readonly', + JSResourceReference: 'readonly', + MouseEventHandler: 'readonly', + PropagationPhases: 'readonly', + PropertyDescriptor: 'readonly', + React$AbstractComponent: 'readonly', + React$Component: 'readonly', + React$ComponentType: 'readonly', + React$Config: 'readonly', + React$Context: 'readonly', + React$Element: 'readonly', + React$ElementConfig: 'readonly', + React$ElementProps: 'readonly', + React$ElementRef: 'readonly', + React$ElementType: 'readonly', + React$Key: 'readonly', + React$Node: 'readonly', + React$Portal: 'readonly', + React$Ref: 'readonly', + React$StatelessFunctionalComponent: 'readonly', + ReadableStreamController: 'readonly', + RequestInfo: 'readonly', + RequestOptions: 'readonly', + ResponseState: 'readonly', + StoreAsGlobal: 'readonly', + symbol: 'readonly', + SyntheticEvent: 'readonly', + SyntheticMouseEvent: 'readonly', + Thenable: 'readonly', + TimeoutID: 'readonly', + WheelEventHandler: 'readonly', + spyOnDev: 'readonly', spyOnDevAndProd: 'readonly', spyOnProd: 'readonly', + __DEV__: 'readonly', __EXPERIMENTAL__: 'readonly', __EXTENSION__: 'readonly', __PROFILE__: 'readonly', diff --git a/package.json b/package.json index 13bc6c18bdb5..82371df4e261 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "packages/*" ], "devDependencies": { + "@actuallyworks/node-fetch": "^2.6.0", "@babel/cli": "^7.10.5", "@babel/code-frame": "^7.10.4", "@babel/core": "^7.11.1", - "@babel/eslint-parser": "^7.11.4", "@babel/helper-module-imports": "^7.10.4", "@babel/parser": "^7.11.3", "@babel/plugin-external-helpers": "^7.10.4", @@ -38,7 +38,6 @@ "@babel/traverse": "^7.11.0", "abort-controller": "^3.0.0", "art": "0.10.1", - "babel-eslint": "^10.0.3", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", "chalk": "^3.0.0", "cli-table": "^0.3.1", @@ -50,13 +49,12 @@ "danger": "^9.2.10", "error-stack-parser": "^2.0.6", "eslint": "^7.7.0", - "eslint-config-fbjs": "^3.1.1", "eslint-config-prettier": "^6.9.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-eslint-plugin": "^3.5.3", "eslint-plugin-flowtype": "^2.25.0", + "eslint-plugin-ft-flow": "^2.0.3", "eslint-plugin-jest": "^22.15.0", - "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-no-for-of-loops": "^1.0.0", "eslint-plugin-no-function-declare-after-return": "^1.0.0", "eslint-plugin-react": "^6.7.1", @@ -68,6 +66,7 @@ "glob-stream": "^6.1.0", "google-closure-compiler": "^20200517.0.0", "gzip-size": "^5.1.1", + "hermes-eslint": "^0.9.0", "jasmine-check": "^1.0.0-rc.0", "jest": "^26.6.3", "jest-cli": "^26.6.3", @@ -77,7 +76,6 @@ "minimist": "^1.2.3", "mkdirp": "^0.5.1", "ncp": "^2.0.0", - "@actuallyworks/node-fetch": "^2.6.0", "pacote": "^10.3.0", "prettier": "1.19.1", "prop-types": "^15.6.2", diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 683d95dccf05..d01ee5f9573e 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -31,9 +31,11 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "devDependencies": { + "@babel/eslint-parser": "^7.11.4", "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0", - "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.0.0-0" + "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.0.0-0", + "babel-eslint": "^10.0.3" } } diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js index 493d2521ce5a..00c8bbcd670c 100644 --- a/packages/react-art/src/ReactARTHostConfig.js +++ b/packages/react-art/src/ReactARTHostConfig.js @@ -450,6 +450,7 @@ export function preparePortalMount(portalInstance: any): void { // noop } +// eslint-disable-next-line no-undef export function detachDeletedInstance(node: Instance): void { // noop } @@ -458,6 +459,7 @@ export function requestPostPaintCallback(callback: (time: number) => void) { // noop } +// eslint-disable-next-line no-undef export function prepareRendererToRender(container: Container): void { // noop } diff --git a/packages/react-client/src/forks/ReactFlightClientHostConfig.custom.js b/packages/react-client/src/forks/ReactFlightClientHostConfig.custom.js index c725d4d2f94f..76262bce219d 100644 --- a/packages/react-client/src/forks/ReactFlightClientHostConfig.custom.js +++ b/packages/react-client/src/forks/ReactFlightClientHostConfig.custom.js @@ -26,14 +26,14 @@ declare var $$$hostConfig: any; export type Response = any; -export opaque type BundlerConfig = mixed; // eslint-disable-line no-undef -export opaque type ModuleMetaData = mixed; // eslint-disable-line no-undef -export opaque type ModuleReference = mixed; // eslint-disable-line no-undef +export opaque type BundlerConfig = mixed; +export opaque type ModuleMetaData = mixed; +export opaque type ModuleReference = mixed; // eslint-disable-line no-unused-vars export const resolveModuleReference = $$$hostConfig.resolveModuleReference; export const preloadModule = $$$hostConfig.preloadModule; export const requireModule = $$$hostConfig.requireModule; -export opaque type Source = mixed; // eslint-disable-line no-undef +export opaque type Source = mixed; export type UninitializedModel = string; export const parseModel = $$$hostConfig.parseModel; diff --git a/packages/react-devtools-extensions/flow-typed/jest.js b/packages/react-devtools-extensions/flow-typed/jest.js index dba752e9ba30..3cad1447b12c 100644 --- a/packages/react-devtools-extensions/flow-typed/jest.js +++ b/packages/react-devtools-extensions/flow-typed/jest.js @@ -1,5 +1,16 @@ +/** + * 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 + */ + 'use strict'; +/* eslint-disable no-unused-vars */ + type JestMockFn, TReturn> = { (...args: TArguments): TReturn, /** @@ -693,13 +704,11 @@ interface JestExpectType { * specific arguments. */ toHaveBeenCalledWith(...args: Array): void; - toBeCalledWith(...args: Array): void; /** * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called * with specific arguments. */ toHaveBeenLastCalledWith(...args: Array): void; - lastCalledWith(...args: Array): void; /** * Check that an object has a .length property and it is set to a certain * numeric value. diff --git a/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js b/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js index cd2ae506d2ef..f706d830ebc0 100644 --- a/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js +++ b/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js @@ -6,6 +6,8 @@ 'use strict'; +/* eslint-disable no-unused-vars */ + type ReactComponentInstance = React$Component; type ReactTestRendererJSON = { @@ -77,7 +79,6 @@ declare module 'react-test-renderer/shallow' { static createRenderer(): ShallowRenderer; getMountedInstance(): ReactTestInstance; getRenderOutput>(): E; - getRenderOutput(): React$Element; render(element: React$Element, context?: any): void; unmount(): void; } diff --git a/packages/react-devtools-shared/src/__tests__/profilerChangeDescriptions-test.js b/packages/react-devtools-shared/src/__tests__/profilerChangeDescriptions-test.js index 72af30abd471..05966356e51a 100644 --- a/packages/react-devtools-shared/src/__tests__/profilerChangeDescriptions-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilerChangeDescriptions-test.js @@ -10,7 +10,7 @@ describe('Profiler change descriptions', () => { let React; let legacyRender; - let store: Store; + let store; let utils; beforeEach(() => { diff --git a/packages/react-devtools-shared/src/__tests__/profilingHostRoot-test.js b/packages/react-devtools-shared/src/__tests__/profilingHostRoot-test.js index a690aa7fe705..887e7e7911d0 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingHostRoot-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingHostRoot-test.js @@ -12,7 +12,7 @@ describe('profiling HostRoot', () => { let ReactDOMClient; let Scheduler; let legacyRender; - let store: Store; + let store; let utils; let getEffectDurations; let effectDurations; diff --git a/packages/react-devtools-shell/src/e2e/devtools.js b/packages/react-devtools-shell/src/e2e/devtools.js index 3b4ac2b99282..2ee103e5ff2a 100644 --- a/packages/react-devtools-shell/src/e2e/devtools.js +++ b/packages/react-devtools-shell/src/e2e/devtools.js @@ -1,3 +1,12 @@ +/** + * 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. + * + * @noflow + */ + import * as React from 'react'; import * as ReactDOM from 'react-dom'; import {createRoot} from 'react-dom/client'; diff --git a/packages/react-devtools-shell/src/multi/devtools.js b/packages/react-devtools-shell/src/multi/devtools.js index a00e19418bf3..0ba45bc6c15f 100644 --- a/packages/react-devtools-shell/src/multi/devtools.js +++ b/packages/react-devtools-shell/src/multi/devtools.js @@ -1,3 +1,12 @@ +/** + * 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. + * + * @noflow + */ + import * as React from 'react'; import {createRoot} from 'react-dom/client'; import { diff --git a/packages/react-devtools-shell/src/multi/left.js b/packages/react-devtools-shell/src/multi/left.js index aaf380f39a6d..b195361f959f 100644 --- a/packages/react-devtools-shell/src/multi/left.js +++ b/packages/react-devtools-shell/src/multi/left.js @@ -1,3 +1,10 @@ +/** + * 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. + */ + import * as React from 'react'; import {useState} from 'react'; import {createRoot} from 'react-dom'; diff --git a/packages/react-devtools-shell/src/multi/right.js b/packages/react-devtools-shell/src/multi/right.js index f4d08632e75f..1c53c80007b0 100644 --- a/packages/react-devtools-shell/src/multi/right.js +++ b/packages/react-devtools-shell/src/multi/right.js @@ -1,3 +1,10 @@ +/** + * 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. + */ + import * as React from 'react'; import {useLayoutEffect, useRef, useState} from 'react'; import {render} from 'react-dom'; diff --git a/packages/react-devtools-timeline/src/import-worker/importFile.js b/packages/react-devtools-timeline/src/import-worker/importFile.js index 78a968465bd1..c57f436e6fde 100644 --- a/packages/react-devtools-timeline/src/import-worker/importFile.js +++ b/packages/react-devtools-timeline/src/import-worker/importFile.js @@ -16,8 +16,6 @@ import preprocessData from './preprocessData'; import {readInputData} from './readInputData'; import InvalidProfileError from './InvalidProfileError'; -declare var self: DedicatedWorkerGlobalScope; - export async function importFile(file: File): Promise { try { const readFile = await readInputData(file); diff --git a/packages/react-dom-bindings/src/client/ReactDOMSelection.js b/packages/react-dom-bindings/src/client/ReactDOMSelection.js index c9c48ad0822a..214f50f1280c 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMSelection.js +++ b/packages/react-dom-bindings/src/client/ReactDOMSelection.js @@ -31,10 +31,10 @@ export function getOffsets(outerNode) { // catch any error that may otherwise arise. See // https://bugzilla.mozilla.org/show_bug.cgi?id=208427 try { - /* eslint-disable no-unused-expressions */ + /* eslint-disable ft-flow/no-unused-expressions */ anchorNode.nodeType; focusNode.nodeType; - /* eslint-enable no-unused-expressions */ + /* eslint-enable ft-flow/no-unused-expressions */ } catch (e) { return null; } diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index 838d2c3d52c0..94e15d281127 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -390,7 +390,7 @@ function executeDispatchesInOrder(event) { * @param {?object} event Synthetic event to be dispatched. * @private */ -const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) { +const executeDispatchesAndRelease = function(event /* ReactSyntheticEvent */) { if (event) { executeDispatchesInOrder(event); @@ -470,7 +470,7 @@ function shouldPreventMouseEvent(name, type, props) { * @param {string} registrationName Name of listener (e.g. `onClick`). * @return {?function} The stored callback. */ -function getListener(inst: Fiber, registrationName: string) { +function getListener(inst /* Fiber */, registrationName: string) { // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not // live here; needs to be moved to a better place soon const stateNode = inst.stateNode; diff --git a/packages/react-native-renderer/fabric.js b/packages/react-native-renderer/fabric.js index 60d4f289981e..1bf6c861c1f6 100644 --- a/packages/react-native-renderer/fabric.js +++ b/packages/react-native-renderer/fabric.js @@ -10,7 +10,6 @@ import type {ReactFabricType} from './src/ReactNativeTypes'; import * as ReactFabric from './src/ReactFabric'; // Assert that the exports line up with the type we're going to expose. -// eslint-disable-next-line no-unused-expressions (ReactFabric: ReactFabricType); export * from './src/ReactFabric'; diff --git a/packages/react-native-renderer/index.js b/packages/react-native-renderer/index.js index d7660c81cea6..da522f358f21 100644 --- a/packages/react-native-renderer/index.js +++ b/packages/react-native-renderer/index.js @@ -10,7 +10,7 @@ import type {ReactNativeType} from './src/ReactNativeTypes'; import * as ReactNative from './src/ReactNativeRenderer'; // Assert that the exports line up with the type we're going to expose. -// eslint-disable-next-line no-unused-expressions +// eslint-disable-next-line ft-flow/no-unused-expressions (ReactNative: ReactNativeType); export * from './src/ReactNativeRenderer'; diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index b6d370882ae4..ec6918e96d34 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -321,7 +321,6 @@ class ReactFabricHostComponent { } } -// eslint-disable-next-line no-unused-expressions // $FlowFixMe[class-object-subtyping] found when upgrading Flow // $FlowFixMe[method-unbinding] found when upgrading Flow (ReactFabricHostComponent.prototype: $ReadOnly<{...NativeMethods, ...}>); diff --git a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js index be78b3bae2b9..525d10a76150 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js +++ b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js @@ -126,7 +126,6 @@ class ReactNativeFiberHostComponent { } } -// eslint-disable-next-line no-unused-expressions // $FlowFixMe[class-object-subtyping] found when upgrading Flow // $FlowFixMe[method-unbinding] found when upgrading Flow (ReactNativeFiberHostComponent.prototype: $ReadOnly<{...NativeMethods, ...}>); diff --git a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js index a4bd4aa0883f..b93b526f50d4 100644 --- a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js @@ -1402,7 +1402,6 @@ describe('ResponderEventPlugin', () => { class ParentComponent extends React.Component { pRef = React.createRef(); p_P1Ref = React.createRef(); - p_P1Ref = React.createRef(); p_P1_C1Ref = React.createRef(); p_P1_C2Ref = React.createRef(); p_OneOffRef = React.createRef(); diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.js b/packages/react-reconciler/src/ReactFiberCacheComponent.js index 7a898cf738d8..90a1e698a6ad 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.js @@ -38,7 +38,7 @@ const AbortControllerLocal: typeof AbortController = enableCache : (null: any); export type Cache = { - controller: AbortControllerLocal, + controller: AbortController, data: Map<() => mixed, mixed>, refCount: number, }; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 0adee6a3b333..edcf20fc4379 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -1612,6 +1612,7 @@ export function discreteUpdates( // Overload the definition to the two valid signatures. // Warning, this opts-out of checking the function body. +// eslint-disable-next-line no-unused-vars declare function flushSync(fn: () => R): R; // eslint-disable-next-line no-redeclare declare function flushSync(void): void; diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index f2a1eed3780b..edc120182e95 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -403,10 +403,10 @@ export type Dispatcher = { boolean, (callback: () => void, options?: StartTransitionOptions) => void, ], - useMutableSource( - source: MutableSource, - getSnapshot: MutableSourceGetSnapshotFn, - subscribe: MutableSourceSubscribeFn, + useMutableSource( + source: MutableSource, + getSnapshot: MutableSourceGetSnapshotFn, + subscribe: MutableSourceSubscribeFn, ): Snapshot, useSyncExternalStore( subscribe: (() => void) => () => void, diff --git a/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js b/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js index fa0815cca2f2..7024968c16d9 100644 --- a/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js +++ b/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js @@ -9,10 +9,10 @@ declare var $$$hostConfig: any; -export opaque type BundlerConfig = mixed; // eslint-disable-line no-undef -export opaque type ModuleReference = mixed; // eslint-disable-line no-undef -export opaque type ModuleMetaData: any = mixed; // eslint-disable-line no-undef -export opaque type ModuleKey: any = mixed; // eslint-disable-line no-undef +export opaque type BundlerConfig = mixed; +export opaque type ModuleReference = mixed; // eslint-disable-line no-unused-vars +export opaque type ModuleMetaData: any = mixed; +export opaque type ModuleKey: any = mixed; export const isModuleReference = $$$hostConfig.isModuleReference; export const getModuleKey = $$$hostConfig.getModuleKey; export const resolveModuleMetaData = $$$hostConfig.resolveModuleMetaData; diff --git a/packages/scheduler/npm/umd/scheduler.development.js b/packages/scheduler/npm/umd/scheduler.development.js index 2a060933a1ac..6a21525e83a0 100644 --- a/packages/scheduler/npm/umd/scheduler.development.js +++ b/packages/scheduler/npm/umd/scheduler.development.js @@ -12,7 +12,7 @@ 'use strict'; (function(global, factory) { - // eslint-disable-next-line no-unused-expressions + // eslint-disable-next-line ft-flow/no-unused-expressions typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory(require('react'))) : typeof define === 'function' && define.amd // eslint-disable-line no-undef diff --git a/packages/scheduler/npm/umd/scheduler.production.min.js b/packages/scheduler/npm/umd/scheduler.production.min.js index 083586fa186a..20aba28ed765 100644 --- a/packages/scheduler/npm/umd/scheduler.production.min.js +++ b/packages/scheduler/npm/umd/scheduler.production.min.js @@ -12,7 +12,7 @@ 'use strict'; (function(global, factory) { - // eslint-disable-next-line no-unused-expressions + // eslint-disable-next-line ft-flow/no-unused-expressions typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory(require('react'))) : typeof define === 'function' && define.amd // eslint-disable-line no-undef diff --git a/packages/scheduler/npm/umd/scheduler.profiling.min.js b/packages/scheduler/npm/umd/scheduler.profiling.min.js index 083586fa186a..20aba28ed765 100644 --- a/packages/scheduler/npm/umd/scheduler.profiling.min.js +++ b/packages/scheduler/npm/umd/scheduler.profiling.min.js @@ -12,7 +12,7 @@ 'use strict'; (function(global, factory) { - // eslint-disable-next-line no-unused-expressions + // eslint-disable-next-line ft-flow/no-unused-expressions typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory(require('react'))) : typeof define === 'function' && define.amd // eslint-disable-line no-undef diff --git a/scripts/flow/react-devtools.js b/scripts/flow/react-devtools.js index 164619cadcf7..86968330ec31 100644 --- a/scripts/flow/react-devtools.js +++ b/scripts/flow/react-devtools.js @@ -7,6 +7,8 @@ * @flow */ +/* eslint-disable no-unused-vars */ + declare var __EXTENSION__: boolean; declare var __TEST__: boolean; diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index 74fafe6159ec..67d3c06dfc84 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -64,5 +64,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 531d5923a37e..f276094c6495 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -64,5 +64,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index a8fd18cb304b..919751720460 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -63,5 +63,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 0e10648bda04..7019e22435e1 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -59,5 +59,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index f87cac7af86c..524e187c9b3c 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -55,5 +55,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index c9e912aec88d..09554df63b07 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -67,5 +67,5 @@ module.exports = { // These plugins aren't used, but eslint complains if an eslint-ignore comment // references unused plugins. An alternate approach could be to strip // eslint-ignore comments as part of the build. - plugins: ['jest', 'no-for-of-loops', 'react', 'react-internal'], + plugins: ['ft-flow', 'jest', 'no-for-of-loops', 'react', 'react-internal'], }; diff --git a/yarn.lock b/yarn.lock index 4758cf07363f..cda67855e9f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,13 @@ optionalDependencies: chokidar "^2.1.8" +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" @@ -670,6 +677,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" @@ -726,6 +738,15 @@ "@babel/traverse" "^7.9.0" "@babel/types" "^7.9.0" +"@babel/highlight@^7.10.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/highlight@^7.12.13", "@babel/highlight@^7.8.3": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" @@ -1783,14 +1804,6 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/runtime-corejs3@^7.10.2": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.0.tgz#e74206f51ebd69a787a6be762e26593dc3e97756" - integrity sha512-CltlJftStV4CBG4/HWVVRnBWFvLkYd22BkYO4gFgX+89JOlYiKQ5+Ji9Ovqb1o3T5DkkBn3JrVq1V/xweAaeGA== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - "@babel/runtime@7.10.2": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" @@ -1805,13 +1818,6 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.10.2": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.0.tgz#98bd7666186969c04be893d747cf4a6c6c8fa6b0" - integrity sha512-lS4QLXQ2Vbw2ubfQjeQcn+BZgZ5+ROHW9f+DWjEp5Y+NHYmkRGKqHSJ1tuhbUauKu2nhZNTBIvsIQ8dXfY5Gjw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" @@ -2026,6 +2032,35 @@ opn "5.3.0" react "^16.13.1" +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" @@ -3143,6 +3178,11 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.2.0: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" @@ -3321,7 +3361,7 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.5: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3341,6 +3381,16 @@ ajv@^6.5.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.11.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" + integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -3368,9 +3418,9 @@ ansi-colors@^3.0.0: integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-cyan@^0.1.1: version "0.1.1" @@ -3565,14 +3615,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== - dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" - arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -3633,15 +3675,6 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" - integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0" - is-string "^1.0.5" - array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -3741,16 +3774,16 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types-flow@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -3827,16 +3860,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -axe-core@^3.5.4: - version "3.5.5" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" - integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== - -axobject-query@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" - integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== - babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -5498,11 +5521,6 @@ core-js-compat@^3.6.2: browserslist "^4.8.3" semver "7.0.0" -core-js-pure@^3.0.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" - integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== - core-js@3.6.4, core-js@^3.6.4: version "3.6.4" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" @@ -5872,11 +5890,6 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -damerau-levenshtein@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" - integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== - danger@^9.2.10: version "9.2.10" resolved "https://registry.yarnpkg.com/danger/-/danger-9.2.10.tgz#7b4e34ca805dd39bbbc0ca2da9025d63f4d36a85" @@ -6526,11 +6539,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.0.0: - version "9.1.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.1.1.tgz#1d5ffce26d8191e6c3f3a9d27987b1c5bba7d20a" - integrity sha512-AaWyDiNO9rbtMIcGl7tdxMcNu8SOLaDLxmQEFT5JhgKufOJzPPkYmgN2QwqTgw4doWMZZQttC6sUWVQjb+1VdA== - emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" @@ -6661,23 +6669,6 @@ es-abstract@^1.13.0: is-regex "^1.0.4" object-keys "^1.0.12" -es-abstract@^1.17.0: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: version "1.17.6" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" @@ -6717,24 +6708,6 @@ es-abstract@^1.17.2, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-abstract@^1.18.0-next.0: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - es-to-primitive@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" @@ -6832,11 +6805,6 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-fbjs@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-fbjs/-/eslint-config-fbjs-3.1.1.tgz#f5b4c1df888693672116f000527a8df25d61b888" - integrity sha512-Vpyqz+ZcyLyiUGUdUfiQmZnxiQ4Nj/KDRKed/Y5qSm+xHbQJ5zcZUQwLUMWHQicpDHewsdXwlpUAblvy1DtGvg== - eslint-config-prettier@^6.9.0: version "6.9.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz#430d24822e82f7deb1e22a435bfa3999fae4ad64" @@ -6865,6 +6833,14 @@ eslint-plugin-flowtype@^2.25.0: dependencies: lodash "^4.17.10" +eslint-plugin-ft-flow@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz#3b3c113c41902bcbacf0e22b536debcfc3c819e8" + integrity sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg== + dependencies: + lodash "^4.17.21" + string-natural-compare "^3.0.1" + eslint-plugin-jest@^22.15.0: version "22.15.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.15.0.tgz#fe70bfff7eeb47ca0ab229588a867f82bb8592c5" @@ -6872,23 +6848,6 @@ eslint-plugin-jest@^22.15.0: dependencies: "@typescript-eslint/experimental-utils" "^1.13.0" -eslint-plugin-jsx-a11y@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" - integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== - dependencies: - "@babel/runtime" "^7.10.2" - aria-query "^4.2.2" - array-includes "^3.1.1" - ast-types-flow "^0.0.7" - axe-core "^3.5.4" - axobject-query "^2.1.2" - damerau-levenshtein "^1.0.6" - emoji-regex "^9.0.0" - has "^1.0.3" - jsx-ast-utils "^2.4.1" - language-tags "^1.0.5" - eslint-plugin-no-for-of-loops@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-no-for-of-loops/-/eslint-plugin-no-for-of-loops-1.0.1.tgz#2ee3732d50c642b8d1bfacedbfe3b28f86222369" @@ -6906,6 +6865,7 @@ eslint-plugin-no-unsanitized@3.1.2: "eslint-plugin-react-internal@link:./scripts/eslint-rules": version "0.0.0" + uid "" eslint-plugin-react@^6.7.1: version "6.10.3" @@ -6931,7 +6891,7 @@ eslint-scope@3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@5.1.0, eslint-scope@^5.0.0, eslint-scope@^5.1.0: +eslint-scope@5.1.0, eslint-scope@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== @@ -6947,6 +6907,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -7029,27 +6997,31 @@ eslint@5.16.0: text-table "^0.2.0" eslint@^7.7.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.7.0.tgz#18beba51411927c4b64da0a8ceadefe4030d6073" - integrity sha512-1KUxLzos0ZVsyL81PnRN335nDtQ8/vZUD6uMtWbF+5zDtjKcsklIi78XoE0MVL93QvWTu+E5y44VyyCsOMBrIg== + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" - eslint-scope "^5.1.0" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" eslint-utils "^2.1.0" - eslint-visitor-keys "^1.3.0" - espree "^7.2.0" - esquery "^1.2.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -7057,7 +7029,7 @@ eslint@^7.7.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash.merge "^4.6.2" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -7066,7 +7038,7 @@ eslint@^7.7.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" @@ -7088,13 +7060,13 @@ espree@^5.0.1: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" -espree@^7.2.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" - acorn-jsx "^5.2.0" + acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" esprima@4.0.1, esprima@^4.0.0, esprima@^4.0.1: @@ -7109,10 +7081,10 @@ esquery@^1.0.1: dependencies: estraverse "^4.0.0" -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -7123,6 +7095,13 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -7495,7 +7474,7 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -7671,6 +7650,13 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + file-loader@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" @@ -7906,6 +7892,14 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" @@ -7916,6 +7910,11 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + flow-bin@^0.190.0: version "0.190.0" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.190.0.tgz#cfc50e1474facf8150232a6c498fe66a6bb75969" @@ -8258,13 +8257,20 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: is-glob "^4.0.1" +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-stream@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" @@ -8382,12 +8388,12 @@ globals@^11.1.0, globals@^11.7.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0, globals@^13.9.0: + version "13.19.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" + integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" globalthis@^1.0.1: version "1.0.1" @@ -8729,6 +8735,27 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hermes-eslint@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.9.0.tgz#f1423abe3dfd959257430d61a9bccd4700b59e09" + integrity sha512-rlkK51UpGwo0ZWg8hu8DVICth7RfGSvaEJzFflos8bDOYm/d842/J3IXi0lB9R9waOp4VGGSc8VDmh+a9p2Q2w== + dependencies: + esrecurse "^4.3.0" + hermes-estree "0.9.0" + hermes-parser "0.9.0" + +hermes-estree@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.9.0.tgz#026e0abe6db1dcf50a81a79014b779a83db3b814" + integrity sha512-5DZ7Y0CbHVk8zPqgRCvqp8iw+P05svnQDI1aJFjdqCfXJ/1CZ+8aYpGlhJ29zCG5SE5duGTzSxogAYYI4QqXqw== + +hermes-parser@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.9.0.tgz#ede3044d50479c61843cef5bbdcea83933d4e4ec" + integrity sha512-IcvJIlAn+9tpHkP+HTsxWKrIdQPp0gvGrrQmxlL4XnNS+Oh6R/Fpxbcoflm2kY3zgQjEvxZxLiK/2+k3/5wsrw== + dependencies: + hermes-estree "0.9.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -9126,6 +9153,14 @@ import-fresh@3.2.1, import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -9389,11 +9424,6 @@ is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== -is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== - is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" @@ -9597,11 +9627,6 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= - is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -9738,7 +9763,7 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" -is-regex@^1.1.0, is-regex@^1.1.1: +is-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== @@ -10564,6 +10589,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -10665,14 +10695,6 @@ jsx-ast-utils@^1.3.4: resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" integrity sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE= -jsx-ast-utils@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" - integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== - dependencies: - array-includes "^3.1.1" - object.assign "^4.1.0" - jszip@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.7.0.tgz#c420b1e1aa800490724a0dd277e8cca950bc2c41" @@ -10768,18 +10790,6 @@ ky@^0.12.0: resolved "https://registry.yarnpkg.com/ky/-/ky-0.12.0.tgz#c05be95e6745ba422a6d2cc8ae964164962279f9" integrity sha512-t9b7v3V2fGwAcQnnDDQwKQGF55eWrf4pwi1RN08Fy8b/9GEwV7Ea0xQiaSW6ZbeghBHIwl8kgnla4vVo9seepQ== -language-subtag-registry@~0.3.2: - version "0.3.20" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755" - integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg== - -language-tags@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= - dependencies: - language-subtag-registry "~0.3.2" - latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -11021,6 +11031,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.omitby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" @@ -11046,6 +11061,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" @@ -11071,7 +11091,7 @@ lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -lodash@^4.7.0: +lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -12141,7 +12161,7 @@ object-inspect@^1.10.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0, object-inspect@^1.8.0: +object-inspect@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== @@ -12168,16 +12188,6 @@ object.assign@^4.0.4, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" - has-symbols "^1.0.1" - object-keys "^1.1.1" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -13872,9 +13882,9 @@ regexpp@^2.0.1: integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^1.0.0: version "1.0.0" @@ -14536,7 +14546,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.2, semver@^7.2.1, semver@^7.3.2: +semver@7.3.2, semver@^7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== @@ -14551,6 +14561,13 @@ semver@^7.0.0, semver@^7.1.1: resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A== +semver@^7.2.1: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -14765,6 +14782,15 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + smart-buffer@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" @@ -15227,6 +15253,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -15262,6 +15293,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string.prototype.trimend@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" @@ -15341,6 +15381,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom-buf@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-2.0.0.tgz#ff9c223937f8e7154b77e9de9bde094186885c15" @@ -15407,7 +15454,7 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-json-comments@^3.1.0: +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -15540,6 +15587,17 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" +table@^6.0.9: + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -16022,6 +16080,11 @@ type-fest@^0.13.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -16423,7 +16486,12 @@ v8-compile-cache@2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-compile-cache@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== From 5fcf1a4b4c2150a1b9fe0de0144a82a053c63966 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 21 Dec 2022 23:55:49 -0500 Subject: [PATCH 010/269] Bugfix: Synchronous ping during render phase sometimes unwinds the stack, leading to crash (#25851) I found this bug when working on a different task. `pingSuspendedRoot` sometimes calls `prepareFreshStack` to interupt the work-in-progress tree and force a restart from the root. The idea is that if the current render is already in a state where it be blocked from committing, and there's new data that could unblock it, we might as well restart from the beginning. The problem is that this is only safe to do if `pingSuspendedRoot` is called from a non-React task, like an event handler or a microtask. While this is usually the case, it's entirely possible for a thenable to resolve (i.e. to call `pingSuspendedRoot`) synchronously while the render phase is already executing. If that happens, and work loop attempts to unwind the stack, it causes the render phase to crash. --- .../src/ReactFiberWorkLoop.js | 12 ++- .../ReactSuspenseWithNoopRenderer-test.js | 76 +++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index edcf20fc4379..7d5a38ad393d 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -3401,8 +3401,16 @@ function pingSuspendedRoot( includesOnlyRetries(workInProgressRootRenderLanes) && now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) ) { - // Restart from the root. - prepareFreshStack(root, NoLanes); + // Force a restart from the root by unwinding the stack. Unless this is + // being called from the render phase, because that would cause a crash. + if ((executionContext & RenderContext) === NoContext) { + prepareFreshStack(root, NoLanes); + } else { + // TODO: If this does happen during the render phase, we should throw + // the special internal exception that we use to interrupt the stack for + // selective hydration. That was temporarily reverted but we once we add + // it back we can use it here. + } } else { // Even though we can't restart right now, we might get an // opportunity later. So we mark this render as having a ping. diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index 5b4c9345443b..bb4ea103ff12 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -4218,4 +4218,80 @@ describe('ReactSuspenseWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['Unmount Child']); }); + + // @gate enableLegacyCache + it( + 'regression test: pinging synchronously within the render phase ' + + 'does not unwind the stack', + async () => { + // This is a regression test that reproduces a very specific scenario that + // used to cause a crash. + const thenable = { + then(resolve) { + resolve('hi'); + }, + status: 'pending', + }; + + function ImmediatelyPings() { + if (thenable.status === 'pending') { + thenable.status = 'fulfilled'; + throw thenable; + } + return ; + } + + function App({showMore}) { + return ( +
+ }> + {showMore ? ( + <> + + + ) : null} + + {showMore ? ( + + + + ) : null} +
+ ); + } + + // Initial render. This mounts a Suspense boundary, so that in the next + // update we can trigger a "suspend with delay" scenario. + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + expect(Scheduler).toHaveYielded([]); + expect(root).toMatchRenderedOutput(
); + + // Update. This will cause two separate trees to suspend. The first tree + // will be inside an already mounted Suspense boundary, so it will trigger + // a "suspend with delay". The second tree will be a new Suspense + // boundary, but the thenable that is thrown will immediately call its + // ping listener. + // + // Before the bug was fixed, this would lead to a `prepareFreshStack` call + // that unwinds the work-in-progress stack. When that code was written, it + // was expected that pings always happen from an asynchronous task (or + // microtask). But this test shows an example where that's not the case. + // + // The fix was to check if we're in the render phase before calling + // `prepareFreshStack`. + await act(async () => { + root.render(); + }); + expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...', 'Hi']); + expect(root).toMatchRenderedOutput( +
+ + +
, + ); + }, + ); }); From 726a40eded10172e94678c93f6c24901201de4c3 Mon Sep 17 00:00:00 2001 From: KZ-Lucas Date: Fri, 23 Dec 2022 20:43:06 +0900 Subject: [PATCH 011/269] CHANGELOG.md Change parentheses position (#25762) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f180bf6be8ca..57d319384e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ ### Server Components (Experimental) -* Add support for `useId()` inside Server Components. ([@gnoff](https://github.com/gnoff)) in [#24172](https://github.com/facebook/react/pull/24172) +* Add support for `useId()` inside Server Components. ([@gnoff](https://github.com/gnoff) in [#24172](https://github.com/facebook/react/pull/24172)) ## 18.1.0 (April 26, 2022) From 81d4ee9ca5c405dce62f64e61506b8e155f38d8d Mon Sep 17 00:00:00 2001 From: satelllte Date: Fri, 23 Dec 2022 14:22:55 +0200 Subject: [PATCH 012/269] reconciler docs: fix small typo - "mode" (instead of "node") (#25863) --- packages/react-reconciler/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md index 9a3fa5f6485c..2aeb36fbd6ca 100644 --- a/packages/react-reconciler/README.md +++ b/packages/react-reconciler/README.md @@ -81,7 +81,7 @@ const HostConfig = { } ``` -If your target platform has immutable trees, you'll want the **persistent mode** instead. In that mode, existing nodes are never mutated, and instead every change clones the parent tree and then replaces the whole parent tree at the root. This is the node used by the new React Native renderer, codenamed "Fabric". +If your target platform has immutable trees, you'll want the **persistent mode** instead. In that mode, existing nodes are never mutated, and instead every change clones the parent tree and then replaces the whole parent tree at the root. This is the mode used by the new React Native renderer, codenamed "Fabric". ```js const HostConfig = { From de7d1c90718ea8f4844a2219991f7115ef2bd2c5 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 23 Dec 2022 14:31:27 -0500 Subject: [PATCH 013/269] Add `fetchPriority` to `` and `` (#25927) ## Summary - Fixes https://github.com/facebook/react/issues/25682 ## How did you test this change? I tried this but it didn't work ``` yarn build --type=UMD_DEV react/index,react-dom && cd fixtures/attribute-behavior && yarn install && yarn start ``` Co-authored-by: eps1lon --- .../AttributeTableSnapshot.md | 75 +++++++++++++++++++ fixtures/attribute-behavior/src/attributes.js | 18 +++++ .../src/shared/possibleStandardNames.js | 1 + 3 files changed, 94 insertions(+) diff --git a/fixtures/attribute-behavior/AttributeTableSnapshot.md b/fixtures/attribute-behavior/AttributeTableSnapshot.md index 52a6bc31ab31..93ae5b010bc9 100644 --- a/fixtures/attribute-behavior/AttributeTableSnapshot.md +++ b/fixtures/attribute-behavior/AttributeTableSnapshot.md @@ -3348,6 +3348,81 @@ | `externalResourcesRequired=(null)`| (initial)| `` | | `externalResourcesRequired=(undefined)`| (initial)| `` | +## `fetchPriority` (on `` inside `
`) +| Test Case | Flags | Result | +| --- | --- | --- | +| `fetchPriority=(string)`| (changed)| `"high"` | +| `fetchPriority=(empty string)`| (initial)| `"auto"` | +| `fetchPriority=(array with string)`| (changed)| `"high"` | +| `fetchPriority=(empty array)`| (initial)| `"auto"` | +| `fetchPriority=(object)`| (initial)| `"auto"` | +| `fetchPriority=(numeric string)`| (initial)| `"auto"` | +| `fetchPriority=(-1)`| (initial)| `"auto"` | +| `fetchPriority=(0)`| (initial)| `"auto"` | +| `fetchPriority=(integer)`| (initial)| `"auto"` | +| `fetchPriority=(NaN)`| (initial, warning)| `"auto"` | +| `fetchPriority=(float)`| (initial)| `"auto"` | +| `fetchPriority=(true)`| (initial, warning)| `"auto"` | +| `fetchPriority=(false)`| (initial, warning)| `"auto"` | +| `fetchPriority=(string 'true')`| (initial)| `"auto"` | +| `fetchPriority=(string 'false')`| (initial)| `"auto"` | +| `fetchPriority=(string 'on')`| (initial)| `"auto"` | +| `fetchPriority=(string 'off')`| (initial)| `"auto"` | +| `fetchPriority=(symbol)`| (initial, warning)| `"auto"` | +| `fetchPriority=(function)`| (initial, warning)| `"auto"` | +| `fetchPriority=(null)`| (initial)| `"auto"` | +| `fetchPriority=(undefined)`| (initial)| `"auto"` | + +## `fetchpriority` (on `` inside `
`) +| Test Case | Flags | Result | +| --- | --- | --- | +| `fetchpriority=(string)`| (changed, warning)| `"high"` | +| `fetchpriority=(empty string)`| (initial, warning)| `"auto"` | +| `fetchpriority=(array with string)`| (changed, warning)| `"high"` | +| `fetchpriority=(empty array)`| (initial, warning)| `"auto"` | +| `fetchpriority=(object)`| (initial, warning)| `"auto"` | +| `fetchpriority=(numeric string)`| (initial, warning)| `"auto"` | +| `fetchpriority=(-1)`| (initial, warning)| `"auto"` | +| `fetchpriority=(0)`| (initial, warning)| `"auto"` | +| `fetchpriority=(integer)`| (initial, warning)| `"auto"` | +| `fetchpriority=(NaN)`| (initial, warning)| `"auto"` | +| `fetchpriority=(float)`| (initial, warning)| `"auto"` | +| `fetchpriority=(true)`| (initial, warning)| `"auto"` | +| `fetchpriority=(false)`| (initial, warning)| `"auto"` | +| `fetchpriority=(string 'true')`| (initial, warning)| `"auto"` | +| `fetchpriority=(string 'false')`| (initial, warning)| `"auto"` | +| `fetchpriority=(string 'on')`| (initial, warning)| `"auto"` | +| `fetchpriority=(string 'off')`| (initial, warning)| `"auto"` | +| `fetchpriority=(symbol)`| (initial, warning)| `"auto"` | +| `fetchpriority=(function)`| (initial, warning)| `"auto"` | +| `fetchpriority=(null)`| (initial, warning)| `"auto"` | +| `fetchpriority=(undefined)`| (initial, warning)| `"auto"` | + +## `fetchPriority` (on `` inside `
`) +| Test Case | Flags | Result | +| --- | --- | --- | +| `fetchPriority=(string)`| (changed)| `"high"` | +| `fetchPriority=(empty string)`| (initial)| `"auto"` | +| `fetchPriority=(array with string)`| (changed)| `"high"` | +| `fetchPriority=(empty array)`| (initial)| `"auto"` | +| `fetchPriority=(object)`| (initial)| `"auto"` | +| `fetchPriority=(numeric string)`| (initial)| `"auto"` | +| `fetchPriority=(-1)`| (initial)| `"auto"` | +| `fetchPriority=(0)`| (initial)| `"auto"` | +| `fetchPriority=(integer)`| (initial)| `"auto"` | +| `fetchPriority=(NaN)`| (initial, warning)| `"auto"` | +| `fetchPriority=(float)`| (initial)| `"auto"` | +| `fetchPriority=(true)`| (initial, warning)| `"auto"` | +| `fetchPriority=(false)`| (initial, warning)| `"auto"` | +| `fetchPriority=(string 'true')`| (initial)| `"auto"` | +| `fetchPriority=(string 'false')`| (initial)| `"auto"` | +| `fetchPriority=(string 'on')`| (initial)| `"auto"` | +| `fetchPriority=(string 'off')`| (initial)| `"auto"` | +| `fetchPriority=(symbol)`| (initial, warning)| `"auto"` | +| `fetchPriority=(function)`| (initial, warning)| `"auto"` | +| `fetchPriority=(null)`| (initial)| `"auto"` | +| `fetchPriority=(undefined)`| (initial)| `"auto"` | + ## `fill` (on `` inside ``) | Test Case | Flags | Result | | --- | --- | --- | diff --git a/fixtures/attribute-behavior/src/attributes.js b/fixtures/attribute-behavior/src/attributes.js index 6fc61700d3a4..b84a0d11da3b 100644 --- a/fixtures/attribute-behavior/src/attributes.js +++ b/fixtures/attribute-behavior/src/attributes.js @@ -573,6 +573,24 @@ const attributes = [ tagName: 'path', read: getSVGAttribute('externalResourcesRequired'), }, + { + name: 'fetchPriority', + overrideStringValue: 'high', + tagName: 'img', + read: getProperty('fetchPriority'), + }, + { + name: 'fetchpriority', + overrideStringValue: 'high', + tagName: 'img', + read: getProperty('fetchPriority'), + }, + { + name: 'fetchPriority', + overrideStringValue: 'high', + tagName: 'link', + read: getProperty('fetchPriority'), + }, { name: 'fill', containerTagName: 'svg', diff --git a/packages/react-dom-bindings/src/shared/possibleStandardNames.js b/packages/react-dom-bindings/src/shared/possibleStandardNames.js index 6a6029fbcbb6..e1b76a0978ba 100644 --- a/packages/react-dom-bindings/src/shared/possibleStandardNames.js +++ b/packages/react-dom-bindings/src/shared/possibleStandardNames.js @@ -60,6 +60,7 @@ const possibleStandardNames = { draggable: 'draggable', enctype: 'encType', enterkeyhint: 'enterKeyHint', + fetchpriority: 'fetchPriority', for: 'htmlFor', form: 'form', formmethod: 'formMethod', From 48274a43aa708f63a7580142a4c1c1a47f31c1ac Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 4 Jan 2023 14:50:00 -0500 Subject: [PATCH 014/269] Remove vestigial Suspense batching logic (#25861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code was originally added in the old ExpirationTime implementation of Suspense. The idea is that if multiple updates suspend inside the same Suspense boundary, and both of them resolve, we should render both results in the same batch, to reduce jank. This was an incomplete idea, though. We later discovered a stronger requirement — once we show a fallback, we cannot fill in that fallback without completing _all_ the updates that were previously skipped over. Otherwise you get tearing. This was fixed by #18411, then we discovered additional related flaws that were addressed in #24685. See those PR descriptions for additional context. So I believe this older code is no longer necessary. --- packages/react-reconciler/src/ReactFiberWorkLoop.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 7d5a38ad393d..74ad3150258e 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -1300,16 +1300,6 @@ function finishConcurrentRender(root, exitStatus, lanes) { // There's additional work on this root. break; } - const suspendedLanes = root.suspendedLanes; - if (!isSubsetOfLanes(suspendedLanes, lanes)) { - // We should prefer to render the fallback of at the last - // suspended level. Ping the last suspended level to try - // rendering it again. - // FIXME: What if the suspended lanes are Idle? Should not restart. - const eventTime = requestEventTime(); - markRootPinged(root, suspendedLanes, eventTime); - break; - } // The render is suspended, it hasn't timed out, and there's no // lower priority work to do. Instead of committing the fallback From c2d6552079178b36619f5dfd1ea39ae80b1d38b5 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 4 Jan 2023 15:11:56 -0500 Subject: [PATCH 015/269] Unify `use` and `renderDidSuspendDelayIfPossible` implementations (#25922) When unwrapping a promise with `use`, we sometimes suspend the work loop from rendering anything else until the data has resolved. This is different from how Suspense works in the old throw-a-promise world, where rather than suspend rendering midway through the render phase, we prepare a fallback and block the commit at the end, if necessary; however, the logic for determining whether it's OK to block is the same. The implementation is only incidentally different because it happens in two different parts of the code. This means for `use`, we end up doing the same checks twice, which is wasteful in terms of computation, but also introduces a risk that the logic will accidentally diverge. This unifies the implementation by moving it into the SuspenseContext module. Most of the logic for deciding whether to suspend is already performed in the begin phase of SuspenseComponent, so it makes sense to store that information on the stack rather than recompute it on demand. The way I've chosen to model this is to track whether the work loop is rendering inside the "shell" of the tree. The shell is defined as the part of the tree that's visible in the current UI. Once we enter a new Suspense boundary (or a hidden Offscreen boundary, which acts a Suspense boundary), we're no longer in the shell. This is already how Suspense behavior was modeled in terms of UX, so using this concept directly in the implementation turns out to result in less code than before. For the most part, this is purely an internal refactor, though it does fix a bug in the `use` implementation related to nested Suspense boundaries. I wouldn't be surprised if it happens to fix other bugs that we haven't yet discovered, especially around Offscreen. I'll add more tests as I think of them. --- .../src/ReactFiberCompleteWork.js | 21 --- .../src/ReactFiberSuspenseContext.js | 150 ++++++++++-------- .../react-reconciler/src/ReactFiberThrow.js | 43 ++++- .../src/ReactFiberWorkLoop.js | 58 ++++--- .../ReactSuspenseWithNoopRenderer-test.js | 12 +- .../src/__tests__/ReactThenable-test.js | 145 +++++++++++++++++ 6 files changed, 297 insertions(+), 132 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 5ea22d07feae..dbe645d792fd 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -126,7 +126,6 @@ import { setShallowSuspenseListContext, ForceSuspenseFallback, setDefaultShallowSuspenseListContext, - isBadSuspenseFallback, } from './ReactFiberSuspenseContext'; import {popHiddenContext} from './ReactFiberHiddenContext'; import {findFirstSuspended} from './ReactFiberSuspenseComponent'; @@ -148,8 +147,6 @@ import { upgradeHydrationErrorsToRecoverable, } from './ReactFiberHydrationContext'; import { - renderDidSuspend, - renderDidSuspendDelayIfPossible, renderHasNotSuspendedYet, getRenderTargetTime, getWorkInProgressTransitions, @@ -1257,24 +1254,6 @@ function completeWork( if (nextDidTimeout) { const offscreenFiber: Fiber = (workInProgress.child: any); offscreenFiber.flags |= Visibility; - - // TODO: This will still suspend a synchronous tree if anything - // in the concurrent tree already suspended during this render. - // This is a known bug. - if ((workInProgress.mode & ConcurrentMode) !== NoMode) { - // TODO: Move this back to throwException because this is too late - // if this is a large tree which is common for initial loads. We - // don't know if we should restart a render or not until we get - // this marker, and this is too late. - // If this render already had a ping or lower pri updates, - // and this is the first time we know we're going to suspend we - // should be able to immediately restart from within throwException. - if (isBadSuspenseFallback(current, newProps)) { - renderDidSuspendDelayIfPossible(); - } else { - renderDidSuspend(); - } - } } } diff --git a/packages/react-reconciler/src/ReactFiberSuspenseContext.js b/packages/react-reconciler/src/ReactFiberSuspenseContext.js index c9e520e27655..b1010ec30dc2 100644 --- a/packages/react-reconciler/src/ReactFiberSuspenseContext.js +++ b/packages/react-reconciler/src/ReactFiberSuspenseContext.js @@ -9,12 +9,13 @@ import type {Fiber} from './ReactInternalTypes'; import type {StackCursor} from './ReactFiberStack'; -import type {SuspenseState, SuspenseProps} from './ReactFiberSuspenseComponent'; +import type {SuspenseProps, SuspenseState} from './ReactFiberSuspenseComponent'; +import type {OffscreenState} from './ReactFiberOffscreenComponent'; import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags'; import {createCursor, push, pop} from './ReactFiberStack'; import {isCurrentTreeHidden} from './ReactFiberHiddenContext'; -import {SuspenseComponent, OffscreenComponent} from './ReactWorkTags'; +import {OffscreenComponent} from './ReactWorkTags'; // The Suspense handler is the boundary that should capture if something // suspends, i.e. it's the nearest `catch` block on the stack. @@ -22,81 +23,72 @@ const suspenseHandlerStackCursor: StackCursor = createCursor( null, ); -function shouldAvoidedBoundaryCapture( - workInProgress: Fiber, - handlerOnStack: Fiber, - props: any, -): boolean { - if (enableSuspenseAvoidThisFallback) { - // If the parent is already showing content, and we're not inside a hidden - // tree, then we should show the avoided fallback. - if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) { - return true; - } - - // If the handler on the stack is also an avoided boundary, then we should - // favor this inner one. - if ( - handlerOnStack.tag === SuspenseComponent && - handlerOnStack.memoizedProps.unstable_avoidThisFallback === true - ) { - return true; - } - - // If this avoided boundary is dehydrated, then it should capture. - const suspenseState: SuspenseState | null = workInProgress.memoizedState; - if (suspenseState !== null && suspenseState.dehydrated !== null) { - return true; - } - } - - // If none of those cases apply, then we should avoid this fallback and show - // the outer one instead. - return false; -} - -export function isBadSuspenseFallback( - current: Fiber | null, - nextProps: SuspenseProps, -): boolean { - // Check if this is a "bad" fallback state or a good one. A bad fallback state - // is one that we only show as a last resort; if this is a transition, we'll - // block it from displaying, and wait for more data to arrive. - if (current !== null) { - const prevState: SuspenseState = current.memoizedState; - const isShowingFallback = prevState !== null; - if (!isShowingFallback && !isCurrentTreeHidden()) { - // It's bad to switch to a fallback if content is already visible - return true; - } - } - - if ( - enableSuspenseAvoidThisFallback && - nextProps.unstable_avoidThisFallback === true - ) { - // Experimental: Some fallbacks are always bad - return true; - } - - return false; +// Represents the outermost boundary that is not visible in the current tree. +// Everything above this is the "shell". When this is null, it means we're +// rendering in the shell of the app. If it's non-null, it means we're rendering +// deeper than the shell, inside a new tree that wasn't already visible. +// +// The main way we use this concept is to determine whether showing a fallback +// would result in a desirable or undesirable loading state. Activing a fallback +// in the shell is considered an undersirable loading state, because it would +// mean hiding visible (albeit stale) content in the current tree — we prefer to +// show the stale content, rather than switch to a fallback. But showing a +// fallback in a new tree is fine, because there's no stale content to +// prefer instead. +let shellBoundary: Fiber | null = null; + +export function getShellBoundary(): Fiber | null { + return shellBoundary; } export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void { - const props = handler.pendingProps; - const handlerOnStack = suspenseHandlerStackCursor.current; + // TODO: Pass as argument + const current = handler.alternate; + const props: SuspenseProps = handler.pendingProps; + + // Experimental feature: Some Suspense boundaries are marked as having an + // undesirable fallback state. These have special behavior where we only + // activate the fallback if there's no other boundary on the stack that we can + // use instead. if ( enableSuspenseAvoidThisFallback && props.unstable_avoidThisFallback === true && - handlerOnStack !== null && - !shouldAvoidedBoundaryCapture(handler, handlerOnStack, props) + // If an avoided boundary is already visible, it behaves identically to + // a regular Suspense boundary. + (current === null || isCurrentTreeHidden()) ) { - // This boundary should not capture if something suspends. Reuse the - // existing handler on the stack. - push(suspenseHandlerStackCursor, handlerOnStack, handler); - } else { - // Push this handler onto the stack. - push(suspenseHandlerStackCursor, handler, handler); + if (shellBoundary === null) { + // We're rendering in the shell. There's no parent Suspense boundary that + // can provide a desirable fallback state. We'll use this boundary. + push(suspenseHandlerStackCursor, handler, handler); + + // However, because this is not a desirable fallback, the children are + // still considered part of the shell. So we intentionally don't assign + // to `shellBoundary`. + } else { + // There's already a parent Suspense boundary that can provide a desirable + // fallback state. Prefer that one. + const handlerOnStack = suspenseHandlerStackCursor.current; + push(suspenseHandlerStackCursor, handlerOnStack, handler); + } + return; + } + + // TODO: If the parent Suspense handler already suspended, there's no reason + // to push a nested Suspense handler, because it will get replaced by the + // outer fallback, anyway. Consider this as a future optimization. + push(suspenseHandlerStackCursor, handler, handler); + if (shellBoundary === null) { + if (current === null || isCurrentTreeHidden()) { + // This boundary is not visible in the current UI. + shellBoundary = handler; + } else { + const prevState: SuspenseState = current.memoizedState; + if (prevState !== null) { + // This boundary is showing a fallback in the current UI. + shellBoundary = handler; + } + } } } @@ -110,6 +102,20 @@ export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void { export function pushOffscreenSuspenseHandler(fiber: Fiber): void { if (fiber.tag === OffscreenComponent) { push(suspenseHandlerStackCursor, fiber, fiber); + if (shellBoundary !== null) { + // A parent boundary is showing a fallback, so we've already rendered + // deeper than the shell. + } else { + const current = fiber.alternate; + if (current !== null) { + const prevState: OffscreenState = current.memoizedState; + if (prevState !== null) { + // This is the first boundary in the stack that's already showing + // a fallback. So everything outside is considered the shell. + shellBoundary = fiber; + } + } + } } else { // This is a LegacyHidden component. reuseSuspenseHandlerOnStack(fiber); @@ -126,6 +132,10 @@ export function getSuspenseHandler(): Fiber | null { export function popSuspenseHandler(fiber: Fiber): void { pop(suspenseHandlerStackCursor, fiber); + if (shellBoundary === fiber) { + // Popping back into the shell. + shellBoundary = null; + } } // SuspenseList context diff --git a/packages/react-reconciler/src/ReactFiberThrow.js b/packages/react-reconciler/src/ReactFiberThrow.js index d425175eabb5..7390a25e72d7 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.js +++ b/packages/react-reconciler/src/ReactFiberThrow.js @@ -49,7 +49,10 @@ import { enqueueUpdate, } from './ReactFiberClassUpdateQueue'; import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading'; -import {getSuspenseHandler} from './ReactFiberSuspenseContext'; +import { + getShellBoundary, + getSuspenseHandler, +} from './ReactFiberSuspenseContext'; import { renderDidError, renderDidSuspendDelayIfPossible, @@ -58,6 +61,7 @@ import { isAlreadyFailedLegacyErrorBoundary, attachPingListener, restorePendingUpdaters, + renderDidSuspend, } from './ReactFiberWorkLoop'; import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext'; import {logCapturedError} from './ReactFiberErrorLogger'; @@ -349,11 +353,46 @@ function throwException( } } - // Schedule the nearest Suspense to re-render the timed out view. + // Mark the nearest Suspense boundary to switch to rendering a fallback. const suspenseBoundary = getSuspenseHandler(); if (suspenseBoundary !== null) { switch (suspenseBoundary.tag) { case SuspenseComponent: { + // If this suspense boundary is not already showing a fallback, mark + // the in-progress render as suspended. We try to perform this logic + // as soon as soon as possible during the render phase, so the work + // loop can know things like whether it's OK to switch to other tasks, + // or whether it can wait for data to resolve before continuing. + // TODO: Most of these checks are already performed when entering a + // Suspense boundary. We should track the information on the stack so + // we don't have to recompute it on demand. This would also allow us + // to unify with `use` which needs to perform this logic even sooner, + // before `throwException` is called. + if (sourceFiber.mode & ConcurrentMode) { + if (getShellBoundary() === null) { + // Suspended in the "shell" of the app. This is an undesirable + // loading state. We should avoid committing this tree. + renderDidSuspendDelayIfPossible(); + } else { + // If we suspended deeper than the shell, we don't need to delay + // the commmit. However, we still call renderDidSuspend if this is + // a new boundary, to tell the work loop that a new fallback has + // appeared during this render. + // TODO: Theoretically we should be able to delete this branch. + // It's currently used for two things: 1) to throttle the + // appearance of successive loading states, and 2) in + // SuspenseList, to determine whether the children include any + // pending fallbacks. For 1, we should apply throttling to all + // retries, not just ones that render an additional fallback. For + // 2, we should check subtreeFlags instead. Then we can delete + // this branch. + const current = suspenseBoundary.alternate; + if (current === null) { + renderDidSuspend(); + } + } + } + suspenseBoundary.flags &= ~ForceClientRender; markSuspenseBoundaryShouldCapture( suspenseBoundary, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 74ad3150258e..b6efb85e1972 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -12,7 +12,7 @@ import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols'; import type {Wakeable, Thenable} from 'shared/ReactTypes'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; -import type {SuspenseProps, SuspenseState} from './ReactFiberSuspenseComponent'; +import type {SuspenseState} from './ReactFiberSuspenseComponent'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks'; import type {EventPriority} from './ReactEventPriorities'; import type { @@ -276,7 +276,7 @@ import { import {schedulePostPaintCallback} from './ReactPostPaintCallback'; import { getSuspenseHandler, - isBadSuspenseFallback, + getShellBoundary, } from './ReactFiberSuspenseContext'; import {resolveDefaultProps} from './ReactFiberLazyComponent'; @@ -1859,18 +1859,11 @@ function handleThrow(root, thrownValue): void { } function shouldAttemptToSuspendUntilDataResolves() { - // TODO: We should be able to move the - // renderDidSuspend/renderDidSuspendDelayIfPossible logic into this function, - // instead of repeating it in the complete phase. Or something to that effect. - - if (includesOnlyRetries(workInProgressRootRenderLanes)) { - // We can always wait during a retry. - return true; - } - // Check if there are other pending updates that might possibly unblock this // component from suspending. This mirrors the check in // renderDidSuspendDelayIfPossible. We should attempt to unify them somehow. + // TODO: Consider unwinding immediately, using the + // SuspendedOnHydration mechanism. if ( includesNonIdleWork(workInProgressRootSkippedLanes) || includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes) @@ -1883,27 +1876,24 @@ function shouldAttemptToSuspendUntilDataResolves() { // TODO: We should be able to remove the equivalent check in // finishConcurrentRender, and rely just on this one. if (includesOnlyTransitions(workInProgressRootRenderLanes)) { - const suspenseHandler = getSuspenseHandler(); - if (suspenseHandler !== null && suspenseHandler.tag === SuspenseComponent) { - const currentSuspenseHandler = suspenseHandler.alternate; - const nextProps: SuspenseProps = suspenseHandler.memoizedProps; - if (isBadSuspenseFallback(currentSuspenseHandler, nextProps)) { - // The nearest Suspense boundary is already showing content. We should - // avoid replacing it with a fallback, and instead wait until the - // data finishes loading. - return true; - } else { - // This is not a bad fallback condition. We should show a fallback - // immediately instead of waiting for the data to resolve. This includes - // when suspending inside new trees. - return false; - } - } + // If we're rendering inside the "shell" of the app, it's better to suspend + // rendering and wait for the data to resolve. Otherwise, we should switch + // to a fallback and continue rendering. + return getShellBoundary() === null; + } - // During a transition, if there is no Suspense boundary (i.e. suspending in - // the "shell" of an application), or if we're inside a hidden tree, then - // we should wait until the data finishes loading. - return true; + const handler = getSuspenseHandler(); + if (handler === null) { + // TODO: We should support suspending in the case where there's no + // parent Suspense boundary, even outside a transition. Somehow. Otherwise, + // an uncached promise can fall into an infinite loop. + } else { + if (includesOnlyRetries(workInProgressRootRenderLanes)) { + // During a retry, we can suspend rendering if the nearest Suspense boundary + // is the boundary of the "shell", because we're guaranteed not to block + // any new content from appearing. + return handler === getShellBoundary(); + } } // For all other Lanes besides Transitions and Retries, we should not wait @@ -1981,6 +1971,8 @@ export function renderDidSuspendDelayIfPossible(): void { // (inside this function), since by suspending at the end of the render // phase introduces a potential mistake where we suspend lanes that were // pinged or updated while we were rendering. + // TODO: Consider unwinding immediately, using the + // SuspendedOnHydration mechanism. markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes); } } @@ -2198,6 +2190,10 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { } // The work loop is suspended on data. We should wait for it to // resolve before continuing to render. + // TODO: Handle the case where the promise resolves synchronously. + // Usually this is handled when we instrument the promise to add a + // `status` field, but if the promise already has a status, we won't + // have added a listener until right here. const onResolution = () => { ensureRootIsScheduled(root, now()); }; diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index bb4ea103ff12..9d95a57572f5 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -995,14 +995,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(Scheduler).toFlushAndYieldThrough(['Suspend! [Async]', 'Sibling']); await resolveText('Async'); - expect(Scheduler).toFlushAndYield([ - // We've now pinged the boundary but we don't know if we should restart yet, - // because we haven't completed the suspense boundary. - 'Loading...', - // Once we've completed the boundary we restarted. - 'Async', - 'Sibling', - ]); + + // Because we're already showing a fallback, interrupt the current render + // and restart immediately. + expect(Scheduler).toFlushAndYield(['Async', 'Sibling']); expect(root).toMatchRenderedOutput( <> diff --git a/packages/react-reconciler/src/__tests__/ReactThenable-test.js b/packages/react-reconciler/src/__tests__/ReactThenable-test.js index 0af8ccafd381..965d69b7c2aa 100644 --- a/packages/react-reconciler/src/__tests__/ReactThenable-test.js +++ b/packages/react-reconciler/src/__tests__/ReactThenable-test.js @@ -9,6 +9,7 @@ let useState; let useMemo; let Suspense; let startTransition; +let cache; let pendingTextRequests; describe('ReactThenable', () => { @@ -24,6 +25,7 @@ describe('ReactThenable', () => { useMemo = React.useMemo; Suspense = React.Suspense; startTransition = React.startTransition; + cache = React.cache; pendingTextRequests = new Map(); }); @@ -876,4 +878,147 @@ describe('ReactThenable', () => { ]); }, ); + + // @gate enableUseHook + test('load multiple nested Suspense boundaries', async () => { + const getCachedAsyncText = cache(getAsyncText); + + function AsyncText({text}) { + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render( + }> + + }> + + }> + + + + , + ); + }); + expect(Scheduler).toHaveYielded([ + 'Async text requested [A]', + 'Async text requested [B]', + 'Async text requested [C]', + '(Loading C...)', + '(Loading B...)', + '(Loading A...)', + ]); + expect(root).toMatchRenderedOutput('(Loading A...)'); + + await act(async () => { + resolveTextRequests('A'); + }); + expect(Scheduler).toHaveYielded(['A', '(Loading C...)', '(Loading B...)']); + expect(root).toMatchRenderedOutput('A(Loading B...)'); + + await act(async () => { + resolveTextRequests('B'); + }); + expect(Scheduler).toHaveYielded(['B', '(Loading C...)']); + expect(root).toMatchRenderedOutput('AB(Loading C...)'); + + await act(async () => { + resolveTextRequests('C'); + }); + expect(Scheduler).toHaveYielded(['C']); + expect(root).toMatchRenderedOutput('ABC'); + }); + + // @gate enableUseHook + test('load multiple nested Suspense boundaries (uncached requests)', async () => { + // This the same as the previous test, except the requests are not cached. + // The tree should still eventually resolve, despite the + // duplicate requests. + function AsyncText({text}) { + // This initiates a new request on each render. + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render( + }> + + }> + + }> + + + + , + ); + }); + expect(Scheduler).toHaveYielded([ + 'Async text requested [A]', + 'Async text requested [B]', + 'Async text requested [C]', + '(Loading C...)', + '(Loading B...)', + '(Loading A...)', + ]); + expect(root).toMatchRenderedOutput('(Loading A...)'); + + await act(async () => { + resolveTextRequests('A'); + }); + expect(Scheduler).toHaveYielded(['Async text requested [A]']); + expect(root).toMatchRenderedOutput('(Loading A...)'); + + await act(async () => { + resolveTextRequests('A'); + }); + expect(Scheduler).toHaveYielded([ + // React suspends until A finishes loading. + 'Async text requested [A]', + 'A', + + // Now React can continue rendering the rest of the tree. + + // React does not suspend on the inner requests, because that would + // block A from appearing. Instead it shows a fallback. + 'Async text requested [B]', + 'Async text requested [C]', + '(Loading C...)', + '(Loading B...)', + ]); + expect(root).toMatchRenderedOutput('A(Loading B...)'); + + await act(async () => { + resolveTextRequests('B'); + }); + expect(Scheduler).toHaveYielded(['Async text requested [B]']); + expect(root).toMatchRenderedOutput('A(Loading B...)'); + + await act(async () => { + resolveTextRequests('B'); + }); + expect(Scheduler).toHaveYielded([ + // React suspends until B finishes loading. + 'Async text requested [B]', + 'B', + + // React does not suspend on C, because that would block B from appearing. + 'Async text requested [C]', + '(Loading C...)', + ]); + expect(root).toMatchRenderedOutput('AB(Loading C...)'); + + await act(async () => { + resolveTextRequests('C'); + }); + expect(Scheduler).toHaveYielded(['Async text requested [C]']); + expect(root).toMatchRenderedOutput('AB(Loading C...)'); + + await act(async () => { + resolveTextRequests('C'); + }); + expect(Scheduler).toHaveYielded(['Async text requested [C]', 'C']); + expect(root).toMatchRenderedOutput('ABC'); + }); }); From ff9f943741671b6d83d732b2131d3f7e7d3c54c8 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 4 Jan 2023 17:31:13 -0500 Subject: [PATCH 016/269] [DevTools] add perf regression test page in shell (#25078) ## Summary This PR adds a "perf regression tests" page to react-devtools-shell. This page is meant to be used as a performance sanity check we will run whenever we release a new version or finish a major refactor. Similar to other pages in the shell, this page can load the inline version of devtools and a test react app on the same page. But this page does not load devtools automatically like other pages. Instead, it provides a button that allows us to load devtools on-demand, so that we can easily compare perf numbers without devtools against the numbers with devtools. image As a first step, this page currently only contain one test: mount/unmount a large subtree. This is to catch perf issues that devtools can cause on the react applications it's running on, which was once a bug fixed in #24863. In the future, we plan to add: - more test apps covering different scenarios - perf numbers within devtools (e.g. initial load) ## How did you test this change? In order to show this test app can actually catch the perf regression it's aiming at, I reverted #24863 locally. Here is the result: https://user-images.githubusercontent.com/1001890/184059214-9c9b308c-173b-4dd7-b815-46fbd7067073.mov As shown in the video, the time it takes to unmount the large subtree significantly increased after DevTools is loaded. For comparison, here is how it looks like before the fix was reverted: image ## about the `requestAnimationFrame` method For this test, I used `requestAnimationFrame` to catch the time when render and commit are done. It aligns very well with the numbers reported by Chrome DevTools performance profiling. For example, in one run, the numbers reported by my method are image They are very close to the numbers reported by Chrome profiling: image image `` is not able to catch this issue here. If you are aware of a better way to do this, please kindly share with me. --- packages/react-devtools-shell/index.html | 3 + .../react-devtools-shell/perf-regression.html | 45 +++++++++++++++ .../src/e2e-regression/devtools.js | 2 +- .../src/perf-regression/app.js | 22 ++++++++ .../src/perf-regression/apps/LargeSubtree.js | 44 +++++++++++++++ .../src/perf-regression/apps/index.js | 19 +++++++ .../src/perf-regression/devtools.js | 55 +++++++++++++++++++ .../react-devtools-shell/webpack.config.js | 2 + 8 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 packages/react-devtools-shell/perf-regression.html create mode 100644 packages/react-devtools-shell/src/perf-regression/app.js create mode 100644 packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js create mode 100644 packages/react-devtools-shell/src/perf-regression/apps/index.js create mode 100644 packages/react-devtools-shell/src/perf-regression/devtools.js diff --git a/packages/react-devtools-shell/index.html b/packages/react-devtools-shell/index.html index 81b9c2ce18cc..4cde55278a9a 100644 --- a/packages/react-devtools-shell/index.html +++ b/packages/react-devtools-shell/index.html @@ -51,7 +51,10 @@ multi DevTools | e2e tests + | e2e regression tests + | + perf regression tests
diff --git a/packages/react-devtools-shell/perf-regression.html b/packages/react-devtools-shell/perf-regression.html new file mode 100644 index 000000000000..27fd87f88dd8 --- /dev/null +++ b/packages/react-devtools-shell/perf-regression.html @@ -0,0 +1,45 @@ + + + + + React DevTools + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/packages/react-devtools-shell/src/e2e-regression/devtools.js b/packages/react-devtools-shell/src/e2e-regression/devtools.js index d8554eb55865..eb3a62622415 100644 --- a/packages/react-devtools-shell/src/e2e-regression/devtools.js +++ b/packages/react-devtools-shell/src/e2e-regression/devtools.js @@ -9,7 +9,7 @@ import {initialize as createDevTools} from 'react-devtools-inline/frontend'; // This is a pretty gross hack to make the runtime loaded named-hooks-code work. // TODO (Webpack 5) Hoepfully we can remove this once we upgrade to Webpack 5. -// $FlowFixMer +// $FlowFixMe __webpack_public_path__ = '/dist/'; // eslint-disable-line no-undef // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration. diff --git a/packages/react-devtools-shell/src/perf-regression/app.js b/packages/react-devtools-shell/src/perf-regression/app.js new file mode 100644 index 000000000000..88308e0fcf34 --- /dev/null +++ b/packages/react-devtools-shell/src/perf-regression/app.js @@ -0,0 +1,22 @@ +/** @flow */ + +// This test harness mounts each test app as a separate root to test multi-root applications. + +import * as React from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './apps/index'; + +function mountApp() { + const container = document.createElement('div'); + + ((document.body: any): HTMLBodyElement).appendChild(container); + + const root = createRoot(container); + root.render( + + + , + ); +} + +mountApp(); diff --git a/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js b/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js new file mode 100644 index 000000000000..11da40cf2c11 --- /dev/null +++ b/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js @@ -0,0 +1,44 @@ +/** + * 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 + */ + +import * as React from 'react'; + +function generateArray(size) { + return Array.from({length: size}, () => Math.floor(Math.random() * size)); +} + +const arr = generateArray(50000); + +export default function LargeSubtree() { + const [showList, setShowList] = React.useState(false); + const toggleList = () => { + const startTime = performance.now(); + setShowList(!showList); + // requestAnimationFrame should happen after render+commit is done + window.requestAnimationFrame(() => { + const afterRenderTime = performance.now(); + console.log( + `Time spent on ${ + showList ? 'unmounting' : 'mounting' + } the subtree: ${afterRenderTime - startTime}ms`, + ); + }); + }; + return ( +
+

Mount/Unmount a large subtree

+

Click the button to toggle the state. Open console for results.

+ +
    +
  • dummy item
  • + {showList && arr.map((num, idx) =>
  • {num}
  • )} +
+
+ ); +} diff --git a/packages/react-devtools-shell/src/perf-regression/apps/index.js b/packages/react-devtools-shell/src/perf-regression/apps/index.js new file mode 100644 index 000000000000..2508dd40103e --- /dev/null +++ b/packages/react-devtools-shell/src/perf-regression/apps/index.js @@ -0,0 +1,19 @@ +/** + * 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 + */ + +import * as React from 'react'; +import LargeSubtree from './LargeSubtree'; + +export default function Home() { + return ( +
+ +
+ ); +} diff --git a/packages/react-devtools-shell/src/perf-regression/devtools.js b/packages/react-devtools-shell/src/perf-regression/devtools.js new file mode 100644 index 000000000000..ffbc8afd718c --- /dev/null +++ b/packages/react-devtools-shell/src/perf-regression/devtools.js @@ -0,0 +1,55 @@ +import * as React from 'react'; +import {createRoot} from 'react-dom/client'; +import { + activate as activateBackend, + initialize as initializeBackend, +} from 'react-devtools-inline/backend'; +import {initialize as createDevTools} from 'react-devtools-inline/frontend'; + +// This is a pretty gross hack to make the runtime loaded named-hooks-code work. +// TODO (Webpack 5) Hoepfully we can remove this once we upgrade to Webpack 5. +// $FlowFixMe +__webpack_public_path__ = '/dist/'; // eslint-disable-line no-undef + +// TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration. +function hookNamesModuleLoaderFunction() { + return import('react-devtools-inline/hookNames'); +} + +function inject(contentDocument, sourcePath) { + const script = contentDocument.createElement('script'); + script.src = sourcePath; + + ((contentDocument.body: any): HTMLBodyElement).appendChild(script); +} + +function init( + appSource: string, + appIframe: HTMLIFrameElement, + devtoolsContainer: HTMLElement, + loadDevToolsButton: HTMLButtonElement, +) { + const {contentDocument, contentWindow} = appIframe; + + initializeBackend(contentWindow); + + inject(contentDocument, appSource); + + loadDevToolsButton.addEventListener('click', () => { + const DevTools = createDevTools(contentWindow); + createRoot(devtoolsContainer).render( + , + ); + activateBackend(contentWindow); + }); +} + +init( + 'dist/perf-regression-app.js', + document.getElementById('iframe'), + document.getElementById('devtools'), + document.getElementById('load-devtools'), +); diff --git a/packages/react-devtools-shell/webpack.config.js b/packages/react-devtools-shell/webpack.config.js index 26d27a08a7cf..e1a30f733444 100644 --- a/packages/react-devtools-shell/webpack.config.js +++ b/packages/react-devtools-shell/webpack.config.js @@ -157,6 +157,8 @@ const app = makeConfig( 'multi-devtools': './src/multi/devtools.js', 'multi-right': './src/multi/right.js', 'e2e-regression': './src/e2e-regression/app.js', + 'perf-regression-app': './src/perf-regression/app.js', + 'perf-regression-devtools': './src/perf-regression/devtools.js', }, { react: resolve(builtModulesDir, 'react'), From 2619886ac0f1d41c9b361d82f069f9ed682321ff Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 5 Jan 2023 15:27:51 -0500 Subject: [PATCH 017/269] fix function type for flow (#25965) These files fail CI `yarn_flow`. --- .../src/perf-regression/apps/LargeSubtree.js | 2 +- packages/react-devtools-shell/src/perf-regression/apps/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js b/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js index 11da40cf2c11..63c37a6453be 100644 --- a/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js +++ b/packages/react-devtools-shell/src/perf-regression/apps/LargeSubtree.js @@ -15,7 +15,7 @@ function generateArray(size) { const arr = generateArray(50000); -export default function LargeSubtree() { +export default function LargeSubtree(): React.Node { const [showList, setShowList] = React.useState(false); const toggleList = () => { const startTime = performance.now(); diff --git a/packages/react-devtools-shell/src/perf-regression/apps/index.js b/packages/react-devtools-shell/src/perf-regression/apps/index.js index 2508dd40103e..3e51b9a5d7c4 100644 --- a/packages/react-devtools-shell/src/perf-regression/apps/index.js +++ b/packages/react-devtools-shell/src/perf-regression/apps/index.js @@ -10,7 +10,7 @@ import * as React from 'react'; import LargeSubtree from './LargeSubtree'; -export default function Home() { +export default function Home(): React.Node { return (
From b83baf63f71669ca7bf222afec305b7a6fd782b7 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Thu, 5 Jan 2023 15:41:49 -0500 Subject: [PATCH 018/269] Transform updates to support Flow this annotation syntax (#25918) Flow introduced a new syntax to annotated the context type of a function, this tries to update the rest and add 1 example usage. - 2b1fb91a55deb9b7b60452cb57184c2f182a42fd already added the changes required for eslint. - Jest transform is updated to use the recommended `hermes-parser` which can parse current and Flow syntax and will be updated in the future. - Rollup uses a new plugin to strip the flow types. This isn't ideal as the npm module is deprecated in favor of using `hermes-parser`, but I couldn't figure out how to integrate that with Rollup. --- package.json | 2 ++ .../src/shared/DOMNamespaces.js | 2 ++ .../shared/ReactControlledValuePropTypes.js | 2 ++ .../src/shared/assertValidProps.js | 2 ++ .../src/test-utils/ReactTestUtils.js | 2 ++ .../src/legacy-events/ResponderEventPlugin.js | 2 ++ packages/react-server/src/ReactFizzServer.js | 2 +- packages/react/src/ReactForwardRef.js | 2 ++ packages/react/src/ReactMemo.js | 2 ++ .../forks/invokeGuardedCallbackImpl.www.js | 2 ++ scripts/babel/getComments.js | 31 +++++++++++++++++++ .../babel/transform-react-version-pragma.js | 6 ++-- scripts/babel/transform-test-gate-pragma.js | 6 ++-- scripts/jest/preprocessor.js | 5 ++- scripts/rollup/build.js | 12 ++++++- yarn.lock | 23 +++++++++++++- 16 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 scripts/babel/getComments.js diff --git a/package.json b/package.json index 82371df4e261..12b1adb0fa2c 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,13 @@ "fbjs-scripts": "1.2.0", "filesize": "^6.0.1", "flow-bin": "^0.190.0", + "flow-remove-types": "^2.196.1", "glob": "^7.1.6", "glob-stream": "^6.1.0", "google-closure-compiler": "^20200517.0.0", "gzip-size": "^5.1.1", "hermes-eslint": "^0.9.0", + "hermes-parser": "^0.9.0", "jasmine-check": "^1.0.0-rc.0", "jest": "^26.6.3", "jest-cli": "^26.6.3", diff --git a/packages/react-dom-bindings/src/shared/DOMNamespaces.js b/packages/react-dom-bindings/src/shared/DOMNamespaces.js index c939595c0463..43fdf0086515 100644 --- a/packages/react-dom-bindings/src/shared/DOMNamespaces.js +++ b/packages/react-dom-bindings/src/shared/DOMNamespaces.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ export const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; diff --git a/packages/react-dom-bindings/src/shared/ReactControlledValuePropTypes.js b/packages/react-dom-bindings/src/shared/ReactControlledValuePropTypes.js index 596fe81cf1b6..ac1b16010647 100644 --- a/packages/react-dom-bindings/src/shared/ReactControlledValuePropTypes.js +++ b/packages/react-dom-bindings/src/shared/ReactControlledValuePropTypes.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ const hasReadOnlyValue = { diff --git a/packages/react-dom-bindings/src/shared/assertValidProps.js b/packages/react-dom-bindings/src/shared/assertValidProps.js index 32e462b4e026..9424838e0a1a 100644 --- a/packages/react-dom-bindings/src/shared/assertValidProps.js +++ b/packages/react-dom-bindings/src/shared/assertValidProps.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ import voidElementTags from './voidElementTags'; diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index 94e15d281127..0dc8ceabf955 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ import * as React from 'react'; diff --git a/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js b/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js index e3d1cf1bcd56..8b92b3766f4b 100644 --- a/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js +++ b/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ import { diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 236c68dfe867..9922a36db8c4 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1668,7 +1668,7 @@ function erroredTask( } } -function abortTaskSoft(task: Task): void { +function abortTaskSoft(this: Request, task: Task): void { // This aborts task without aborting the parent boundary that it blocks. // It's used for when we didn't need this task to complete the tree. // If task was needed, then it should use abortTask instead. diff --git a/packages/react/src/ReactForwardRef.js b/packages/react/src/ReactForwardRef.js index bfd8fc9afd3a..5b2514cad04f 100644 --- a/packages/react/src/ReactForwardRef.js +++ b/packages/react/src/ReactForwardRef.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols'; diff --git a/packages/react/src/ReactMemo.js b/packages/react/src/ReactMemo.js index 3f797de451d1..5bce1d4c3d9c 100644 --- a/packages/react/src/ReactMemo.js +++ b/packages/react/src/ReactMemo.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ import {REACT_MEMO_TYPE} from 'shared/ReactSymbols'; diff --git a/packages/shared/forks/invokeGuardedCallbackImpl.www.js b/packages/shared/forks/invokeGuardedCallbackImpl.www.js index 858f3331a35b..0fec4be6614e 100644 --- a/packages/shared/forks/invokeGuardedCallbackImpl.www.js +++ b/packages/shared/forks/invokeGuardedCallbackImpl.www.js @@ -3,6 +3,8 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @noflow */ // Provided by www diff --git a/scripts/babel/getComments.js b/scripts/babel/getComments.js new file mode 100644 index 000000000000..886e7d4b3613 --- /dev/null +++ b/scripts/babel/getComments.js @@ -0,0 +1,31 @@ +/** + * 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. + */ + +'use strict'; + +function getComments(path) { + const allComments = path.hub.file.ast.comments; + if (path.node.leadingComments) { + // Babel AST includes comments. + return path.node.leadingComments; + } + // In Hermes AST we need to find the comments by range. + const comments = []; + let line = path.node.loc.start.line; + let i = allComments.length - 1; + while (i >= 0 && allComments[i].loc.end.line >= line) { + i--; + } + while (i >= 0 && allComments[i].loc.end.line === line - 1) { + line = allComments[i].loc.start.line; + comments.unshift(allComments[i]); + i--; + } + return comments; +} + +module.exports = getComments; diff --git a/scripts/babel/transform-react-version-pragma.js b/scripts/babel/transform-react-version-pragma.js index 272d0d2fa59d..1414852d2cf5 100644 --- a/scripts/babel/transform-react-version-pragma.js +++ b/scripts/babel/transform-react-version-pragma.js @@ -2,6 +2,8 @@ /* eslint-disable no-for-of-loops/no-for-of-loops */ +const getComments = require('./getComments'); + const GATE_VERSION_STR = '@reactVersion '; function transform(babel) { @@ -65,7 +67,7 @@ function transform(babel) { callee.name === 'it' || callee.name === 'fit' ) { - const comments = statement.leadingComments; + const comments = getComments(path); const condition = buildGateVersionCondition(comments); if (condition !== null) { callee.name = @@ -87,7 +89,7 @@ function transform(babel) { callee.property.type === 'Identifier' && callee.property.name === 'only' ) { - const comments = statement.leadingComments; + const comments = getComments(path); const condition = buildGateVersionCondition(comments); if (condition !== null) { statement.expression = t.callExpression( diff --git a/scripts/babel/transform-test-gate-pragma.js b/scripts/babel/transform-test-gate-pragma.js index 8b1f469f5905..bdef629812ad 100644 --- a/scripts/babel/transform-test-gate-pragma.js +++ b/scripts/babel/transform-test-gate-pragma.js @@ -2,6 +2,8 @@ /* eslint-disable no-for-of-loops/no-for-of-loops */ +const getComments = require('./getComments'); + function transform(babel) { const {types: t} = babel; @@ -278,7 +280,7 @@ function transform(babel) { callee.name === 'it' || callee.name === 'fit' ) { - const comments = statement.leadingComments; + const comments = getComments(path); if (comments !== undefined) { const condition = buildGateCondition(comments); if (condition !== null) { @@ -304,7 +306,7 @@ function transform(babel) { callee.property.type === 'Identifier' && callee.property.name === 'only' ) { - const comments = statement.leadingComments; + const comments = getComments(path); if (comments !== undefined) { const condition = buildGateCondition(comments); if (condition !== null) { diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index d8f04b5bcbfb..4f6a49baa5a5 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -4,6 +4,7 @@ const path = require('path'); const babel = require('@babel/core'); const coffee = require('coffee-script'); +const hermesParser = require('hermes-parser'); const tsPreprocessor = require('./typescript/preprocessor'); const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction'); @@ -93,7 +94,9 @@ module.exports = { ) { plugins.push(pathToTransformReactVersionPragma); } - return babel.transform( + let sourceAst = hermesParser.parse(src, {babel: true}); + return babel.transformFromAstSync( + sourceAst, src, Object.assign( {filename: path.relative(process.cwd(), filePath)}, diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 9bd707287203..c08adda6d925 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -4,6 +4,7 @@ const rollup = require('rollup'); const babel = require('rollup-plugin-babel'); const closure = require('./plugins/closure-plugin'); const commonjs = require('rollup-plugin-commonjs'); +const flowRemoveTypes = require('flow-remove-types'); const prettier = require('rollup-plugin-prettier'); const replace = require('rollup-plugin-replace'); const stripBanner = require('rollup-plugin-strip-banner'); @@ -99,7 +100,6 @@ const syncWWWPath = argv['sync-www']; // Non-ES2015 stuff applied before closure compiler. const babelPlugins = [ // These plugins filter out non-ES2015. - '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-proposal-class-properties', {loose: true}], 'syntax-trailing-function-commas', // These use loose mode which avoids embedding a runtime. @@ -325,6 +325,16 @@ function getPlugins( bundleType === RN_FB_PROFILING; const shouldStayReadable = isFBWWWBundle || isRNBundle || forcePrettyOutput; return [ + { + name: 'rollup-plugin-flow-remove-types', + transform(code) { + const transformed = flowRemoveTypes(code); + return { + code: transformed.toString(), + map: transformed.generateMap(), + }; + }, + }, // Shim any modules that need forking in this environment. useForks(forks), // Ensure we don't try to bundle any fbjs modules. diff --git a/yarn.lock b/yarn.lock index cda67855e9f0..5e181ca3fdeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7920,6 +7920,20 @@ flow-bin@^0.190.0: resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.190.0.tgz#cfc50e1474facf8150232a6c498fe66a6bb75969" integrity sha512-Qo3bvN3cmGFXsq63ZxcHFZXQDvgx84fCuq8cXuKk5xbvuebBGwMqS+ku/rH+gEkciRrcTYrXqoSzb9b6ShcoJg== +flow-parser@^0.196.1: + version "0.196.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.196.1.tgz#3c31f102454518f0c68eeb99f57501c2a0c9bff0" + integrity sha512-V3yaKHyBWhl+LF6sxgbfqxMlwoFKs8UKh2DYTrGj1AHi9ST7Zyp+9ToF4l9eoL6l/DxdFwCNF3MAJ1vCVrgJmw== + +flow-remove-types@^2.196.1: + version "2.196.1" + resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.196.1.tgz#c77ab53679beb1b1ba420c16865cea714a67defc" + integrity sha512-pAEe2B/fKtV96MVGWQgmjP5Z1nLeFFe++r83ql1Zj86+p+3IujsbvwxiXCiF/SS6ObbB6TmciCxxd+FsOUyY3Q== + dependencies: + flow-parser "^0.196.1" + pirates "^3.0.2" + vlq "^0.2.1" + fluent-syntax@0.13.0: version "0.13.0" resolved "https://registry.yarnpkg.com/fluent-syntax/-/fluent-syntax-0.13.0.tgz#417144d99cba94ff474c422b3e6623d5a842855a" @@ -8749,7 +8763,7 @@ hermes-estree@0.9.0: resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.9.0.tgz#026e0abe6db1dcf50a81a79014b779a83db3b814" integrity sha512-5DZ7Y0CbHVk8zPqgRCvqp8iw+P05svnQDI1aJFjdqCfXJ/1CZ+8aYpGlhJ29zCG5SE5duGTzSxogAYYI4QqXqw== -hermes-parser@0.9.0: +hermes-parser@0.9.0, hermes-parser@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.9.0.tgz#ede3044d50479c61843cef5bbdcea83933d4e4ec" integrity sha512-IcvJIlAn+9tpHkP+HTsxWKrIdQPp0gvGrrQmxlL4XnNS+Oh6R/Fpxbcoflm2kY3zgQjEvxZxLiK/2+k3/5wsrw== @@ -12991,6 +13005,13 @@ pinpoint@^1.1.0: resolved "https://registry.yarnpkg.com/pinpoint/-/pinpoint-1.1.0.tgz#0cf7757a6977f1bf7f6a32207b709e377388e874" integrity sha1-DPd1eml38b9/ajIge3CeN3OI6HQ= +pirates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-3.0.2.tgz#7e6f85413fd9161ab4e12b539b06010d85954bb9" + integrity sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q== + dependencies: + node-modules-regexp "^1.0.0" + pirates@^4.0.0, pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" From bbf4d22113bb93608e183b07af077816a4a7074a Mon Sep 17 00:00:00 2001 From: Ming Ye Date: Fri, 6 Jan 2023 04:56:31 +0800 Subject: [PATCH 019/269] Update import for babel-code-frame in build script (#25963) ## Summary Updating import for babel-code-frame to use the official @babel package, as babel-code-frame is a ghost dependency. This change is necessary to avoid potential issues and stay up-to-date with the latest version of @babel/code-frame, which is already declared in our project's package.json. ## How did you test this change? yarn test --- scripts/rollup/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index c08adda6d925..b054e0d4a320 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -21,7 +21,7 @@ const useForks = require('./plugins/use-forks-plugin'); const stripUnusedImports = require('./plugins/strip-unused-imports'); const Packaging = require('./packaging'); const {asyncRimRaf} = require('./utils'); -const codeFrame = require('babel-code-frame'); +const codeFrame = require('@babel/code-frame'); const Wrappers = require('./wrappers'); const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL; From 5379b6123f171bb48cc8a9c435c11ccb9f8ff0e7 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Thu, 5 Jan 2023 15:21:35 -0800 Subject: [PATCH 020/269] Batch sync, default and continuous lanes (#25700) ## Summary This is the other approach for unifying default and sync lane https://github.com/facebook/react/pull/25524. The approach in that PR is to merge default and continuous lane into the sync lane, and use a new field to track the priority. But there are a couple places that field will be needed, and it is difficult to correctly reset the field when there is no sync lane. In this PR we take the other approach that doesn't remove any lane, but batch them to get the behavior we want. ## How did you test this change? yarn test Co-authored-by: Andrew Clark --- .../src/__tests__/ReactDOMFiberAsync-test.js | 27 +- ...DOMServerPartialHydration-test.internal.js | 120 ++++----- ...MServerSelectiveHydration-test.internal.js | 26 +- .../react-reconciler/src/ReactFiberLane.js | 93 ++++--- .../__tests__/ReactBatching-test.internal.js | 18 +- .../ReactClassSetStateCallback-test.js | 14 +- .../src/__tests__/ReactExpiration-test.js | 4 +- .../src/__tests__/ReactFlushSync-test.js | 12 +- .../src/__tests__/ReactHooks-test.internal.js | 10 +- .../ReactHooksWithNoopRenderer-test.js | 36 ++- .../__tests__/ReactIncrementalUpdates-test.js | 240 ++++++++++-------- .../src/__tests__/ReactOffscreen-test.js | 11 +- .../__tests__/ReactOffscreenSuspense-test.js | 10 +- .../src/__tests__/ReactTransition-test.js | 30 ++- .../useMutableSource-test.internal.js | 11 +- packages/shared/ReactFeatureFlags.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../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 | 1 + .../src/__tests__/useSubscription-test.js | 8 +- 26 files changed, 414 insertions(+), 267 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index a94cbe367a8a..5ed762afb86a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -275,17 +275,32 @@ describe('ReactDOMFiberAsync', () => { expect(ops).toEqual([]); }); // Only the active updates have flushed - expect(container.textContent).toEqual('BC'); - expect(ops).toEqual(['BC']); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(container.textContent).toEqual('ABC'); + expect(ops).toEqual(['ABC']); + } else { + expect(container.textContent).toEqual('BC'); + expect(ops).toEqual(['BC']); + } - instance.push('D'); - expect(container.textContent).toEqual('BC'); - expect(ops).toEqual(['BC']); + if (gate(flags => flags.enableUnifiedSyncLane)) { + instance.push('D'); + expect(container.textContent).toEqual('ABC'); + expect(ops).toEqual(['ABC']); + } else { + instance.push('D'); + expect(container.textContent).toEqual('BC'); + expect(ops).toEqual(['BC']); + } // Flush the async updates Scheduler.unstable_flushAll(); expect(container.textContent).toEqual('ABCD'); - expect(ops).toEqual(['BC', 'ABCD']); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(ops).toEqual(['ABC', 'ABCD']); + } else { + expect(ops).toEqual(['BC', 'ABCD']); + } }); // @gate www diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index f2bec4ec6a68..6da64f9cda99 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -449,10 +449,9 @@ describe('ReactDOMServerPartialHydration', () => { expect(deleted.length).toBe(0); // Performing an update should force it to delete the boundary - root.render(); - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render(); + }); expect(hydrated.length).toBe(1); expect(deleted.length).toBe(1); @@ -945,13 +944,12 @@ describe('ReactDOMServerPartialHydration', () => { root.render(); // At the same time, resolving the promise so that rendering can complete. - suspend = false; - resolve(); - await promise; - // This should first complete the hydration and then flush the update onto the hydrated state. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + suspend = false; + resolve(); + await promise; + }); // The new span should be the same since we should have successfully hydrated // before changing it. @@ -1093,9 +1091,9 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(null); // Render an update, but leave it still suspended. - root.render(); - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render(); + }); // Flushing now should delete the existing content and show the fallback. @@ -1104,12 +1102,11 @@ describe('ReactDOMServerPartialHydration', () => { expect(container.textContent).toBe('Loading...'); // Unsuspending shows the content. - suspend = false; - resolve(); - await promise; - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + suspend = false; + resolve(); + await promise; + }); const span = container.getElementsByTagName('span')[0]; expect(span.textContent).toBe('Hi'); @@ -1174,23 +1171,21 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(span); // Render an update, but leave it still suspended. - root.render(); - // Flushing now should delete the existing content and show the fallback. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render(); + }); expect(container.getElementsByTagName('span').length).toBe(1); expect(ref.current).toBe(span); expect(container.textContent).toBe(''); // Unsuspending shows the content. - suspend = false; - resolve(); - await promise; - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + suspend = false; + resolve(); + await promise; + }); expect(span.textContent).toBe('Hi'); expect(span.className).toBe('hi'); @@ -1252,20 +1247,21 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(null); // Render an update, but leave it still suspended. - root.render(); - // Flushing now should delete the existing content and show the fallback. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render(); + }); expect(container.getElementsByTagName('span').length).toBe(0); expect(ref.current).toBe(null); expect(container.textContent).toBe('Loading...'); // Unsuspending shows the content. - suspend = false; - resolve(); - await promise; + await act(async () => { + suspend = false; + resolve(); + await promise; + }); Scheduler.unstable_flushAll(); jest.runAllTimers(); @@ -1490,13 +1486,12 @@ describe('ReactDOMServerPartialHydration', () => { ); // At the same time, resolving the promise so that rendering can complete. - suspend = false; - resolve(); - await promise; - // This should first complete the hydration and then flush the update onto the hydrated state. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + suspend = false; + resolve(); + await promise; + }); // Since this should have been hydrated, this should still be the same span. const newSpan = container.getElementsByTagName('span')[0]; @@ -1569,27 +1564,25 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(null); // Render an update, but leave it still suspended. - root.render( - - - , - ); - // Flushing now should delete the existing content and show the fallback. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render( + + + , + ); + }); expect(container.getElementsByTagName('span').length).toBe(0); expect(ref.current).toBe(null); expect(container.textContent).toBe('Loading...'); // Unsuspending shows the content. - suspend = false; - resolve(); - await promise; - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + suspend = false; + resolve(); + await promise; + }); const span = container.getElementsByTagName('span')[0]; expect(span.textContent).toBe('Hi'); @@ -2320,16 +2313,15 @@ describe('ReactDOMServerPartialHydration', () => { // Render an update, which will be higher or the same priority as pinging the hydration. // The new update doesn't suspend. - root.render( - - - , - ); - // Since we're still suspended on the original data, we can't hydrate. // This will force all expiration times to flush. - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + await act(async () => { + root.render( + + + , + ); + }); // This will now be a new span because we weren't able to hydrate before const newSpan = container.getElementsByTagName('span')[0]; diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index c6da651a813a..ca795e98d141 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -1786,7 +1786,7 @@ describe('ReactDOMServerSelectiveHydration', () => { document.body.removeChild(container); }); - it('can force hydration in response to sync update', () => { + it('can force hydration in response to sync update', async () => { function Child({text}) { Scheduler.unstable_yieldValue(`Child ${text}`); return (spanRef = ref)}>{text}; @@ -1812,15 +1812,17 @@ describe('ReactDOMServerSelectiveHydration', () => { const root = ReactDOMClient.hydrateRoot(container, ); expect(Scheduler).toFlushUntilNextPaint(['App A']); - ReactDOM.flushSync(() => { - root.render(); + await act(async () => { + ReactDOM.flushSync(() => { + root.render(); + }); }); expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); // @gate experimental || www - it('can force hydration in response to continuous update', () => { + it('can force hydration in response to continuous update', async () => { function Child({text}) { Scheduler.unstable_yieldValue(`Child ${text}`); return (spanRef = ref)}>{text}; @@ -1846,14 +1848,17 @@ describe('ReactDOMServerSelectiveHydration', () => { const root = ReactDOMClient.hydrateRoot(container, ); expect(Scheduler).toFlushUntilNextPaint(['App A']); - TODO_scheduleContinuousSchedulerTask(() => { - root.render(); + await act(async () => { + TODO_scheduleContinuousSchedulerTask(() => { + root.render(); + }); }); - expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']); + + expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); - it('can force hydration in response to default update', () => { + it('can force hydration in response to default update', async () => { function Child({text}) { Scheduler.unstable_yieldValue(`Child ${text}`); return (spanRef = ref)}>{text}; @@ -1878,11 +1883,10 @@ describe('ReactDOMServerSelectiveHydration', () => { const initialSpan = container.getElementsByTagName('span')[0]; const root = ReactDOMClient.hydrateRoot(container, ); expect(Scheduler).toFlushUntilNextPaint(['App A']); - - ReactDOM.unstable_batchedUpdates(() => { + await act(async () => { root.render(); }); - expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']); + expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']); expect(initialSpan).toBe(spanRef); }); diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 97a28f88099e..42b2fc6f9a3d 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -23,6 +23,7 @@ import { enableUpdaterTracking, allowConcurrentByDefault, enableTransitionTracing, + enableUnifiedSyncLane, } from 'shared/ReactFeatureFlags'; import {isDevToolsPresent} from './ReactFiberDevToolsHook'; import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode'; @@ -45,6 +46,8 @@ export const InputContinuousLane: Lane = /* */ 0b0000000000000000000 export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000; export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000; +export const SyncUpdateLanes: Lane = /* */ 0b0000000000000000000000000101010; + const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000; const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000; const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000; @@ -133,6 +136,12 @@ let nextTransitionLane: Lane = TransitionLane1; let nextRetryLane: Lane = RetryLane1; function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { + if (enableUnifiedSyncLane) { + const pendingSyncLanes = lanes & SyncUpdateLanes; + if (pendingSyncLanes !== 0) { + return pendingSyncLanes; + } + } switch (getHighestPriorityLane(lanes)) { case SyncHydrationLane: return SyncHydrationLane; @@ -754,46 +763,50 @@ export function getBumpedLaneForHydration( const renderLane = getHighestPriorityLane(renderLanes); let lane; - switch (renderLane) { - case SyncLane: - lane = SyncHydrationLane; - break; - case InputContinuousLane: - lane = InputContinuousHydrationLane; - break; - case DefaultLane: - lane = DefaultHydrationLane; - break; - case TransitionLane1: - case TransitionLane2: - case TransitionLane3: - case TransitionLane4: - case TransitionLane5: - case TransitionLane6: - case TransitionLane7: - case TransitionLane8: - case TransitionLane9: - case TransitionLane10: - case TransitionLane11: - case TransitionLane12: - case TransitionLane13: - case TransitionLane14: - case TransitionLane15: - case TransitionLane16: - case RetryLane1: - case RetryLane2: - case RetryLane3: - case RetryLane4: - lane = TransitionHydrationLane; - break; - case IdleLane: - lane = IdleHydrationLane; - break; - default: - // Everything else is already either a hydration lane, or shouldn't - // be retried at a hydration lane. - lane = NoLane; - break; + if (enableUnifiedSyncLane && (renderLane & SyncUpdateLanes) !== NoLane) { + lane = SyncHydrationLane; + } else { + switch (renderLane) { + case SyncLane: + lane = SyncHydrationLane; + break; + case InputContinuousLane: + lane = InputContinuousHydrationLane; + break; + case DefaultLane: + lane = DefaultHydrationLane; + break; + case TransitionLane1: + case TransitionLane2: + case TransitionLane3: + case TransitionLane4: + case TransitionLane5: + case TransitionLane6: + case TransitionLane7: + case TransitionLane8: + case TransitionLane9: + case TransitionLane10: + case TransitionLane11: + case TransitionLane12: + case TransitionLane13: + case TransitionLane14: + case TransitionLane15: + case TransitionLane16: + case RetryLane1: + case RetryLane2: + case RetryLane3: + case RetryLane4: + lane = TransitionHydrationLane; + break; + case IdleLane: + lane = IdleHydrationLane; + break; + default: + // Everything else is already either a hydration lane, or shouldn't + // be retried at a hydration lane. + lane = NoLane; + break; + } } // Check if the lane we chose is suspended. If so, that indicates that we diff --git a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js index 7998d2a23177..f9938a79b53d 100644 --- a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js @@ -157,12 +157,18 @@ describe('ReactBlockingMode', () => { }), ); - // Only the second update should have flushed synchronously - expect(Scheduler).toHaveYielded(['B1']); - expect(root).toMatchRenderedOutput('A0B1'); - // Now flush the first update - expect(Scheduler).toFlushAndYield(['A1']); - expect(root).toMatchRenderedOutput('A1B1'); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded(['A1', 'B1']); + expect(root).toMatchRenderedOutput('A1B1'); + } else { + // Only the second update should have flushed synchronously + expect(Scheduler).toHaveYielded(['B1']); + expect(root).toMatchRenderedOutput('A0B1'); + + // Now flush the first update + expect(Scheduler).toFlushAndYield(['A1']); + expect(root).toMatchRenderedOutput('A1B1'); + } }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js b/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js index 0f97bb599718..1342bd7310ba 100644 --- a/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js +++ b/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js @@ -35,9 +35,17 @@ describe('ReactClassSetStateCallback', () => { expect(Scheduler).toHaveYielded([0]); await act(async () => { - app.setState({step: 1}, () => - Scheduler.unstable_yieldValue('Callback 1'), - ); + if (gate(flags => flags.enableUnifiedSyncLane)) { + React.startTransition(() => { + app.setState({step: 1}, () => + Scheduler.unstable_yieldValue('Callback 1'), + ); + }); + } else { + app.setState({step: 1}, () => + Scheduler.unstable_yieldValue('Callback 1'), + ); + } ReactNoop.flushSync(() => { app.setState({step: 2}, () => Scheduler.unstable_yieldValue('Callback 2'), diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js index 24127589dfd9..72ca257fefa8 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js @@ -339,7 +339,9 @@ describe('ReactExpiration', () => { // Before the update can finish, update again. Even though no time has // advanced, this update should be given a different expiration time than // the currently rendering one. So, C and D should render with 1, not 2. - subscribers.forEach(s => s.setState({text: '2'})); + React.startTransition(() => { + subscribers.forEach(s => s.setState({text: '2'})); + }); expect(Scheduler).toFlushAndYieldThrough([ '1 [C] [render]', '1 [D] [render]', diff --git a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js index 07cc3437a5e2..5802253e1c1f 100644 --- a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js @@ -54,15 +54,21 @@ describe('ReactFlushSync', () => { // The passive effect will schedule a sync update and a normal update. // They should commit in two separate batches. First the sync one. expect(() => { - expect(Scheduler).toFlushUntilNextPaint(['1, 0']); + expect(Scheduler).toFlushUntilNextPaint( + gate(flags => flags.enableUnifiedSyncLane) ? ['1, 1'] : ['1, 0'], + ); }).toErrorDev('flushSync was called from inside a lifecycle method'); // The remaining update is not sync ReactNoop.flushSync(); expect(Scheduler).toHaveYielded([]); - // Now flush it. - expect(Scheduler).toFlushUntilNextPaint(['1, 1']); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toFlushUntilNextPaint([]); + } else { + // Now flush it. + expect(Scheduler).toFlushUntilNextPaint(['1, 1']); + } }); expect(root).toMatchRenderedOutput('1, 1'); }); diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 63dc3c04d0a3..ebf4a2544692 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -568,9 +568,13 @@ describe('ReactHooks', () => { }); }; - // Update at normal priority - ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100)); - + if (gate(flags => flags.enableUnifiedSyncLane)) { + // Update at transition priority + React.startTransition(() => update(n => n * 100)); + } else { + // Update at normal priority + ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100)); + } // The new state is eagerly computed. expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 25d19be6b48d..8bbf49c2cc51 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -815,7 +815,13 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.discreteUpdates(() => { setRow(5); }); - setRow(20); + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.startTransition(() => { + setRow(20); + }); + } else { + setRow(20); + } }); expect(Scheduler).toHaveYielded(['Up', 'Down']); expect(root).toMatchRenderedOutput(); @@ -955,11 +961,15 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.flushSync(() => { counter.current.dispatch(INCREMENT); }); - expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - - expect(Scheduler).toFlushAndYield(['Count: 4']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded(['Count: 4']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); + } else { + expect(Scheduler).toHaveYielded(['Count: 1']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(Scheduler).toFlushAndYield(['Count: 4']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); + } }); }); @@ -1717,11 +1727,15 @@ describe('ReactHooksWithNoopRenderer', () => { // As a result we, somewhat surprisingly, commit them in the opposite order. // This should be fine because any non-discrete set of work doesn't guarantee order // and easily could've happened slightly later too. - expect(Scheduler).toHaveYielded([ - 'Will set count to 1', - 'Count: 2', - 'Count: 1', - ]); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded(['Will set count to 1', 'Count: 1']); + } else { + expect(Scheduler).toHaveYielded([ + 'Will set count to 1', + 'Count: 2', + 'Count: 1', + ]); + } expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); }); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js index 4d667b0ab670..b56fdc85361b 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js @@ -47,7 +47,7 @@ describe('ReactIncrementalUpdates', () => { state = {}; componentDidMount() { Scheduler.unstable_yieldValue('commit'); - ReactNoop.deferredUpdates(() => { + React.startTransition(() => { // Has low priority this.setState({b: 'b'}); this.setState({c: 'c'}); @@ -111,13 +111,13 @@ describe('ReactIncrementalUpdates', () => { expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']); ReactNoop.flushSync(() => { - ReactNoop.deferredUpdates(() => { + React.startTransition(() => { instance.setState({x: 'x'}); instance.setState({y: 'y'}); }); instance.setState({a: 'a'}); instance.setState({b: 'b'}); - ReactNoop.deferredUpdates(() => { + React.startTransition(() => { instance.updater.enqueueReplaceState(instance, {c: 'c'}); instance.setState({d: 'd'}); }); @@ -162,7 +162,11 @@ describe('ReactIncrementalUpdates', () => { } // Schedule some async updates - if (gate(flags => flags.enableSyncDefaultUpdates)) { + if ( + gate( + flags => flags.enableSyncDefaultUpdates || flags.enableUnifiedSyncLane, + ) + ) { React.startTransition(() => { instance.setState(createUpdate('a')); instance.setState(createUpdate('b')); @@ -179,23 +183,37 @@ describe('ReactIncrementalUpdates', () => { expect(ReactNoop.getChildren()).toEqual([span('')]); // Schedule some more updates at different priorities - if (gate(flags => flags.enableSyncDefaultUpdates)) { - instance.setState(createUpdate('d')); - ReactNoop.flushSync(() => { - instance.setState(createUpdate('e')); - instance.setState(createUpdate('f')); - }); - React.startTransition(() => { - instance.setState(createUpdate('g')); - }); + instance.setState(createUpdate('d')); + ReactNoop.flushSync(() => { + instance.setState(createUpdate('e')); + instance.setState(createUpdate('f')); + }); + React.startTransition(() => { + instance.setState(createUpdate('g')); + }); - // The sync updates should have flushed, but not the async ones + // The sync updates should have flushed, but not the async ones. + if ( + gate( + flags => flags.enableSyncDefaultUpdates && flags.enableUnifiedSyncLane, + ) + ) { + expect(Scheduler).toHaveYielded(['d', 'e', 'f']); + expect(ReactNoop.getChildren()).toEqual([span('def')]); + } else { + // Update d was dropped and replaced by e. expect(Scheduler).toHaveYielded(['e', 'f']); expect(ReactNoop.getChildren()).toEqual([span('ef')]); + } - // Now flush the remaining work. Even though e and f were already processed, - // they should be processed again, to ensure that the terminal state - // is deterministic. + // Now flush the remaining work. Even though e and f were already processed, + // they should be processed again, to ensure that the terminal state + // is deterministic. + if ( + gate( + flags => flags.enableSyncDefaultUpdates && !flags.enableUnifiedSyncLane, + ) + ) { expect(Scheduler).toFlushAndYield([ // Since 'g' is in a transition, we'll process 'd' separately first. // That causes us to process 'd' with 'e' and 'f' rebased. @@ -211,25 +229,19 @@ describe('ReactIncrementalUpdates', () => { 'f', 'g', ]); - expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]); } else { - instance.setState(createUpdate('d')); - ReactNoop.flushSync(() => { - instance.setState(createUpdate('e')); - instance.setState(createUpdate('f')); - }); - instance.setState(createUpdate('g')); - - // The sync updates should have flushed, but not the async ones - expect(Scheduler).toHaveYielded(['e', 'f']); - expect(ReactNoop.getChildren()).toEqual([span('ef')]); - - // Now flush the remaining work. Even though e and f were already processed, - // they should be processed again, to ensure that the terminal state - // is deterministic. - expect(Scheduler).toFlushAndYield(['a', 'b', 'c', 'd', 'e', 'f', 'g']); - expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]); + expect(Scheduler).toFlushAndYield([ + // Then we'll re-process everything for 'g'. + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + ]); } + expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]); }); it('can abort an update, schedule a replaceState, and resume', () => { @@ -261,7 +273,11 @@ describe('ReactIncrementalUpdates', () => { } // Schedule some async updates - if (gate(flags => flags.enableSyncDefaultUpdates)) { + if ( + gate( + flags => flags.enableSyncDefaultUpdates || flags.enableUnifiedSyncLane, + ) + ) { React.startTransition(() => { instance.setState(createUpdate('a')); instance.setState(createUpdate('b')); @@ -278,26 +294,39 @@ describe('ReactIncrementalUpdates', () => { expect(ReactNoop.getChildren()).toEqual([span('')]); // Schedule some more updates at different priorities - if (gate(flags => flags.enableSyncDefaultUpdates)) { - instance.setState(createUpdate('d')); + instance.setState(createUpdate('d')); - ReactNoop.flushSync(() => { - instance.setState(createUpdate('e')); - // No longer a public API, but we can test that it works internally by - // reaching into the updater. - instance.updater.enqueueReplaceState(instance, createUpdate('f')); - }); - React.startTransition(() => { - instance.setState(createUpdate('g')); - }); + ReactNoop.flushSync(() => { + instance.setState(createUpdate('e')); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + instance.updater.enqueueReplaceState(instance, createUpdate('f')); + }); + React.startTransition(() => { + instance.setState(createUpdate('g')); + }); - // The sync updates should have flushed, but not the async ones. + // The sync updates should have flushed, but not the async ones. + if ( + gate( + flags => flags.enableSyncDefaultUpdates && flags.enableUnifiedSyncLane, + ) + ) { + expect(Scheduler).toHaveYielded(['d', 'e', 'f']); + } else { + // Update d was dropped and replaced by e. expect(Scheduler).toHaveYielded(['e', 'f']); - expect(ReactNoop.getChildren()).toEqual([span('f')]); - - // Now flush the remaining work. Even though e and f were already processed, - // they should be processed again, to ensure that the terminal state - // is deterministic. + } + expect(ReactNoop.getChildren()).toEqual([span('f')]); + + // Now flush the remaining work. Even though e and f were already processed, + // they should be processed again, to ensure that the terminal state + // is deterministic. + if ( + gate( + flags => flags.enableSyncDefaultUpdates && !flags.enableUnifiedSyncLane, + ) + ) { expect(Scheduler).toFlushAndYield([ // Since 'g' is in a transition, we'll process 'd' separately first. // That causes us to process 'd' with 'e' and 'f' rebased. @@ -313,28 +342,19 @@ describe('ReactIncrementalUpdates', () => { 'f', 'g', ]); - expect(ReactNoop.getChildren()).toEqual([span('fg')]); } else { - instance.setState(createUpdate('d')); - ReactNoop.flushSync(() => { - instance.setState(createUpdate('e')); - // No longer a public API, but we can test that it works internally by - // reaching into the updater. - instance.updater.enqueueReplaceState(instance, createUpdate('f')); - }); - instance.setState(createUpdate('g')); - - // The sync updates should have flushed, but not the async ones. Update d - // was dropped and replaced by e. - expect(Scheduler).toHaveYielded(['e', 'f']); - expect(ReactNoop.getChildren()).toEqual([span('f')]); - - // Now flush the remaining work. Even though e and f were already processed, - // they should be processed again, to ensure that the terminal state - // is deterministic. - expect(Scheduler).toFlushAndYield(['a', 'b', 'c', 'd', 'e', 'f', 'g']); - expect(ReactNoop.getChildren()).toEqual([span('fg')]); + expect(Scheduler).toFlushAndYield([ + // Then we'll re-process everything for 'g'. + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + ]); } + expect(ReactNoop.getChildren()).toEqual([span('fg')]); }); it('passes accumulation of previous updates to replaceState updater function', () => { @@ -688,21 +708,29 @@ describe('ReactIncrementalUpdates', () => { pushToLog('B'), ); }); - expect(Scheduler).toHaveYielded([ - // A and B are pending. B is higher priority, so we'll render that first. - 'Committed: B', - // Because A comes first in the queue, we're now in rebase mode. B must - // be rebased on top of A. Also, in a layout effect, we received two new - // updates: C and D. C is user-blocking and D is synchronous. - // - // First render the synchronous update. What we're testing here is that - // B *is not dropped* even though it has lower than sync priority. That's - // because we already committed it. However, this render should not - // include C, because that update wasn't already committed. - 'Committed: BD', - 'Committed: BCD', - 'Committed: ABCD', - ]); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded([ + 'Committed: B', + 'Committed: BCD', + 'Committed: ABCD', + ]); + } else { + expect(Scheduler).toHaveYielded([ + // A and B are pending. B is higher priority, so we'll render that first. + 'Committed: B', + // Because A comes first in the queue, we're now in rebase mode. B must + // be rebased on top of A. Also, in a layout effect, we received two new + // updates: C and D. C is user-blocking and D is synchronous. + // + // First render the synchronous update. What we're testing here is that + // B *is not dropped* even though it has lower than sync priority. That's + // because we already committed it. However, this render should not + // include C, because that update wasn't already committed. + 'Committed: BD', + 'Committed: BCD', + 'Committed: ABCD', + ]); + } expect(root).toMatchRenderedOutput('ABCD'); }); @@ -748,21 +776,29 @@ describe('ReactIncrementalUpdates', () => { pushToLog('B'), ); }); - expect(Scheduler).toHaveYielded([ - // A and B are pending. B is higher priority, so we'll render that first. - 'Committed: B', - // Because A comes first in the queue, we're now in rebase mode. B must - // be rebased on top of A. Also, in a layout effect, we received two new - // updates: C and D. C is user-blocking and D is synchronous. - // - // First render the synchronous update. What we're testing here is that - // B *is not dropped* even though it has lower than sync priority. That's - // because we already committed it. However, this render should not - // include C, because that update wasn't already committed. - 'Committed: BD', - 'Committed: BCD', - 'Committed: ABCD', - ]); + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded([ + 'Committed: B', + 'Committed: BCD', + 'Committed: ABCD', + ]); + } else { + expect(Scheduler).toHaveYielded([ + // A and B are pending. B is higher priority, so we'll render that first. + 'Committed: B', + // Because A comes first in the queue, we're now in rebase mode. B must + // be rebased on top of A. Also, in a layout effect, we received two new + // updates: C and D. C is user-blocking and D is synchronous. + // + // First render the synchronous update. What we're testing here is that + // B *is not dropped* even though it has lower than sync priority. That's + // because we already committed it. However, this render should not + // include C, because that update wasn't already committed. + 'Committed: BD', + 'Committed: BCD', + 'Committed: ABCD', + ]); + } expect(root).toMatchRenderedOutput('ABCD'); }); diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index d723c44a5d30..cffcf617824a 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -690,8 +690,15 @@ describe('ReactOffscreen', () => { ); // Before the inner update can finish, we receive another pair of updates. - setOuter(2); - setInner(2); + if (gate(flags => flags.enableUnifiedSyncLane)) { + React.startTransition(() => { + setOuter(2); + setInner(2); + }); + } else { + setOuter(2); + setInner(2); + } // Also, before either of these new updates are processed, the hidden // tree is revealed at high priority. diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js index e1a6ca49fd64..272979fab644 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js @@ -381,7 +381,9 @@ describe('ReactOffscreen', () => { expect(root).toMatchRenderedOutput(); await act(async () => { - setStep(1); + React.startTransition(() => { + setStep(1); + }); ReactNoop.flushSync(() => { setText('B'); }); @@ -513,8 +515,10 @@ describe('ReactOffscreen', () => { // Before the tree commits, schedule a concurrent event. The inner update // is to a tree that's just about to be hidden. - setOuter(2); - setInner(2); + startTransition(() => { + setOuter(2); + setInner(2); + }); // Commit the previous render. jest.runAllTimers(); diff --git a/packages/react-reconciler/src/__tests__/ReactTransition-test.js b/packages/react-reconciler/src/__tests__/ReactTransition-test.js index 560bb527b1b0..dd0b9e0d0fed 100644 --- a/packages/react-reconciler/src/__tests__/ReactTransition-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTransition-test.js @@ -934,16 +934,28 @@ describe('ReactTransition', () => { updateNormalPri(); }); - expect(Scheduler).toHaveYielded([ - // Finish transition update. - 'Normal pri: 0', - 'Commit', + if (gate(flags => flags.enableUnifiedSyncLane)) { + expect(Scheduler).toHaveYielded([ + 'Normal pri: 0', + 'Commit', - // Normal pri update. - 'Transition pri: 1', - 'Normal pri: 1', - 'Commit', - ]); + // Normal pri update. + 'Transition pri: 1', + 'Normal pri: 1', + 'Commit', + ]); + } else { + expect(Scheduler).toHaveYielded([ + // Finish transition update. + 'Normal pri: 0', + 'Commit', + + // Normal pri update. + 'Transition pri: 1', + 'Normal pri: 1', + 'Commit', + ]); + } expect(root).toMatchRenderedOutput('Transition pri: 1, Normal pri: 1'); }); diff --git a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js index 6321168d70aa..c4ca0ae1de99 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js +++ b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js @@ -1558,8 +1558,15 @@ describe('useMutableSource', () => { expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']); // Mutate in an event. This schedules a subscription update on a, which // already mounted, but not b, which hasn't subscribed yet. - mutateA('a1'); - mutateB('b1'); + if (gate(flags => flags.enableUnifiedSyncLane)) { + React.startTransition(() => { + mutateA('a1'); + mutateB('b1'); + }); + } else { + mutateA('a1'); + mutateB('b1'); + } // Mutate again at lower priority. This will schedule another subscription // update on a, but not b. When b mounts and subscriptions, the value it diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 8fcd13894c33..24dc67d37469 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -151,6 +151,8 @@ export const enableUseRefAccessWarning = false; // Enables time slicing for updates that aren't wrapped in startTransition. export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = __EXPERIMENTAL__; + // Adds an opt-in to time slicing for updates that aren't wrapped in // startTransition. Only relevant when enableSyncDefaultUpdates is disabled. export const allowConcurrentByDefault = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6d84e7661242..c43847ff08df 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -72,6 +72,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = true; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = false; export const allowConcurrentByDefault = true; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 9b5c301b7d5b..401362abc7f9 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -63,6 +63,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = false; export const allowConcurrentByDefault = false; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 45561ee4c5fe..96fd0dd2cca1 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -63,6 +63,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = __EXPERIMENTAL__; export const allowConcurrentByDefault = false; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index dace39942cf2..bcdcd974509a 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -62,6 +62,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = false; export const allowConcurrentByDefault = true; export const consoleManagedByDevToolsDuringStrictMode = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 47ed0529927c..f2dcfc00f9b5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -63,6 +63,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = false; export const allowConcurrentByDefault = true; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index bfcce69fe730..1dda88f6bc17 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -63,6 +63,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = __EXPERIMENTAL__; export const allowConcurrentByDefault = false; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index e45ef9d1af50..cd4535e761a1 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -63,6 +63,7 @@ export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; export const enableLegacyHidden = false; export const enableSyncDefaultUpdates = true; +export const enableUnifiedSyncLane = __EXPERIMENTAL__; export const allowConcurrentByDefault = true; export const enableCustomElementPropertySupport = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 882871de6f50..12b4ba8d35fd 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -24,6 +24,7 @@ export const enableProfilerNestedUpdateScheduledHook = __VARIANT__; export const disableSchedulerTimeoutInWorkLoop = __VARIANT__; export const enableLazyContextPropagation = __VARIANT__; export const enableSyncDefaultUpdates = __VARIANT__; +export const enableUnifiedSyncLane = __VARIANT__; export const consoleManagedByDevToolsDuringStrictMode = __VARIANT__; export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = __VARIANT__; export const enableClientRenderFallbackOnTextMismatch = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 89fae798d37b..73a592286cb8 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -31,6 +31,7 @@ export const { disableSchedulerTimeoutInWorkLoop, enableLazyContextPropagation, enableSyncDefaultUpdates, + enableUnifiedSyncLane, enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay, enableClientRenderFallbackOnTextMismatch, enableTransitionTracing, diff --git a/packages/use-subscription/src/__tests__/useSubscription-test.js b/packages/use-subscription/src/__tests__/useSubscription-test.js index 985312abd1d6..ee89c3dbb956 100644 --- a/packages/use-subscription/src/__tests__/useSubscription-test.js +++ b/packages/use-subscription/src/__tests__/useSubscription-test.js @@ -454,7 +454,13 @@ describe('useSubscription', () => { observableA.next('a-2'); // Update again - renderer.update(); + if (gate(flags => flags.enableUnifiedSyncLane)) { + React.startTransition(() => { + renderer.update(); + }); + } else { + renderer.update(); + } // Flush everything and ensure that the correct subscribable is used expect(Scheduler).toFlushAndYield([ From 0b974418c9a56f6c560298560265dcf4b65784bc Mon Sep 17 00:00:00 2001 From: mofeiZ <34200447+mofeiZ@users.noreply.github.com> Date: Fri, 6 Jan 2023 14:28:55 -0500 Subject: [PATCH 021/269] [Fizz] Fork Fizz instruction set for inline script and external runtime (#25862) ~~[Fizz] Duplicate completeBoundaryWithStyles to not reference globals~~ ## Summary Follow-up / cleanup PR to #25437 - `completeBoundaryWithStylesInlineLocals` is used by the Fizz external runtime, which bundles together all Fizz instruction functions (and is able to reference / rename `completeBoundary` and `resourceMap` as locals). - `completeBoundaryWithStylesInlineGlobals` is used by the Fizz inline script writer, which sends Fizz instruction functions on an as-needed basis. This version needs to reference `completeBoundary($RC)` and `resourceMap($RM)` as globals. Ideally, Closure would take care of inlining a shared implementation, but I couldn't figure out a zero-overhead inline due to lack of an `@inline` compiler directive. It seems that Closure thinks that a shared `completeBoundaryWithStyles` is too large and will always keep it as a separate function. I've also tried currying / writing a higher order function (`getCompleteBoundaryWithStyles`) with no luck ## How did you test this change? - generated Fizz inline instructions should be unchanged - bundle size for unstable_external_runtime should be slightly smaller (due to lack of globals) - `ReactDOMFizzServer-test.js` and `ReactDOMFloat-test.js` should be unaffected --- .../server/ReactDOMServerExternalRuntime.js | 2 +- .../ReactDOMFizzInlineClientRenderBoundary.js | 2 +- .../ReactDOMFizzInlineCompleteBoundary.js | 2 +- ...DOMFizzInlineCompleteBoundaryWithStyles.js | 2 +- .../ReactDOMFizzInlineCompleteSegment.js | 2 +- ...actDOMFizzInstructionSetExternalRuntime.js | 113 +++++++++++++++++ .../ReactDOMFizzInstructionSetInlineSource.js | 116 +++++++++++++++++ ...js => ReactDOMFizzInstructionSetShared.js} | 117 ++---------------- .../rollup/generate-inline-fizz-runtime.js | 6 +- 9 files changed, 249 insertions(+), 113 deletions(-) create mode 100644 packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js create mode 100644 packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineSource.js rename packages/react-dom-bindings/src/server/fizz-instruction-set/{ReactDOMFizzInstructionSet.js => ReactDOMFizzInstructionSetShared.js} (56%) diff --git a/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js b/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js index 63f5b64d93c8..830db3997030 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js +++ b/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js @@ -12,7 +12,7 @@ import { completeBoundaryWithStyles, completeBoundary, completeSegment, -} from './fizz-instruction-set/ReactDOMFizzInstructionSet'; +} from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime'; if (!window.$RC) { // TODO: Eventually remove, we currently need to set these globals for diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js index 96f750a8a46f..8cfd59e8d1b6 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js @@ -1,4 +1,4 @@ -import {clientRenderBoundary} from './ReactDOMFizzInstructionSet'; +import {clientRenderBoundary} from './ReactDOMFizzInstructionSetInlineSource'; // This is a string so Closure's advanced compilation mode doesn't mangle it. // eslint-disable-next-line dot-notation diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js index ed85f4e70a79..403fe847d988 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js @@ -1,4 +1,4 @@ -import {completeBoundary} from './ReactDOMFizzInstructionSet'; +import {completeBoundary} from './ReactDOMFizzInstructionSetInlineSource'; // This is a string so Closure's advanced compilation mode doesn't mangle it. // eslint-disable-next-line dot-notation diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js index 62760ee543b5..e3ffd1f58403 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js @@ -1,4 +1,4 @@ -import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSet'; +import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSetInlineSource'; // This is a string so Closure's advanced compilation mode doesn't mangle it. // eslint-disable-next-line dot-notation diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js index dbccb338b50e..d3dbd9eb50e9 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js @@ -1,4 +1,4 @@ -import {completeSegment} from './ReactDOMFizzInstructionSet'; +import {completeSegment} from './ReactDOMFizzInstructionSetInlineSource'; // This is a string so Closure's advanced compilation mode doesn't mangle it. // eslint-disable-next-line dot-notation diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js new file mode 100644 index 000000000000..4598accc5d54 --- /dev/null +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js @@ -0,0 +1,113 @@ +/* eslint-disable dot-notation */ + +// Instruction set for the Fizz external runtime + +import { + clientRenderBoundary, + completeBoundary, + completeSegment, + LOADED, + ERRORED, +} from './ReactDOMFizzInstructionSetShared'; + +export {clientRenderBoundary, completeBoundary, completeSegment}; + +const resourceMap = new Map(); + +// This function is almost identical to the version used by inline scripts +// (ReactDOMFizzInstructionSetInlineSource), with the exception of how we read +// completeBoundary and resourceMap +export function completeBoundaryWithStyles( + suspenseBoundaryID, + contentID, + styles, +) { + const precedences = new Map(); + const thisDocument = document; + let lastResource, node; + + // Seed the precedence list with existing resources + const nodes = thisDocument.querySelectorAll( + 'link[data-precedence],style[data-precedence]', + ); + for (let i = 0; (node = nodes[i++]); ) { + precedences.set(node.dataset['precedence'], (lastResource = node)); + } + + let i = 0; + const dependencies = []; + let style, href, precedence, attr, loadingState, resourceEl; + + function setStatus(s) { + this['s'] = s; + } + + while ((style = styles[i++])) { + let j = 0; + href = style[j++]; + // We check if this resource is already in our resourceMap and reuse it if so. + // If it is already loaded we don't return it as a depenendency since there is nothing + // to wait for + loadingState = resourceMap.get(href); + if (loadingState) { + if (loadingState['s'] !== 'l') { + dependencies.push(loadingState); + } + continue; + } + + // We construct our new resource element, looping over remaining attributes if any + // setting them to the Element. + resourceEl = thisDocument.createElement('link'); + resourceEl.href = href; + resourceEl.rel = 'stylesheet'; + resourceEl.dataset['precedence'] = precedence = style[j++]; + while ((attr = style[j++])) { + resourceEl.setAttribute(attr, style[j++]); + } + + // We stash a pending promise in our map by href which will resolve or reject + // when the underlying resource loads or errors. We add it to the dependencies + // array to be returned. + loadingState = resourceEl['_p'] = new Promise((re, rj) => { + resourceEl.onload = re; + resourceEl.onerror = rj; + }); + loadingState.then( + setStatus.bind(loadingState, LOADED), + setStatus.bind(loadingState, ERRORED), + ); + resourceMap.set(href, loadingState); + dependencies.push(loadingState); + + // The prior style resource is the last one placed at a given + // precedence or the last resource itself which may be null. + // We grab this value and then update the last resource for this + // precedence to be the inserted element, updating the lastResource + // pointer if needed. + const prior = precedences.get(precedence) || lastResource; + if (prior === lastResource) { + lastResource = resourceEl; + } + precedences.set(precedence, resourceEl); + + // Finally, we insert the newly constructed instance at an appropriate location + // in the Document. + if (prior) { + prior.parentNode.insertBefore(resourceEl, prior.nextSibling); + } else { + const head = thisDocument.head; + head.insertBefore(resourceEl, head.firstChild); + } + } + + Promise.all(dependencies).then( + completeBoundary.bind(null, suspenseBoundaryID, contentID, ''), + completeBoundary.bind( + null, + suspenseBoundaryID, + contentID, + 'Resource failed to load', + ), + ); +} diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineSource.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineSource.js new file mode 100644 index 000000000000..3c96d1a15af8 --- /dev/null +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineSource.js @@ -0,0 +1,116 @@ +/* eslint-disable dot-notation */ + +// Instruction set for Fizz inline scripts. +// DO NOT DIRECTLY IMPORT THIS FILE. This is the source for the compiled and +// minified code in ReactDOMFizzInstructionSetInlineCodeStrings. + +import { + clientRenderBoundary, + completeBoundary, + completeSegment, + LOADED, + ERRORED, +} from './ReactDOMFizzInstructionSetShared'; + +export {clientRenderBoundary, completeBoundary, completeSegment}; + +// This function is almost identical to the version used by the external +// runtime (ReactDOMFizzInstructionSetExternalRuntime), with the exception of +// how we read completeBoundaryImpl and resourceMap +export function completeBoundaryWithStyles( + suspenseBoundaryID, + contentID, + styles, +) { + const completeBoundaryImpl = window['$RC']; + const resourceMap = window['$RM']; + + const precedences = new Map(); + const thisDocument = document; + let lastResource, node; + + // Seed the precedence list with existing resources + const nodes = thisDocument.querySelectorAll( + 'link[data-precedence],style[data-precedence]', + ); + for (let i = 0; (node = nodes[i++]); ) { + precedences.set(node.dataset['precedence'], (lastResource = node)); + } + + let i = 0; + const dependencies = []; + let style, href, precedence, attr, loadingState, resourceEl; + + function setStatus(s) { + this['s'] = s; + } + + while ((style = styles[i++])) { + let j = 0; + href = style[j++]; + // We check if this resource is already in our resourceMap and reuse it if so. + // If it is already loaded we don't return it as a depenendency since there is nothing + // to wait for + loadingState = resourceMap.get(href); + if (loadingState) { + if (loadingState['s'] !== 'l') { + dependencies.push(loadingState); + } + continue; + } + + // We construct our new resource element, looping over remaining attributes if any + // setting them to the Element. + resourceEl = thisDocument.createElement('link'); + resourceEl.href = href; + resourceEl.rel = 'stylesheet'; + resourceEl.dataset['precedence'] = precedence = style[j++]; + while ((attr = style[j++])) { + resourceEl.setAttribute(attr, style[j++]); + } + + // We stash a pending promise in our map by href which will resolve or reject + // when the underlying resource loads or errors. We add it to the dependencies + // array to be returned. + loadingState = resourceEl['_p'] = new Promise((re, rj) => { + resourceEl.onload = re; + resourceEl.onerror = rj; + }); + loadingState.then( + setStatus.bind(loadingState, LOADED), + setStatus.bind(loadingState, ERRORED), + ); + resourceMap.set(href, loadingState); + dependencies.push(loadingState); + + // The prior style resource is the last one placed at a given + // precedence or the last resource itself which may be null. + // We grab this value and then update the last resource for this + // precedence to be the inserted element, updating the lastResource + // pointer if needed. + const prior = precedences.get(precedence) || lastResource; + if (prior === lastResource) { + lastResource = resourceEl; + } + precedences.set(precedence, resourceEl); + + // Finally, we insert the newly constructed instance at an appropriate location + // in the Document. + if (prior) { + prior.parentNode.insertBefore(resourceEl, prior.nextSibling); + } else { + const head = thisDocument.head; + head.insertBefore(resourceEl, head.firstChild); + } + } + + Promise.all(dependencies).then( + completeBoundaryImpl.bind(null, suspenseBoundaryID, contentID, ''), + completeBoundaryImpl.bind( + null, + suspenseBoundaryID, + contentID, + 'Resource failed to load', + ), + ); +} diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js similarity index 56% rename from packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet.js rename to packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js index 4abe722309f7..a2a8e402e2f2 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js @@ -1,12 +1,15 @@ /* eslint-disable dot-notation */ -const COMMENT_NODE = 8; -const SUSPENSE_START_DATA = '$'; -const SUSPENSE_END_DATA = '/$'; -const SUSPENSE_PENDING_START_DATA = '$?'; -const SUSPENSE_FALLBACK_START_DATA = '$!'; -const LOADED = 'l'; -const ERRORED = 'e'; +// Shared implementation and constants between the inline script and external +// runtime instruction sets. + +export const COMMENT_NODE = 8; +export const SUSPENSE_START_DATA = '$'; +export const SUSPENSE_END_DATA = '/$'; +export const SUSPENSE_PENDING_START_DATA = '$?'; +export const SUSPENSE_FALLBACK_START_DATA = '$!'; +export const LOADED = 'l'; +export const ERRORED = 'e'; // TODO: Symbols that are referenced outside this module use dynamic accessor // notation instead of dot notation to prevent Closure's advanced compilation @@ -42,106 +45,6 @@ export function clientRenderBoundary( } } -export function completeBoundaryWithStyles( - suspenseBoundaryID, - contentID, - styles, -) { - // TODO: In the non-inline version of the runtime, these don't need to be read - // from the global scope. - const completeBoundaryImpl = window['$RC']; - const resourceMap = window['$RM']; - - const precedences = new Map(); - const thisDocument = document; - let lastResource, node; - - // Seed the precedence list with existing resources - const nodes = thisDocument.querySelectorAll( - 'link[data-precedence],style[data-precedence]', - ); - for (let i = 0; (node = nodes[i++]); ) { - precedences.set(node.dataset['precedence'], (lastResource = node)); - } - - let i = 0; - const dependencies = []; - let style, href, precedence, attr, loadingState, resourceEl; - - function setStatus(s) { - this['s'] = s; - } - - while ((style = styles[i++])) { - let j = 0; - href = style[j++]; - // We check if this resource is already in our resourceMap and reuse it if so. - // If it is already loaded we don't return it as a depenendency since there is nothing - // to wait for - loadingState = resourceMap.get(href); - if (loadingState) { - if (loadingState['s'] !== 'l') { - dependencies.push(loadingState); - } - continue; - } - - // We construct our new resource element, looping over remaining attributes if any - // setting them to the Element. - resourceEl = thisDocument.createElement('link'); - resourceEl.href = href; - resourceEl.rel = 'stylesheet'; - resourceEl.dataset['precedence'] = precedence = style[j++]; - while ((attr = style[j++])) { - resourceEl.setAttribute(attr, style[j++]); - } - - // We stash a pending promise in our map by href which will resolve or reject - // when the underlying resource loads or errors. We add it to the dependencies - // array to be returned. - loadingState = resourceEl['_p'] = new Promise((re, rj) => { - resourceEl.onload = re; - resourceEl.onerror = rj; - }); - loadingState.then( - setStatus.bind(loadingState, LOADED), - setStatus.bind(loadingState, ERRORED), - ); - resourceMap.set(href, loadingState); - dependencies.push(loadingState); - - // The prior style resource is the last one placed at a given - // precedence or the last resource itself which may be null. - // We grab this value and then update the last resource for this - // precedence to be the inserted element, updating the lastResource - // pointer if needed. - const prior = precedences.get(precedence) || lastResource; - if (prior === lastResource) { - lastResource = resourceEl; - } - precedences.set(precedence, resourceEl); - - // Finally, we insert the newly constructed instance at an appropriate location - // in the Document. - if (prior) { - prior.parentNode.insertBefore(resourceEl, prior.nextSibling); - } else { - const head = thisDocument.head; - head.insertBefore(resourceEl, head.firstChild); - } - } - - Promise.all(dependencies).then( - completeBoundaryImpl.bind(null, suspenseBoundaryID, contentID, ''), - completeBoundaryImpl.bind( - null, - suspenseBoundaryID, - contentID, - 'Resource failed to load', - ), - ); -} - export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) { const contentNode = document.getElementById(contentID); // We'll detach the content node so that regardless of what happens next we don't leave in the tree. diff --git a/scripts/rollup/generate-inline-fizz-runtime.js b/scripts/rollup/generate-inline-fizz-runtime.js index d116c0620f71..226c844561be 100644 --- a/scripts/rollup/generate-inline-fizz-runtime.js +++ b/scripts/rollup/generate-inline-fizz-runtime.js @@ -39,7 +39,11 @@ async function main() { const fullEntryPath = instructionDir + '/' + entry; const compiler = new ClosureCompiler({ entry_point: fullEntryPath, - js: [fullEntryPath, instructionDir + '/ReactDOMFizzInstructionSet.js'], + js: [ + fullEntryPath, + instructionDir + '/ReactDOMFizzInstructionSetInlineSource.js', + instructionDir + '/ReactDOMFizzInstructionSetShared.js', + ], compilation_level: 'ADVANCED', module_resolution: 'NODE', // This is necessary to prevent Closure from inlining a Promise polyfill From 0b4f443020af386f2b48c47c074cb504ed672dc8 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Mon, 9 Jan 2023 15:46:48 -0500 Subject: [PATCH 022/269] [flow] enable enforce_local_inference_annotations (#25921) This setting is an incremental path to the next Flow version enforcing type annotations on most functions (except some inline callbacks). Used ``` node_modules/.bin/flow codemod annotate-functions-and-classes --write . ``` to add a majority of the types with some hand cleanup when for large inferred objects that should just be `Fiber` or weird constructs including `any`. Suppressed the remaining issues. Builds on #25918 --- packages/jest-react/src/internalAct.js | 2 +- packages/react-cache/src/ReactCacheOld.js | 7 +- .../react-client/src/ReactFlightClient.js | 11 +- .../src/ReactFlightClientStream.js | 1 + .../react-debug-tools/src/ReactDebugHooks.js | 18 +-- packages/react-devtools-core/src/backend.js | 4 +- .../react-devtools-core/src/standalone.js | 10 +- .../react-devtools-extensions/src/backend.js | 4 +- packages/react-devtools-inline/src/backend.js | 2 + .../react-devtools-inline/src/frontend.js | 1 + .../src/backend/agent.js | 2 +- .../src/backend/console.js | 2 + .../src/backend/legacy/renderer.js | 2 +- .../src/backend/legacy/utils.js | 2 + .../src/backend/profilingHooks.js | 2 +- .../src/backend/renderer.js | 34 +++--- .../src/backend/views/Highlighter/Overlay.js | 8 +- .../src/backend/views/Highlighter/index.js | 4 +- .../src/devtools/ContextMenu/ContextMenu.js | 11 +- .../devtools/ContextMenu/ContextMenuItem.js | 2 +- .../src/devtools/cache.js | 4 +- .../src/devtools/store.js | 6 +- .../views/Components/ComponentSearchInput.js | 3 +- .../devtools/views/Components/Components.js | 1 + .../views/Components/EditableValue.js | 3 + .../src/devtools/views/Components/Element.js | 5 + .../Components/InspectedElementContextTree.js | 1 + .../Components/InspectedElementHooksTree.js | 1 + .../InspectedElementSuspenseToggle.js | 2 +- .../src/devtools/views/Components/KeyValue.js | 11 +- .../NativeStyleEditor/AutoSizeInput.js | 1 + .../NativeStyleEditor/StyleEditor.js | 3 + .../views/Components/NewArrayValue.js | 3 +- .../devtools/views/Components/NewKeyValue.js | 8 +- .../devtools/views/Components/OwnersStack.js | 2 +- .../src/devtools/views/Components/Tree.js | 3 +- .../src/devtools/views/ModalDialog.js | 2 +- .../views/Profiler/CommitTreeBuilder.js | 2 +- .../views/Profiler/SidebarEventInfo.js | 3 +- .../Profiler/SidebarSelectedFiberInfo.js | 1 + .../views/Profiler/SnapshotSelector.js | 4 + .../src/devtools/views/Profiler/Tooltip.js | 12 +- .../src/devtools/views/SearchInput.js | 2 + .../src/devtools/views/hooks.js | 6 +- packages/react-devtools-shared/src/hook.js | 35 ++++-- .../react-window/src/createGridComponent.js | 27 +++-- .../react-window/src/createListComponent.js | 25 ++-- .../react-window/src/shouldComponentUpdate.js | 3 +- .../src/registerDevToolsEventLogger.js | 2 +- .../src/app/DeeplyNestedComponents/index.js | 6 +- .../src/app/EditableProps/index.js | 13 +- .../src/app/ElementTypes/index.js | 2 +- .../src/app/ErrorBoundaries/index.js | 7 +- .../src/app/Iframe/index.js | 1 + .../src/app/InlineWarnings/index.js | 19 +++ .../src/app/InspectableElements/Contexts.js | 66 ++++++++--- .../app/InspectableElements/CustomHooks.js | 4 +- .../app/InspectableElements/CustomObject.js | 2 +- .../src/app/SuspenseTree/index.js | 3 +- .../react-devtools-shell/src/app/devtools.js | 2 +- .../react-devtools-shell/src/app/index.js | 5 +- .../src/e2e-apps/ListApp.js | 1 + .../src/e2e-apps/ListAppLegacy.js | 5 +- .../src/e2e-regression/app-legacy.js | 2 +- .../src/e2e-regression/app.js | 2 +- .../src/perf-regression/apps/LargeSubtree.js | 2 +- .../src/EventTooltip.js | 5 +- .../react-devtools-timeline/src/Timeline.js | 1 + .../src/TimelineSearchInput.js | 3 +- .../src/content-views/ReactMeasuresView.js | 4 +- .../src/view-base/Surface.js | 6 +- .../src/client/ReactDOMComponent.js | 7 +- .../src/client/ReactDOMHostConfig.js | 4 +- .../src/client/ReactDOMInput.js | 4 +- .../src/client/ReactDOMSelect.js | 2 +- .../src/client/getNodeForCharacterOffset.js | 4 +- .../src/client/inputValueTracking.js | 5 +- .../src/events/DOMEventProperties.js | 2 +- .../src/events/DOMPluginEventSystem.js | 1 + .../src/events/ReactDOMEventListener.js | 16 +-- .../src/events/ReactDOMEventReplaying.js | 2 +- .../src/events/SyntheticEvent.js | 23 ++-- .../events/plugins/BeforeInputEventPlugin.js | 20 ++-- .../src/events/plugins/ChangeEventPlugin.js | 40 ++++--- .../src/events/plugins/SelectEventPlugin.js | 6 +- .../src/server/ReactDOMServerFormatConfig.js | 18 ++- .../src/server/escapeTextForBrowser.js | 2 +- .../src/shared/DOMProperty.js | 3 +- packages/react-dom/src/client/ReactDOMRoot.js | 4 + .../src/server/ReactDOMFizzServerNode.js | 5 +- .../src/server/ReactDOMFizzStaticNode.js | 2 +- .../src/server/ReactDOMLegacyServerImpl.js | 2 + .../server/ReactDOMLegacyServerNodeStream.js | 2 + .../src/dom/create-event-handle/Focus.js | 5 +- .../react-native-renderer/src/ReactFabric.js | 2 + .../src/ReactFabricHostConfig.js | 1 + .../src/ReactNativeBridgeEventPlugin.js | 7 ++ .../src/ReactNativeEventEmitter.js | 1 + .../src/ReactNativeFiberInspector.js | 5 + .../src/ReactNativeGetListeners.js | 1 + .../src/ReactNativeRenderer.js | 1 + .../src/legacy-events/EventBatching.js | 1 + packages/react-reconciler/src/DebugTracing.js | 4 +- .../react-reconciler/src/ReactChildFiber.js | 10 +- packages/react-reconciler/src/ReactFiber.js | 1 + .../src/ReactFiberBeginWork.js | 111 ++++++++++-------- .../src/ReactFiberCacheComponent.js | 3 +- .../src/ReactFiberClassComponent.js | 34 +++--- .../src/ReactFiberClassUpdateQueue.js | 2 +- .../src/ReactFiberCommitWork.js | 17 +-- .../react-reconciler/src/ReactFiberHooks.js | 6 +- .../src/ReactFiberHydrationContext.js | 2 +- .../src/ReactFiberReconciler.js | 2 +- .../react-reconciler/src/ReactFiberRoot.js | 10 +- .../react-reconciler/src/ReactFiberScope.js | 17 ++- .../react-reconciler/src/ReactFiberThrow.js | 1 + .../src/ReactFiberTreeReflection.js | 2 +- .../src/ReactFiberWorkLoop.js | 37 +++--- .../src/ReactStrictModeWarnings.js | 2 +- .../react-refresh/src/ReactFreshRuntime.js | 14 ++- .../ReactFlightDOMRelayClientHostConfig.js | 1 + .../src/ReactFlightDOMClient.js | 4 +- .../src/ReactFlightDOMServerNode.js | 4 +- .../src/ReactFlightWebpackNodeLoader.js | 4 +- .../src/ReactFlightWebpackNodeRegister.js | 12 +- .../src/ReactFlightWebpackPlugin.js | 4 +- .../ReactFlightNativeRelayClientHostConfig.js | 1 + .../src/ReactFizzClassComponent.js | 14 ++- packages/react-server/src/ReactFizzServer.js | 13 +- .../react-server/src/ReactFlightServer.js | 7 +- .../src/ReactTestRenderer.js | 4 + packages/react/src/ReactAct.js | 9 +- packages/react/src/ReactChildren.js | 1 + packages/react/src/ReactContext.js | 11 +- packages/react/src/ReactLazy.js | 2 + packages/scheduler/src/forks/Scheduler.js | 10 +- packages/scheduler/src/forks/SchedulerMock.js | 1 + .../scheduler/src/forks/SchedulerPostTask.js | 2 +- packages/shared/ReactErrorUtils.js | 1 + packages/shared/checkPropTypes.js | 2 +- packages/shared/invokeGuardedCallbackImpl.js | 3 + .../src/useSyncExternalStoreWithSelector.js | 2 +- scripts/flow/config/flowconfig | 2 +- scripts/flow/createFlowConfigs.js | 2 - 144 files changed, 689 insertions(+), 371 deletions(-) diff --git a/packages/jest-react/src/internalAct.js b/packages/jest-react/src/internalAct.js index d078c888c75e..1b5349554c40 100644 --- a/packages/jest-react/src/internalAct.js +++ b/packages/jest-react/src/internalAct.js @@ -122,7 +122,7 @@ export function act(scope: () => Thenable | T): Thenable { } } -function flushActWork(resolve, reject) { +function flushActWork(resolve: () => void, reject: (error: any) => void) { if (Scheduler.unstable_hasPendingWork()) { try { Scheduler.unstable_flushUntilNextPaint(); diff --git a/packages/react-cache/src/ReactCacheOld.js b/packages/react-cache/src/ReactCacheOld.js index 3c83b76cacbc..99716076984c 100644 --- a/packages/react-cache/src/ReactCacheOld.js +++ b/packages/react-cache/src/ReactCacheOld.js @@ -7,7 +7,7 @@ * @flow */ -import type {Thenable} from 'shared/ReactTypes'; +import type {ReactContext, Thenable} from 'shared/ReactTypes'; import * as React from 'react'; @@ -48,7 +48,7 @@ const ReactCurrentDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED .ReactCurrentDispatcher; -function readContext(Context) { +function readContext(Context: ReactContext) { const dispatcher = ReactCurrentDispatcher.current; if (dispatcher === null) { // This wasn't being minified but we're going to retire this package anyway. @@ -62,6 +62,7 @@ function readContext(Context) { return dispatcher.readContext(Context); } +// $FlowFixMe[missing-local-annot] function identityHashFn(input) { if (__DEV__) { if ( @@ -133,7 +134,7 @@ function accessResult( } } -function deleteEntry(resource, key) { +function deleteEntry(resource: any, key: mixed) { const entriesForResource = entries.get(resource); if (entriesForResource !== undefined) { entriesForResource.delete(key); diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index c023a1feca63..3c9d26338b66 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -94,6 +94,7 @@ type SomeChunk = | InitializedChunk | ErroredChunk; +// $FlowFixMe[missing-this-annot] function Chunk(status: any, value: any, reason: any, response: Response) { this.status = status; this.value = value; @@ -104,6 +105,7 @@ function Chunk(status: any, value: any, reason: any, response: Response) { Chunk.prototype = (Object.create(Promise.prototype): any); // TODO: This doesn't return a new Promise chain unlike the real .then Chunk.prototype.then = function( + this: SomeChunk, resolve: (value: T) => mixed, reject: (reason: mixed) => mixed, ) { @@ -369,7 +371,11 @@ export function reportGlobalError(response: Response, error: Error): void { }); } -function createElement(type, key, props): React$Element { +function createElement( + type: mixed, + key: mixed, + props: mixed, +): React$Element { const element: any = { // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, @@ -446,6 +452,7 @@ function createModelResolver( value: null, }; } + // $FlowFixMe[missing-local-annot] return value => { parentObject[key] = value; blocked.deps--; @@ -465,7 +472,7 @@ function createModelResolver( } function createModelReject(chunk: SomeChunk) { - return error => triggerErrorOnChunk(chunk, error); + return (error: mixed) => triggerErrorOnChunk(chunk, error); } export function parseModelString( diff --git a/packages/react-client/src/ReactFlightClientStream.js b/packages/react-client/src/ReactFlightClientStream.js index c3f3c48b1a5b..4709ca8085dd 100644 --- a/packages/react-client/src/ReactFlightClientStream.js +++ b/packages/react-client/src/ReactFlightClientStream.js @@ -122,6 +122,7 @@ export function processBinaryChunk( } function createFromJSONCallback(response: Response) { + // $FlowFixMe[missing-this-annot] return function(key: string, value: JSONValue) { if (typeof value === 'string') { // We can't use .bind here because we need the "this" value. diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 5971c38a97af..fbe4dc2f360a 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -350,7 +350,7 @@ const Dispatcher: DispatcherType = { // create a proxy to throw a custom error // in case future versions of React adds more hooks const DispatcherProxyHandler = { - get(target, prop) { + get(target: DispatcherType, prop: string) { if (target.hasOwnProperty(prop)) { return target[prop]; } @@ -404,7 +404,7 @@ export type HooksTree = Array; let mostLikelyAncestorIndex = 0; -function findSharedIndex(hookStack, rootStack, rootIndex) { +function findSharedIndex(hookStack: any, rootStack: any, rootIndex: number) { const source = rootStack[rootIndex].source; hookSearch: for (let i = 0; i < hookStack.length; i++) { if (hookStack[i].source === source) { @@ -425,7 +425,7 @@ function findSharedIndex(hookStack, rootStack, rootIndex) { return -1; } -function findCommonAncestorIndex(rootStack, hookStack) { +function findCommonAncestorIndex(rootStack: any, hookStack: any) { let rootIndex = findSharedIndex( hookStack, rootStack, @@ -446,7 +446,7 @@ function findCommonAncestorIndex(rootStack, hookStack) { return -1; } -function isReactWrapper(functionName, primitiveName) { +function isReactWrapper(functionName: any, primitiveName: string) { if (!functionName) { return false; } @@ -460,7 +460,7 @@ function isReactWrapper(functionName, primitiveName) { ); } -function findPrimitiveIndex(hookStack, hook) { +function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) { const stackCache = getPrimitiveStackCache(); const primitiveStack = stackCache.get(hook.primitive); if (primitiveStack === undefined) { @@ -488,7 +488,7 @@ function findPrimitiveIndex(hookStack, hook) { return -1; } -function parseTrimmedStack(rootStack, hook) { +function parseTrimmedStack(rootStack: any, hook: HookLogEntry) { // Get the stack trace between the primitive hook function and // the root function call. I.e. the stack frames of custom hooks. const hookStack = ErrorStackParser.parse(hook.stackError); @@ -520,8 +520,8 @@ function parseCustomHookName(functionName: void | string): string { } function buildTree( - rootStack, - readHookLog, + rootStack: any, + readHookLog: Array, includeHooksSource: boolean, ): HooksTree { const rootChildren = []; @@ -764,7 +764,7 @@ function inspectHooksOfForwardRef( return buildTree(rootStack, readHookLog, includeHooksSource); } -function resolveDefaultProps(Component, baseProps) { +function resolveDefaultProps(Component: any, baseProps: any) { if (Component && Component.defaultProps) { // Resolve default props. Taken from ReactElement const props = assign({}, baseProps); diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 70048a9de6cd..f14ece0eefd1 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -44,7 +44,7 @@ const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; let savedComponentFilters: Array = getDefaultComponentFilters(); -function debug(methodName: string, ...args) { +function debug(methodName: string, ...args: Array) { if (__DEBUG__) { console.log( `%c[core/backend] %c${methodName}`, @@ -276,7 +276,7 @@ export function connectToDevTools(options: ?ConnectOptions) { scheduleRetry(); } - function handleMessage(event) { + function handleMessage(event: MessageEvent) { let data; try { if (typeof event.data === 'string') { diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index 1b11a4710099..22cf1677003e 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -86,11 +86,12 @@ let bridge: FrontendBridge | null = null; let store: Store | null = null; let root = null; -const log = (...args) => console.log('[React DevTools]', ...args); -log.warn = (...args) => console.warn('[React DevTools]', ...args); -log.error = (...args) => console.error('[React DevTools]', ...args); +const log = (...args: Array) => console.log('[React DevTools]', ...args); +log.warn = (...args: Array) => console.warn('[React DevTools]', ...args); +log.error = (...args: Array) => + console.error('[React DevTools]', ...args); -function debug(methodName: string, ...args) { +function debug(methodName: string, ...args: Array) { if (__DEBUG__) { console.log( `%c[core/standalone] %c${methodName}`, @@ -166,6 +167,7 @@ function onDisconnected() { disconnectedCallback(); } +// $FlowFixMe[missing-local-annot] function onError({code, message}) { safeUnmount(); diff --git a/packages/react-devtools-extensions/src/backend.js b/packages/react-devtools-extensions/src/backend.js index ad55ed6d50fd..5c397c3ee212 100644 --- a/packages/react-devtools-extensions/src/backend.js +++ b/packages/react-devtools-extensions/src/backend.js @@ -8,6 +8,7 @@ let welcomeHasInitialized = false; +// $FlowFixMe[missing-local-annot] function welcome(event) { if ( event.source !== window || @@ -42,7 +43,7 @@ function welcome(event) { window.addEventListener('message', welcome); -function setup(hook) { +function setup(hook: any) { if (hook == null) { // DevTools didn't get injected into this page (maybe b'c of the contentType). return; @@ -55,6 +56,7 @@ function setup(hook) { const bridge = new Bridge({ listen(fn) { + // $FlowFixMe[missing-local-annot] const listener = event => { if ( event.source !== window || diff --git a/packages/react-devtools-inline/src/backend.js b/packages/react-devtools-inline/src/backend.js index 97d2368ac169..659b6c8521c9 100644 --- a/packages/react-devtools-inline/src/backend.js +++ b/packages/react-devtools-inline/src/backend.js @@ -10,6 +10,7 @@ import type {BackendBridge} from 'react-devtools-shared/src/bridge'; import type {Wall} from 'react-devtools-shared/src/types'; function startActivation(contentWindow: any, bridge: BackendBridge) { + // $FlowFixMe[missing-local-annot] const onSavedPreferences = data => { // This is the only message we're listening for, // so it's safe to cleanup after we've received it. @@ -96,6 +97,7 @@ export function createBridge(contentWindow: any, wall?: Wall): BackendBridge { if (wall == null) { wall = { listen(fn) { + // $FlowFixMe[missing-local-annot] const onMessage = ({data}) => { fn(data); }; diff --git a/packages/react-devtools-inline/src/frontend.js b/packages/react-devtools-inline/src/frontend.js index 272fbba0b926..018ce76c5df2 100644 --- a/packages/react-devtools-inline/src/frontend.js +++ b/packages/react-devtools-inline/src/frontend.js @@ -37,6 +37,7 @@ export function createBridge(contentWindow: any, wall?: Wall): FrontendBridge { if (wall == null) { wall = { listen(fn) { + // $FlowFixMe[missing-local-annot] const onMessage = ({data}) => { fn(data); }; diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 78812286bf21..0af2033cf955 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -42,7 +42,7 @@ import type {ComponentFilter} from '../types'; import {isSynchronousXHRSupported} from './utils'; import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools'; -const debug = (methodName, ...args) => { +const debug = (methodName: string, ...args: Array) => { if (__DEBUG__) { console.log( `%cAgent %c${methodName}`, diff --git a/packages/react-devtools-shared/src/backend/console.js b/packages/react-devtools-shared/src/backend/console.js index 5753d910eba1..61ead6d7c478 100644 --- a/packages/react-devtools-shared/src/backend/console.js +++ b/packages/react-devtools-shared/src/backend/console.js @@ -197,6 +197,7 @@ export function patch({ ? targetConsole[method].__REACT_DEVTOOLS_ORIGINAL_METHOD__ : targetConsole[method]); + // $FlowFixMe[missing-local-annot] const overrideMethod = (...args) => { let shouldAppendWarningStack = false; if (method !== 'log') { @@ -335,6 +336,7 @@ export function patchForStrictMode() { ? targetConsole[method].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ : targetConsole[method]); + // $FlowFixMe[missing-local-annot] const overrideMethod = (...args) => { if (!consoleSettingsRef.hideConsoleLogsInStrictMode) { // Dim the text color of the double logs if we're not diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 8d02e853a406..1b667cf12218 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -195,7 +195,7 @@ export function attach( return ((internalInstanceToIDMap.get(internalInstance): any): number); } - function areEqualArrays(a, b) { + function areEqualArrays(a: Array, b: Array) { if (a.length !== b.length) { return false; } diff --git a/packages/react-devtools-shared/src/backend/legacy/utils.js b/packages/react-devtools-shared/src/backend/legacy/utils.js index 581f8e9222d2..bc443c1dd164 100644 --- a/packages/react-devtools-shared/src/backend/legacy/utils.js +++ b/packages/react-devtools-shared/src/backend/legacy/utils.js @@ -11,6 +11,7 @@ import type {InternalInstance} from './renderer'; export function decorate(object: Object, attr: string, fn: Function): Function { const old = object[attr]; + // $FlowFixMe[missing-this-annot] webpack config needs to be updated to allow `this` type annotations object[attr] = function(instance: InternalInstance) { return fn.call(this, old, arguments); }; @@ -34,6 +35,7 @@ export function restoreMany(source: Object, olds: Object): void { } } +// $FlowFixMe[missing-this-annot] webpack config needs to be updated to allow `this` type annotations export function forceUpdate(instance: InternalInstance): void { if (typeof instance.forceUpdate === 'function') { instance.forceUpdate(); diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js index f83875b14d1a..47d173c55c3c 100644 --- a/packages/react-devtools-shared/src/backend/profilingHooks.js +++ b/packages/react-devtools-shared/src/backend/profilingHooks.js @@ -204,7 +204,7 @@ export function createProfilingHooks({ } } - function markAndClear(markName) { + function markAndClear(markName: string) { // This method won't be called unless these functions are defined, so we can skip the extra typeof check. ((performanceTarget: any): Performance).mark(markName); ((performanceTarget: any): Performance).clearMarks(markName); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 68425faac8d7..54f95a2fbea6 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -2546,7 +2546,7 @@ export function attach( // We don't patch any methods so there is no cleanup. } - function rootSupportsProfiling(root) { + function rootSupportsProfiling(root: any) { if (root.memoizedInteractions != null) { // v16 builds include this field for the scheduler/tracing API. return true; @@ -2610,7 +2610,7 @@ export function attach( } } - function getUpdatersList(root): Array | null { + function getUpdatersList(root: any): Array | null { return root.memoizedUpdaters != null ? Array.from(root.memoizedUpdaters) .filter(fiber => getFiberIDUnsafe(fiber) !== null) @@ -2618,7 +2618,7 @@ export function attach( : null; } - function handleCommitFiberUnmount(fiber) { + function handleCommitFiberUnmount(fiber: any) { // If the untrackFiberSet already has the unmounted Fiber, this means we've already // recordedUnmount, so we don't need to do it again. If we don't do this, we might // end up double-deleting Fibers in some cases (like Legacy Suspense). @@ -2630,7 +2630,7 @@ export function attach( } } - function handlePostCommitFiberRoot(root) { + function handlePostCommitFiberRoot(root: any) { if (isProfiling && rootSupportsProfiling(root)) { if (currentCommitProfilingMetadata !== null) { const {effectDuration, passiveEffectDuration} = getEffectDurations( @@ -2644,7 +2644,7 @@ export function attach( } } - function handleCommitFiberRoot(root, priorityLevel) { + function handleCommitFiberRoot(root: any, priorityLevel: void | number) { const current = root.current; const alternate = current.alternate; @@ -2805,18 +2805,18 @@ export function attach( } } - function getDisplayNameForFiberID(id) { + function getDisplayNameForFiberID(id: number) { const fiber = idToArbitraryFiberMap.get(id); return fiber != null ? getDisplayNameForFiber(((fiber: any): Fiber)) : null; } - function getFiberForNative(hostInstance) { + function getFiberForNative(hostInstance: NativeType) { return renderer.findFiberByHostInstance(hostInstance); } function getFiberIDForNative( - hostInstance, - findNearestUnfilteredAncestor = false, + hostInstance: NativeType, + findNearestUnfilteredAncestor: boolean = false, ) { let fiber = renderer.findFiberByHostInstance(hostInstance); if (fiber != null) { @@ -2832,7 +2832,7 @@ export function attach( // This function is copied from React and should be kept in sync: // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberTreeReflection.js - function assertIsMounted(fiber) { + function assertIsMounted(fiber: Fiber) { if (getNearestMountedFiber(fiber) !== fiber) { throw new Error('Unable to find node on an unmounted component.'); } @@ -3712,7 +3712,7 @@ export function attach( }; } - function logElementToConsole(id) { + function logElementToConsole(id: number) { const result = isMostRecentlyInspectedElementCurrent(id) ? mostRecentlyInspectedElement : inspectElementRaw(id); @@ -4149,7 +4149,7 @@ export function attach( // null (do nothing) const forceErrorForFiberIDs = new Map(); - function shouldErrorFiberAccordingToMap(fiber) { + function shouldErrorFiberAccordingToMap(fiber: any) { if (typeof setErrorHandler !== 'function') { throw new Error( 'Expected overrideError() to not get called for earlier React versions.', @@ -4185,7 +4185,7 @@ export function attach( return status; } - function overrideError(id, forceError) { + function overrideError(id: number, forceError: boolean) { if ( typeof setErrorHandler !== 'function' || typeof scheduleUpdate !== 'function' @@ -4214,12 +4214,12 @@ export function attach( const forceFallbackForSuspenseIDs = new Set(); - function shouldSuspendFiberAccordingToSet(fiber) { + function shouldSuspendFiberAccordingToSet(fiber: any) { const maybeID = getFiberIDUnsafe(((fiber: any): Fiber)); return maybeID !== null && forceFallbackForSuspenseIDs.has(maybeID); } - function overrideSuspense(id, forceFallback) { + function overrideSuspense(id: number, forceFallback: boolean) { if ( typeof setSuspenseHandler !== 'function' || typeof scheduleUpdate !== 'function' @@ -4317,7 +4317,9 @@ export function attach( return true; } - function updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath) { + function updateTrackedPathStateAfterMount( + mightSiblingsBeOnTrackedPath: boolean, + ) { // updateTrackedPathStateBeforeMount() told us whether to match siblings. // Now that we're entering siblings, let's use that information. mightBeOnTrackedPath = mightSiblingsBeOnTrackedPath; diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js index 5414f3ef0efe..f19a335bf79d 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js @@ -277,7 +277,11 @@ export default class Overlay { } } -function findTipPos(dims, bounds, tipSize) { +function findTipPos( + dims: Box, + bounds: Box, + tipSize: {height: number, width: number}, +) { const tipHeight = Math.max(tipSize.height, 20); const tipWidth = Math.max(tipSize.width, 60); const margin = 5; @@ -314,7 +318,7 @@ function findTipPos(dims, bounds, tipSize) { }; } -function boxWrap(dims, what, node) { +function boxWrap(dims: any, what: string, node: HTMLElement) { assign(node.style, { borderTopWidth: dims[what + 'Top'] + 'px', borderLeftWidth: dims[what + 'Left'] + 'px', diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index 6793e92e51dd..dada3978e2e3 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -38,7 +38,7 @@ export default function setupHighlighter( registerListenersOnWindow(window); } - function registerListenersOnWindow(window) { + function registerListenersOnWindow(window: any) { // This plug-in may run in non-DOM environments (e.g. React Native). if (window && typeof window.addEventListener === 'function') { window.addEventListener('click', onClick, true); @@ -66,7 +66,7 @@ export default function setupHighlighter( iframesListeningTo = new Set(); } - function removeListenersOnWindow(window) { + function removeListenersOnWindow(window: any) { // This plug-in may run in non-DOM environments (e.g. React Native). if (window && typeof window.removeEventListener === 'function') { window.removeEventListener('click', onClick, true); diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js index a800b58fc37e..3b4d2b696496 100644 --- a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js +++ b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js @@ -81,7 +81,15 @@ export default function ContextMenu({children, id}: Props): React.Node { }, []); useEffect(() => { - const showMenuFn = ({data, pageX, pageY}) => { + const showMenuFn = ({ + data, + pageX, + pageY, + }: { + data: any, + pageX: number, + pageY: number, + }) => { setState({data, isVisible: true, pageX, pageY}); }; const hideMenuFn = () => setState(HIDDEN_STATE); @@ -96,6 +104,7 @@ export default function ContextMenu({children, id}: Props): React.Node { const menu = ((menuRef.current: any): HTMLElement); const container = containerRef.current; if (container !== null) { + // $FlowFixMe[missing-local-annot] const hideUnlessContains = event => { if (!menu.contains(event.target)) { hideMenu(); diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem.js b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem.js index 3de6acef774b..d634f5eeef28 100644 --- a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem.js +++ b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem.js @@ -28,7 +28,7 @@ export default function ContextMenuItem({ }: Props): React.Node { const {hideMenu} = useContext(RegistryContext); - const handleClick = event => { + const handleClick = (event: any) => { onClick(); hideMenu(); }; diff --git a/packages/react-devtools-shared/src/devtools/cache.js b/packages/react-devtools-shared/src/devtools/cache.js index 6afbc64ada41..c8f62ba3853c 100644 --- a/packages/react-devtools-shared/src/devtools/cache.js +++ b/packages/react-devtools-shared/src/devtools/cache.js @@ -7,7 +7,7 @@ * @flow */ -import type {Thenable} from 'shared/ReactTypes'; +import type {ReactContext, Thenable} from 'shared/ReactTypes'; import * as React from 'react'; import {createContext} from 'react'; @@ -63,7 +63,7 @@ const Rejected = 2; const ReactCurrentDispatcher = (React: any) .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher; -function readContext(Context) { +function readContext(Context: ReactContext) { const dispatcher = ReactCurrentDispatcher.current; if (dispatcher === null) { throw new Error( diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 71d53c886e67..fd9264eed5a2 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -46,7 +46,7 @@ import type { } from 'react-devtools-shared/src/bridge'; import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError'; -const debug = (methodName, ...args) => { +const debug = (methodName: string, ...args: Array) => { if (__DEBUG__) { console.log( `%cStore %c${methodName}`, @@ -1146,7 +1146,7 @@ export default class Store extends EventEmitter<{ debug(`Remove root ${id}`); } - const recursivelyDeleteElements = elementID => { + const recursivelyDeleteElements = (elementID: number) => { const element = this._idToElement.get(elementID); this._idToElement.delete(elementID); if (element) { @@ -1431,7 +1431,7 @@ export default class Store extends EventEmitter<{ // but the downstream errors they cause will be reported as bugs. // For example, https://github.com/facebook/react/issues/21402 // Emitting an error event allows the ErrorBoundary to show the original error. - _throwAndEmitError(error: Error) { + _throwAndEmitError(error: Error): empty { this.emit('error', error); // Throwing is still valuable for local development diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ComponentSearchInput.js b/packages/react-devtools-shared/src/devtools/views/Components/ComponentSearchInput.js index 3085f0a2120e..f84e29bf7302 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/ComponentSearchInput.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/ComponentSearchInput.js @@ -19,7 +19,8 @@ export default function ComponentSearchInput(props: Props): React.Node { const {searchIndex, searchResults, searchText} = useContext(TreeStateContext); const dispatch = useContext(TreeDispatcherContext); - const search = text => dispatch({type: 'SET_SEARCH_TEXT', payload: text}); + const search = (text: string) => + dispatch({type: 'SET_SEARCH_TEXT', payload: text}); const goToNextResult = () => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}); const goToPreviousResult = () => dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'}); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.js b/packages/react-devtools-shared/src/devtools/views/Components/Components.js index 9535e598174d..ba2e99cf3439 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Components.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.js @@ -100,6 +100,7 @@ function Components(_: {}) { onResizeEnd = () => dispatch({type: 'ACTION_SET_IS_RESIZING', payload: false}); + // $FlowFixMe[missing-local-annot] onResize = event => { const resizeElement = resizeElementRef.current; const wrapperElement = wrapperElementRef.current; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/EditableValue.js b/packages/react-devtools-shared/src/devtools/views/Components/EditableValue.js index 03fe9d7bf7fb..0511a388a574 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/EditableValue.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/EditableValue.js @@ -36,6 +36,7 @@ export default function EditableValue({ externalValue: value, }); + // $FlowFixMe[missing-local-annot] const handleChange = ({target}) => dispatch({ type: 'UPDATE', @@ -43,6 +44,7 @@ export default function EditableValue({ externalValue: value, }); + // $FlowFixMe[missing-local-annot] const handleCheckBoxToggle = ({target}) => { dispatch({ type: 'UPDATE', @@ -58,6 +60,7 @@ export default function EditableValue({ overrideValue(path, target.checked); }; + // $FlowFixMe[missing-local-annot] const handleKeyDown = event => { // Prevent keydown events from e.g. change selected element in the tree event.stopPropagation(); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.js b/packages/react-devtools-shared/src/devtools/views/Components/Element.js index bb850398df32..c43164b51103 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Element.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.js @@ -75,6 +75,7 @@ export default function Element({data, index, style}: Props): React.Node { } }; + // $FlowFixMe[missing-local-annot] const handleClick = ({metaKey}) => { if (id !== null) { logEvent({ @@ -99,6 +100,7 @@ export default function Element({data, index, style}: Props): React.Node { setIsHovered(false); }; + // $FlowFixMe[missing-local-annot] const handleKeyDoubleClick = event => { // Double clicks on key value are used for text selection (if the text has been truncated). // They should not enter the owners tree view. @@ -220,6 +222,7 @@ export default function Element({data, index, style}: Props): React.Node { } // Prevent double clicks on toggle from drilling into the owner list. +// $FlowFixMe[missing-local-annot] const swallowDoubleClick = event => { event.preventDefault(); event.stopPropagation(); @@ -233,6 +236,7 @@ type ExpandCollapseToggleProps = { function ExpandCollapseToggle({element, store}: ExpandCollapseToggleProps) { const {children, id, isCollapsed} = element; + // $FlowFixMe[missing-local-annot] const toggleCollapsed = event => { event.preventDefault(); event.stopPropagation(); @@ -240,6 +244,7 @@ function ExpandCollapseToggle({element, store}: ExpandCollapseToggleProps) { store.toggleIsCollapsed(id, !isCollapsed); }; + // $FlowFixMe[missing-local-annot] const stopPropagation = event => { // Prevent the row from selecting event.stopPropagation(); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js index f04a0fd8e067..eb74c244d9d1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js @@ -53,6 +53,7 @@ export default function InspectedElementContextTree({ // We add an object with a "value" key as a wrapper around Context data // so that we can use the shared component to display it. // This wrapper object can't be renamed. + // $FlowFixMe[missing-local-annot] const canRenamePathsAtDepth = depth => depth > 1; if (isEmpty) { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js index f97a27c372a4..6744cf4bc647 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js @@ -223,6 +223,7 @@ function HookView({ // Certain hooks are not editable at all (as identified by react-debug-tools). // Primitive hook names (e.g. the "State" name for useState) are also never editable. + // $FlowFixMe[missing-local-annot] const canRenamePathsAtDepth = depth => isStateEditable && depth > 1; const isCustomHook = subHooks.length > 0; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js index b400c6c7f329..09808d6e3dfd 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js @@ -39,7 +39,7 @@ export default function InspectedElementSuspenseToggle({ const isSuspended = state !== null; - const toggleSuspense = (path, value) => { + const toggleSuspense = (path: any, value: boolean) => { const rendererID = store.getRendererIDForElement(id); if (rendererID !== null) { bridge.send('overrideSuspense', { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js index 26c65c15ff96..ca2f623c1a4c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js @@ -138,7 +138,7 @@ export default function KeyValue({ paddingLeft: `${(depth - 1) * 0.75}rem`, }; - const overrideValue = (newPath, newValue) => { + const overrideValue = (newPath: Array, newValue: any) => { if (hookID != null) { newPath = parseHookPathForEdit(newPath); } @@ -156,7 +156,7 @@ export default function KeyValue({ } }; - const deletePath = pathToDelete => { + const deletePath = (pathToDelete: Array) => { if (hookID != null) { pathToDelete = parseHookPathForEdit(pathToDelete); } @@ -173,7 +173,10 @@ export default function KeyValue({ } }; - const renamePath = (oldPath, newPath) => { + const renamePath = ( + oldPath: Array, + newPath: Array, + ) => { if (newPath[newPath.length - 1] === '') { // Deleting the key suggests an intent to delete the whole path. if (canDeletePaths) { @@ -477,7 +480,9 @@ export default function KeyValue({ return children; } +// $FlowFixMe[missing-local-annot] function DeleteToggle({deletePath, name, path}) { + // $FlowFixMe[missing-local-annot] const handleClick = event => { event.stopPropagation(); deletePath(path); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.js b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.js index 057dd4625f2c..059cb42273f4 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.js @@ -27,6 +27,7 @@ export default function AutoSizeInput({ value, ...rest }: Props): React.Node { + // $FlowFixMe[missing-local-annot] const onFocusWrapper = event => { const input = event.target; if (input !== null) { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js index 7b85633f214a..5b7cdf856341 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js @@ -171,6 +171,7 @@ function Row({ const [isAttributeValid, setIsAttributeValid] = useState(true); const [isValueValid, setIsValueValid] = useState(true); + // $FlowFixMe[missing-local-annot] const validateAndSetLocalAttribute = newAttribute => { const isValid = newAttribute === '' || @@ -183,6 +184,7 @@ function Row({ }); }; + // $FlowFixMe[missing-local-annot] const validateAndSetLocalValue = newValue => { let isValid = false; try { @@ -262,6 +264,7 @@ function Field({ placeholder, value, }: FieldProps) { + // $FlowFixMe[missing-local-annot] const onKeyDown = event => { switch (event.key) { case 'Enter': diff --git a/packages/react-devtools-shared/src/devtools/views/Components/NewArrayValue.js b/packages/react-devtools-shared/src/devtools/views/Components/NewArrayValue.js index 0a6cdf842fa0..c8247f744962 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/NewArrayValue.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/NewArrayValue.js @@ -46,7 +46,8 @@ export default function NewArrayValue({ // This is a bit of an unusual usage of the EditableName component, // but otherwise it acts the way we want for a new Array entry. - const overrideName = (oldPath, newPath) => { + // $FlowFixMe[missing-local-annot] + const overrideName = (oldPath: any, newPath) => { const value = newPath[newPath.length - 1]; let parsedValue; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/NewKeyValue.js b/packages/react-devtools-shared/src/devtools/views/Components/NewKeyValue.js index 6435e6da4bf5..8fb4cc1dc8ca 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/NewKeyValue.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/NewKeyValue.js @@ -42,11 +42,15 @@ export default function NewKeyValue({ const [newPropKey, setNewPropKey] = useState(0); const [newPropName, setNewPropName] = useState(''); - const overrideNewEntryName = (oldPath, newPath) => { + // $FlowFixMe[missing-local-annot] + const overrideNewEntryName = (oldPath: any, newPath) => { setNewPropName(newPath[newPath.length - 1]); }; - const overrideNewEntryValue = (newPath, value) => { + const overrideNewEntryValue = ( + newPath: Array, + value: any, + ) => { if (!newPropName) { return; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js index f79a307885c9..c557e374ab2f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js @@ -56,7 +56,7 @@ type State = { selectedIndex: number, }; -function dialogReducer(state, action) { +function dialogReducer(state: State, action: Action) { switch (action.type) { case 'UPDATE_OWNER_ID': const selectedIndex = action.owners.findIndex( diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js index 106bda6d3612..7374e4d70ff3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js @@ -101,7 +101,7 @@ export default function Tree(props: Props): React.Node { // Picking an element in the inspector should put focus into the tree. // This ensures that keyboard navigation works right after picking a node. useEffect(() => { - function handleStopInspectingNative(didSelectNode) { + function handleStopInspectingNative(didSelectNode: boolean) { if (didSelectNode && focusTargetRef.current !== null) { focusTargetRef.current.focus(); logEvent({ @@ -540,6 +540,7 @@ function updateIndentationSizeVar( list.style.setProperty('--indentation-size', `${maxIndentationSize}px`); } +// $FlowFixMe[missing-local-annot] function InnerElementType({children, style, ...rest}) { const {ownerID} = useContext(TreeStateContext); diff --git a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js index c7d3f7befb7a..cf927e055de7 100644 --- a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js +++ b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js @@ -61,7 +61,7 @@ const ModalDialogContext: ReactContext = createContext { +const debug = (methodName: string, ...args: Array) => { if (__DEBUG__) { console.log( `%cCommitTreeBuilder %c${methodName}`, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js index 716bf67adee7..d0d8be666ed3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js @@ -7,6 +7,7 @@ * @flow */ +import type {Stack} from '../../utils'; import type {SchedulingEvent} from 'react-devtools-timeline/src/types'; import * as React from 'react'; @@ -35,7 +36,7 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) { const {componentName, timestamp} = eventInfo; const componentStack = eventInfo.componentStack || null; - const viewSource = source => { + const viewSource = (source: ?Stack) => { if (viewUrlSourceFunction != null && source != null) { viewUrlSourceFunction(...source); } diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js index bfdb4a6e3168..930183ee0cc6 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js @@ -38,6 +38,7 @@ export default function SidebarSelectedFiberInfo(_: Props): React.Node { rootID: ((rootID: any): number), }); + // $FlowFixMe[missing-local-annot] const handleKeyDown = event => { switch (event.key) { case 'ArrowUp': diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js index 052c0c100c64..57b26ce15fa3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js @@ -86,6 +86,7 @@ export default function SnapshotSelector(_: Props): React.Node { let label = null; if (numFilteredCommits > 0) { + // $FlowFixMe[missing-local-annot] const handleCommitInputChange = event => { const value = parseInt(event.currentTarget.value, 10); if (!isNaN(value)) { @@ -100,10 +101,12 @@ export default function SnapshotSelector(_: Props): React.Node { } }; + // $FlowFixMe[missing-local-annot] const handleClick = event => { event.currentTarget.select(); }; + // $FlowFixMe[missing-local-annot] const handleKeyDown = event => { switch (event.key) { case 'ArrowDown': @@ -159,6 +162,7 @@ export default function SnapshotSelector(_: Props): React.Node { selectCommitIndex(filteredCommitIndices[nextCommitIndex]); }; + // $FlowFixMe[missing-local-annot] const handleKeyDown = event => { switch (event.key) { case 'ArrowLeft': diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js index baf61f82822b..b43f51cb04e2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js @@ -55,7 +55,15 @@ export default function Tooltip({ const TOOLTIP_OFFSET = 5; // Method used to find the position of the tooltip based on current mouse position -function getTooltipPosition(element, mousePosition) { +function getTooltipPosition( + element: empty, + mousePosition: { + height: number, + mouseX: number, + mouseY: number, + width: number, + }, +) { const {height, mouseX, mouseY, width} = mousePosition; let top: number | string = 0; let left: number | string = 0; @@ -85,7 +93,7 @@ function getTooltipPosition(element, mousePosition) { // method used to find the current mouse position inside the container function getMousePosition( - relativeContainer, + relativeContainer: null, mouseEvent: SyntheticMouseEvent, ) { if (relativeContainer !== null) { diff --git a/packages/react-devtools-shared/src/devtools/views/SearchInput.js b/packages/react-devtools-shared/src/devtools/views/SearchInput.js index 2569b685536e..8a43202f87c3 100644 --- a/packages/react-devtools-shared/src/devtools/views/SearchInput.js +++ b/packages/react-devtools-shared/src/devtools/views/SearchInput.js @@ -40,9 +40,11 @@ export default function SearchInput({ const resetSearch = () => search(''); + // $FlowFixMe[missing-local-annot] const handleChange = ({currentTarget}) => { search(currentTarget.value); }; + // $FlowFixMe[missing-local-annot] const handleKeyPress = ({key, shiftKey}) => { if (key === 'Enter') { if (shiftKey) { diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index 6cc82dd424dd..c5ee3a3e83d7 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -43,7 +43,10 @@ type UseEditableValueState = { parsedValue: any, }; -function useEditableValueReducer(state, action) { +function useEditableValueReducer( + state: UseEditableValueState, + action: UseEditableValueAction, +) { switch (action.type) { case 'RESET': return { @@ -188,6 +191,7 @@ export function useLocalStorage( // Listen for changes to this local storage value made from other windows. // This enables the e.g. "⚛️ Elements" tab to update in response to changes from "⚛️ Settings". useLayoutEffect(() => { + // $FlowFixMe[missing-local-annot] const onStorage = event => { const newValue = getValueFromLocalStorage(); if (key === event.key && storedValue !== newValue) { diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 547f6b35e63a..acecd13f06a6 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -9,7 +9,12 @@ */ import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools'; -import type {DevToolsHook} from 'react-devtools-shared/src/backend/types'; +import type { + RendererID, + ReactRenderer, + Handler, + DevToolsHook, +} from 'react-devtools-shared/src/backend/types'; import { patchConsoleUsingWindowValues, @@ -40,7 +45,7 @@ export function installHook(target: any): DevToolsHook | null { } } - function detectReactBuildType(renderer) { + function detectReactBuildType(renderer: ReactRenderer) { try { if (typeof renderer.version === 'string') { // React DOM Fiber (16+) @@ -263,7 +268,7 @@ export function installHook(target: any): DevToolsHook | null { ? targetConsole[method].__REACT_DEVTOOLS_STRICT_MODE_ORIGINAL_METHOD__ : targetConsole[method]); - const overrideMethod = (...args) => { + const overrideMethod = (...args: $ReadOnlyArray) => { if (!hideConsoleLogsInStrictMode) { // Dim the text color of the double logs if we're not // hiding them. @@ -316,7 +321,7 @@ export function installHook(target: any): DevToolsHook | null { let uidCounter = 0; - function inject(renderer) { + function inject(renderer: ReactRenderer) { const id = ++uidCounter; renderers.set(id, renderer); @@ -374,19 +379,19 @@ export function installHook(target: any): DevToolsHook | null { let hasDetectedBadDCE = false; - function sub(event, fn) { + function sub(event: string, fn: Handler) { hook.on(event, fn); return () => hook.off(event, fn); } - function on(event, fn) { + function on(event: string, fn: Handler) { if (!listeners[event]) { listeners[event] = []; } listeners[event].push(fn); } - function off(event, fn) { + function off(event: string, fn: Handler) { if (!listeners[event]) { return; } @@ -399,13 +404,13 @@ export function installHook(target: any): DevToolsHook | null { } } - function emit(event, data) { + function emit(event: string, data: any) { if (listeners[event]) { listeners[event].map(fn => fn(data)); } } - function getFiberRoots(rendererID) { + function getFiberRoots(rendererID: RendererID) { const roots = fiberRoots; if (!roots[rendererID]) { roots[rendererID] = new Set(); @@ -413,14 +418,18 @@ export function installHook(target: any): DevToolsHook | null { return roots[rendererID]; } - function onCommitFiberUnmount(rendererID, fiber) { + function onCommitFiberUnmount(rendererID: RendererID, fiber: any) { const rendererInterface = rendererInterfaces.get(rendererID); if (rendererInterface != null) { rendererInterface.handleCommitFiberUnmount(fiber); } } - function onCommitFiberRoot(rendererID, root, priorityLevel) { + function onCommitFiberRoot( + rendererID: RendererID, + root: any, + priorityLevel: void | number, + ) { const mountedRoots = hook.getFiberRoots(rendererID); const current = root.current; const isKnownRoot = mountedRoots.has(root); @@ -439,14 +448,14 @@ export function installHook(target: any): DevToolsHook | null { } } - function onPostCommitFiberRoot(rendererID, root) { + function onPostCommitFiberRoot(rendererID: RendererID, root: any) { const rendererInterface = rendererInterfaces.get(rendererID); if (rendererInterface != null) { rendererInterface.handlePostCommitFiberRoot(root); } } - function setStrictMode(rendererID, isStrictMode) { + function setStrictMode(rendererID: RendererID, isStrictMode: any) { const rendererInterface = rendererInterfaces.get(rendererID); if (rendererInterface != null) { if (isStrictMode) { diff --git a/packages/react-devtools-shared/src/node_modules/react-window/src/createGridComponent.js b/packages/react-devtools-shared/src/node_modules/react-window/src/createGridComponent.js index d1da857a5496..a4c90eba85f3 100644 --- a/packages/react-devtools-shared/src/node_modules/react-window/src/createGridComponent.js +++ b/packages/react-devtools-shared/src/node_modules/react-window/src/createGridComponent.js @@ -143,7 +143,13 @@ type ValidateProps = (props: Props) => void; const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; -const defaultItemKey = ({ columnIndex, data, rowIndex }) => +const defaultItemKey = ( + { + columnIndex, + data, + rowIndex + }: { columnIndex: number, data: any, rowIndex: number }, +) => `${rowIndex}:${columnIndex}`; // In DEV mode, this Set helps us only log a warning once per component instance. @@ -197,7 +203,7 @@ export default function createGridComponent({ _resetIsScrollingTimeoutId: TimeoutID | null = null; _outerRef: ?HTMLDivElement; - static defaultProps = { + static defaultProps: { direction: string, itemData: void, useIsScrolling: boolean } = { direction: 'ltr', itemData: undefined, useIsScrolling: false, @@ -393,7 +399,7 @@ export default function createGridComponent({ } } - render() { + render(): any { const { children, className, @@ -486,7 +492,7 @@ export default function createGridComponent({ ); } - _callOnItemsRendered: ( + _callOnItemsRendered: (( overscanColumnStartIndex: number, overscanColumnStopIndex: number, overscanRowStartIndex: number, @@ -495,8 +501,7 @@ export default function createGridComponent({ visibleColumnStopIndex: number, visibleRowStartIndex: number, visibleRowStopIndex: number - ) => void; - _callOnItemsRendered = memoizeOne( + ) => void) = memoizeOne( ( overscanColumnStartIndex: number, overscanColumnStopIndex: number, @@ -519,14 +524,13 @@ export default function createGridComponent({ }) ); - _callOnScroll: ( + _callOnScroll: (( scrollLeft: number, scrollTop: number, horizontalScrollDirection: ScrollDirection, verticalScrollDirection: ScrollDirection, scrollUpdateWasRequested: boolean - ) => void; - _callOnScroll = memoizeOne( + ) => void) = memoizeOne( ( scrollLeft: number, scrollTop: number, @@ -595,7 +599,6 @@ export default function createGridComponent({ // So that pure component sCU will prevent re-renders. // We maintain this cache, and pass a style prop rather than index, // So that List can clear cached styles and force item re-render if necessary. - _getItemStyle: (rowIndex: number, columnIndex: number) => Object; _getItemStyle = (rowIndex: number, columnIndex: number): Object => { const { columnWidth, direction, rowHeight } = this.props; @@ -628,8 +631,8 @@ export default function createGridComponent({ return style; }; - _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); + _getItemStyleCache: ((_: any, __: any, ___: any) => ItemStyleCache) = + memoizeOne((_: any, __: any, ___: any) => ({})); _getHorizontalRangeToRender(): [number, number, number, number] { const { diff --git a/packages/react-devtools-shared/src/node_modules/react-window/src/createListComponent.js b/packages/react-devtools-shared/src/node_modules/react-window/src/createListComponent.js index 11ff59317c5f..210dba2b642b 100644 --- a/packages/react-devtools-shared/src/node_modules/react-window/src/createListComponent.js +++ b/packages/react-devtools-shared/src/node_modules/react-window/src/createListComponent.js @@ -162,7 +162,13 @@ export default function createListComponent({ _outerRef: ?HTMLDivElement; _resetIsScrollingTimeoutId: TimeoutID | null = null; - static defaultProps = { + static defaultProps: { + direction: string, + itemData: void, + layout: string, + overscanCount: number, + useIsScrolling: boolean, + } = { direction: 'ltr', itemData: undefined, layout: 'vertical', @@ -288,7 +294,7 @@ export default function createListComponent({ } } - render() { + render(): any { const { children, className, @@ -370,13 +376,12 @@ export default function createListComponent({ ); } - _callOnItemsRendered: ( + _callOnItemsRendered: (( overscanStartIndex: number, overscanStopIndex: number, visibleStartIndex: number, visibleStopIndex: number - ) => void; - _callOnItemsRendered = memoizeOne( + ) => void) = memoizeOne( ( overscanStartIndex: number, overscanStopIndex: number, @@ -391,12 +396,11 @@ export default function createListComponent({ }) ); - _callOnScroll: ( + _callOnScroll: (( scrollDirection: ScrollDirection, scrollOffset: number, scrollUpdateWasRequested: boolean - ) => void; - _callOnScroll = memoizeOne( + ) => void) = memoizeOne( ( scrollDirection: ScrollDirection, scrollOffset: number, @@ -446,7 +450,6 @@ export default function createListComponent({ // So that pure component sCU will prevent re-renders. // We maintain this cache, and pass a style prop rather than index, // So that List can clear cached styles and force item re-render if necessary. - _getItemStyle: (index: number) => Object; _getItemStyle = (index: number): Object => { const { direction, itemSize, layout } = this.props; @@ -480,8 +483,8 @@ export default function createListComponent({ return style; }; - _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); + _getItemStyleCache: ((_: any, __: any, ___: any) => ItemStyleCache) = + memoizeOne((_: any, __: any, ___: any) => ({})); _getRangeToRender(): [number, number, number, number] { const { itemCount, overscanCount } = this.props; diff --git a/packages/react-devtools-shared/src/node_modules/react-window/src/shouldComponentUpdate.js b/packages/react-devtools-shared/src/node_modules/react-window/src/shouldComponentUpdate.js index 6b891bd4d2e5..01ca6d507126 100644 --- a/packages/react-devtools-shared/src/node_modules/react-window/src/shouldComponentUpdate.js +++ b/packages/react-devtools-shared/src/node_modules/react-window/src/shouldComponentUpdate.js @@ -7,8 +7,9 @@ import shallowDiffers from './shallowDiffers'; // It knows to compare individual style props and ignore the wrapper object. // See https://reactjs.org/docs/react-component.html#shouldcomponentupdate export default function shouldComponentUpdate( + this: interface { props: any, state: any }, nextProps: Object, - nextState: Object + nextState: Object, ): boolean { return ( !areEqual(this.props, nextProps) || shallowDiffers(this.state, nextState) diff --git a/packages/react-devtools-shared/src/registerDevToolsEventLogger.js b/packages/react-devtools-shared/src/registerDevToolsEventLogger.js index 8f261c1ed90d..6bc08d91524b 100644 --- a/packages/react-devtools-shared/src/registerDevToolsEventLogger.js +++ b/packages/react-devtools-shared/src/registerDevToolsEventLogger.js @@ -56,7 +56,7 @@ export function registerDevToolsEventLogger( } } - function handleLoggingIFrameLoaded(iframe) { + function handleLoggingIFrameLoaded(iframe: HTMLIFrameElement) { if (loggingIFrame != null) { return; } diff --git a/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js index dc1de76dfa73..8c9a02e5e0c7 100644 --- a/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js +++ b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js @@ -10,19 +10,19 @@ import * as React from 'react'; import {Fragment} from 'react'; -function wrapWithHoc(Component, index) { +function wrapWithHoc(Component: () => any, index: number) { function HOC() { return ; } - const displayName = Component.displayName || Component.name; + const displayName = (Component: any).displayName || Component.name; // $FlowFixMe[incompatible-type] found when upgrading Flow HOC.displayName = `withHoc${index}(${displayName})`; return HOC; } -function wrapWithNested(Component, times) { +function wrapWithNested(Component: () => any, times: number) { for (let i = 0; i < times; i++) { Component = wrapWithHoc(Component, i); } diff --git a/packages/react-devtools-shell/src/app/EditableProps/index.js b/packages/react-devtools-shell/src/app/EditableProps/index.js index fe617337faeb..5cfad3f7e837 100644 --- a/packages/react-devtools-shell/src/app/EditableProps/index.js +++ b/packages/react-devtools-shell/src/app/EditableProps/index.js @@ -7,6 +7,7 @@ * @flow */ +import type {ReactContext} from 'shared/ReactTypes'; import * as React from 'react'; import { createContext, @@ -23,7 +24,8 @@ import { const initialData = {foo: 'FOO', bar: 'BAR'}; -function reducer(state, action) { +// $FlowFixMe[missing-local-annot] +function reducer(state, action: {type: string}) { switch (action.type) { case 'swap': return {foo: state.bar, bar: state.foo}; @@ -72,19 +74,20 @@ type Props = {name: string, toggle: boolean}; type State = {cities: Array, state: string}; class StatefulClass extends Component { - static contextType = BoolContext; + static contextType: ReactContext = BoolContext; state: State = { cities: ['San Francisco', 'San Jose'], state: 'California', }; - handleChange = ({target}) => + // $FlowFixMe[missing-local-annot] + handleChange = ({target}): any => this.setState({ state: target.value, }); - render() { + render(): any { return (