From dbe98a5aae79efce4de3eea95171d1ef70537fdb Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 28 Mar 2021 16:15:53 -0500 Subject: [PATCH] Move sync task queue to its own module (#21109) The sync task queue is React-specific and doesn't really have anything to do with Scheduler. We'd keep using it even once `postTask` exists. By separating that part out, `SchedulerWithReactIntegration` is now just a module that re-exports the Scheduler API. So I unforked it. When we switch to ES Modules, we can remove this re-exporting module. --- .../src/events/ReactDOMEventListener.js | 16 ++--- .../src/ReactFiberCompleteWork.new.js | 2 +- .../src/ReactFiberCompleteWork.old.js | 2 +- .../src/ReactFiberDevToolsHook.new.js | 2 +- .../src/ReactFiberDevToolsHook.old.js | 2 +- .../src/ReactFiberSyncTaskQueue.new.js | 67 +++++++++++++++++++ ....old.js => ReactFiberSyncTaskQueue.old.js} | 41 +----------- .../src/ReactFiberWorkLoop.new.js | 4 +- .../src/ReactFiberWorkLoop.old.js | 4 +- ...thReactIntegration.new.js => Scheduler.js} | 56 ---------------- 10 files changed, 84 insertions(+), 112 deletions(-) create mode 100644 packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js rename packages/react-reconciler/src/{SchedulerWithReactIntegration.old.js => ReactFiberSyncTaskQueue.old.js} (50%) rename packages/react-reconciler/src/{SchedulerWithReactIntegration.new.js => Scheduler.js} (53%) diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index 8197a2da242a1..3f00dcf305b37 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -34,25 +34,21 @@ import { import getEventTarget from './getEventTarget'; import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; -import { - enableLegacyFBSupport, - enableNewReconciler, -} from 'shared/ReactFeatureFlags'; +import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags'; import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem'; import { flushDiscreteUpdatesIfNeeded, discreteUpdates, } from './ReactDOMUpdateBatching'; -import {getCurrentPriorityLevel as getCurrentPriorityLevel_old} from 'react-reconciler/src/SchedulerWithReactIntegration.old'; import { - getCurrentPriorityLevel as getCurrentPriorityLevel_new, + getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel, IdlePriority as IdleSchedulerPriority, ImmediatePriority as ImmediateSchedulerPriority, LowPriority as LowSchedulerPriority, NormalPriority as NormalSchedulerPriority, UserBlockingPriority as UserBlockingSchedulerPriority, -} from 'react-reconciler/src/SchedulerWithReactIntegration.new'; +} from 'react-reconciler/src/Scheduler'; import { DiscreteEventPriority, ContinuousEventPriority, @@ -62,10 +58,6 @@ import { setCurrentUpdatePriority, } from 'react-reconciler/src/ReactEventPriorities'; -const getCurrentPriorityLevel = enableNewReconciler - ? getCurrentPriorityLevel_new - : getCurrentPriorityLevel_old; - // TODO: can we stop exporting these? export let _enabled = true; @@ -392,7 +384,7 @@ export function getEventPriority(domEventName: DOMEventName): * { // We might be in the Scheduler callback. // Eventually this mechanism will be replaced by a check // of the current priority on the native scheduler. - const schedulerPriority = getCurrentPriorityLevel(); + const schedulerPriority = getCurrentSchedulerPriorityLevel(); switch (schedulerPriority) { case ImmediateSchedulerPriority: return DiscreteEventPriority; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 1dcc0ec9c87d1..0924738f1e956 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -28,7 +28,7 @@ import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new'; import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new'; -import {now} from './SchedulerWithReactIntegration.new'; +import {now} from './Scheduler'; import { IndeterminateComponent, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index dc1112d3b88f6..017db23b66e44 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -28,7 +28,7 @@ import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old'; import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.old'; -import {now} from './SchedulerWithReactIntegration.old'; +import {now} from './Scheduler'; import { IndeterminateComponent, diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js index afcbad36f1559..a98e569b095b9 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.new.js @@ -25,7 +25,7 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, -} from './SchedulerWithReactIntegration.new'; +} from './Scheduler'; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js index 39cd1f02cf99e..43b9f360faa3a 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.old.js @@ -25,7 +25,7 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, -} from './SchedulerWithReactIntegration.old'; +} from './Scheduler'; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; diff --git a/packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js b/packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js new file mode 100644 index 0000000000000..1d3e3daa47ae4 --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js @@ -0,0 +1,67 @@ +/** + * 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 type {SchedulerCallback} from './Scheduler'; + +import { + DiscreteEventPriority, + getCurrentUpdatePriority, + setCurrentUpdatePriority, +} from './ReactEventPriorities.new'; +import {ImmediatePriority, scheduleCallback} from './Scheduler'; + +let syncQueue: Array | null = null; +let isFlushingSyncQueue: boolean = false; + +export function scheduleSyncCallback(callback: SchedulerCallback) { + // Push this callback into an internal queue. We'll flush these either in + // the next tick, or earlier if something calls `flushSyncCallbackQueue`. + if (syncQueue === null) { + syncQueue = [callback]; + } else { + // Push onto existing queue. Don't need to schedule a callback because + // we already scheduled one when we created the queue. + syncQueue.push(callback); + } +} + +export function flushSyncCallbackQueue() { + if (!isFlushingSyncQueue && syncQueue !== null) { + // Prevent re-entrancy. + isFlushingSyncQueue = true; + let i = 0; + const previousUpdatePriority = getCurrentUpdatePriority(); + try { + const isSync = true; + const queue = syncQueue; + // TODO: Is this necessary anymore? The only user code that runs in this + // queue is in the render or commit phases. + setCurrentUpdatePriority(DiscreteEventPriority); + for (; i < queue.length; i++) { + let callback = queue[i]; + do { + callback = callback(isSync); + } while (callback !== null); + } + syncQueue = null; + } catch (error) { + // If something throws, leave the remaining callbacks on the queue. + if (syncQueue !== null) { + syncQueue = syncQueue.slice(i + 1); + } + // Resume flushing in the next tick + scheduleCallback(ImmediatePriority, flushSyncCallbackQueue); + throw error; + } finally { + setCurrentUpdatePriority(previousUpdatePriority); + isFlushingSyncQueue = false; + } + } + return null; +} diff --git a/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js b/packages/react-reconciler/src/ReactFiberSyncTaskQueue.old.js similarity index 50% rename from packages/react-reconciler/src/SchedulerWithReactIntegration.old.js rename to packages/react-reconciler/src/ReactFiberSyncTaskQueue.old.js index 9a57e77cb89fe..48a6f352fecab 100644 --- a/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js +++ b/packages/react-reconciler/src/ReactFiberSyncTaskQueue.old.js @@ -7,50 +7,15 @@ * @flow */ -// This module only exists as an ESM wrapper around the external CommonJS -// Scheduler dependency. Notice that we're intentionally not using named imports -// because Rollup would use dynamic dispatch for CommonJS interop named imports. -// When we switch to ESM, we can delete this module. -import * as Scheduler from 'scheduler'; -import {__interactionsRef} from 'scheduler/tracing'; -import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; -import invariant from 'shared/invariant'; +import type {SchedulerCallback} from './Scheduler'; + import { DiscreteEventPriority, getCurrentUpdatePriority, setCurrentUpdatePriority, } from './ReactEventPriorities.old'; +import {ImmediatePriority, scheduleCallback} from './Scheduler'; -export const scheduleCallback = Scheduler.unstable_scheduleCallback; -export const cancelCallback = Scheduler.unstable_cancelCallback; -export const shouldYield = Scheduler.unstable_shouldYield; -export const requestPaint = Scheduler.unstable_requestPaint; -export const now = Scheduler.unstable_now; -export const getCurrentPriorityLevel = - Scheduler.unstable_getCurrentPriorityLevel; -export const ImmediatePriority = Scheduler.unstable_ImmediatePriority; -export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority; -export const NormalPriority = Scheduler.unstable_NormalPriority; -export const LowPriority = Scheduler.unstable_LowPriority; -export const IdlePriority = Scheduler.unstable_IdlePriority; - -if (enableSchedulerTracing) { - // Provide explicit error message when production+profiling bundle of e.g. - // react-dom is used with production (non-profiling) bundle of - // scheduler/tracing - invariant( - __interactionsRef != null && __interactionsRef.current != null, - 'It is not supported to run the profiling version of a renderer (for ' + - 'example, `react-dom/profiling`) without also replacing the ' + - '`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' + - 'bundler might have a setting for aliasing both modules. Learn more at ' + - 'https://reactjs.org/link/profiling', - ); -} - -export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null; - -// TODO: Move sync task queue to its own module. let syncQueue: Array | null = null; let isFlushingSyncQueue: boolean = false; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index b7ed519ea55dd..8b5a924f02cdd 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -46,9 +46,11 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, +} from './Scheduler'; +import { flushSyncCallbackQueue, scheduleSyncCallback, -} from './SchedulerWithReactIntegration.new'; +} from './ReactFiberSyncTaskQueue.new'; import { NoFlags as NoHookEffect, Passive as HookPassive, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index d29bbdefba4cc..3ee88a2feaf14 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -46,9 +46,11 @@ import { UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, +} from './Scheduler'; +import { flushSyncCallbackQueue, scheduleSyncCallback, -} from './SchedulerWithReactIntegration.old'; +} from './ReactFiberSyncTaskQueue.old'; import { NoFlags as NoHookEffect, Passive as HookPassive, diff --git a/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js b/packages/react-reconciler/src/Scheduler.js similarity index 53% rename from packages/react-reconciler/src/SchedulerWithReactIntegration.new.js rename to packages/react-reconciler/src/Scheduler.js index c686cfe12aac6..f590f284a9ed4 100644 --- a/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js +++ b/packages/react-reconciler/src/Scheduler.js @@ -15,11 +15,6 @@ import * as Scheduler from 'scheduler'; import {__interactionsRef} from 'scheduler/tracing'; import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; -import { - DiscreteEventPriority, - getCurrentUpdatePriority, - setCurrentUpdatePriority, -} from './ReactEventPriorities.new'; export const scheduleCallback = Scheduler.unstable_scheduleCallback; export const cancelCallback = Scheduler.unstable_cancelCallback; @@ -49,54 +44,3 @@ if (enableSchedulerTracing) { } export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null; - -// TODO: Move sync task queue to its own module. -let syncQueue: Array | null = null; -let isFlushingSyncQueue: boolean = false; - -export function scheduleSyncCallback(callback: SchedulerCallback) { - // Push this callback into an internal queue. We'll flush these either in - // the next tick, or earlier if something calls `flushSyncCallbackQueue`. - if (syncQueue === null) { - syncQueue = [callback]; - } else { - // Push onto existing queue. Don't need to schedule a callback because - // we already scheduled one when we created the queue. - syncQueue.push(callback); - } -} - -export function flushSyncCallbackQueue() { - if (!isFlushingSyncQueue && syncQueue !== null) { - // Prevent re-entrancy. - isFlushingSyncQueue = true; - let i = 0; - const previousUpdatePriority = getCurrentUpdatePriority(); - try { - const isSync = true; - const queue = syncQueue; - // TODO: Is this necessary anymore? The only user code that runs in this - // queue is in the render or commit phases. - setCurrentUpdatePriority(DiscreteEventPriority); - for (; i < queue.length; i++) { - let callback = queue[i]; - do { - callback = callback(isSync); - } while (callback !== null); - } - syncQueue = null; - } catch (error) { - // If something throws, leave the remaining callbacks on the queue. - if (syncQueue !== null) { - syncQueue = syncQueue.slice(i + 1); - } - // Resume flushing in the next tick - scheduleCallback(ImmediatePriority, flushSyncCallbackQueue); - throw error; - } finally { - setCurrentUpdatePriority(previousUpdatePriority); - isFlushingSyncQueue = false; - } - } - return null; -}