@samitouri / QOS-React-2 / commits / cd48a06547

Set up infra for react-reconciler fork (#18285)

* ReactFiberReconciler -> ReactFiberReconciler.old * Set up infra for react-reconciler fork We're planning to land some significant refactors of the reconciler. We want to be able to gradually roll out the new implementation side-by- side with the existing one. So we'll create a short lived fork of the react-reconciler package. Once the new implementation has stabilized, we'll delete the old implementation and promote the new one. This means, for as long as the fork exists, we'll need to maintain two separate implementations. This sounds painful, but since the forks will still be largely the same, most changes will not require two separate implementations. In practice, you'll implement the change in the old fork and then copy paste it to the new one. This commit only sets up the build and testing infrastructure. It does not actually fork any modules. I'll do that in subsequent PRs. The forked version of the reconciler will be used to build a special version of React DOM. I've called this build ReactDOMForked. It's only built for www; there's no open source version. The new reconciler is disabled by default. It's enabled in the `yarn test-www-variant` command. The reconciler fork isn't really related to the "variant" feature of the www builds, but I'm piggy backing on that concept to avoid having to add yet another testing dimension.

Andrew Clark committed Mar 12, 2020 at 11:38 UTC cd48a06547390ae426a70d555ec9fd5349c85d08
15 files changed +923 -720
packages/react-reconciler/src/ReactFiberReconciler.js
+126 -714
@@ -7,721 +7,133 @@
7 * @flow
8 */
9
10 -import type {Fiber} from './ReactFiber';
11 -import type {FiberRoot} from './ReactFiberRoot';
12 -import type {RootTag} from 'shared/ReactRootTags';
13 -import type {
14 - Instance,
15 - TextInstance,
16 - Container,
17 - PublicInstance,
18 -} from './ReactFiberHostConfig';
19 -import {FundamentalComponent} from 'shared/ReactWorkTags';
20 -import type {ReactNodeList} from 'shared/ReactTypes';
21 -import type {ExpirationTime} from './ReactFiberExpirationTime';
22 -import type {
23 - SuspenseHydrationCallbacks,
24 - SuspenseState,
25 -} from './ReactFiberSuspenseComponent';
10 +import {enableNewReconciler} from 'shared/ReactFeatureFlags';
11
27 -import {
28 - findCurrentHostFiber,
29 - findCurrentHostFiberWithNoPortals,
30 -} from 'react-reconciler/src/ReactFiberTreeReflection';
31 -import {get as getInstance} from 'shared/ReactInstanceMap';
32 -import {
33 - HostComponent,
34 - ClassComponent,
35 - HostRoot,
36 - SuspenseComponent,
37 -} from 'shared/ReactWorkTags';
38 -import getComponentName from 'shared/getComponentName';
39 -import invariant from 'shared/invariant';
40 -import ReactSharedInternals from 'shared/ReactSharedInternals';
12 +// The entry file imports either the old or new version of the reconciler.
13 +// During build and testing, this indirection is always shimmed with the actual
14 +// modules, otherwise both reconcilers would be initialized. So this is really
15 +// only here for Flow purposes.
16
42 -import {getPublicInstance} from './ReactFiberHostConfig';
43 -import {
44 - findCurrentUnmaskedContext,
45 - processChildContext,
46 - emptyContextObject,
47 - isContextProvider as isLegacyContextProvider,
48 -} from './ReactFiberContext';
49 -import {createFiberRoot} from './ReactFiberRoot';
50 -import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook';
51 -import {
52 - requestCurrentTimeForUpdate,
53 - computeExpirationForFiber,
54 - scheduleUpdateOnFiber,
55 - flushRoot,
56 - batchedEventUpdates,
57 - batchedUpdates,
58 - unbatchedUpdates,
59 - flushSync,
60 - flushControlled,
61 - deferredUpdates,
62 - syncUpdates,
63 - discreteUpdates,
64 - flushDiscreteUpdates,
65 - flushPassiveEffects,
66 - warnIfNotScopedWithMatchingAct,
67 - warnIfUnmockedScheduler,
68 - IsThisRendererActing,
69 -} from './ReactFiberWorkLoop';
70 -import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
17 import {
72 - getStackByFiberInDevAndProd,
73 - isRendering as ReactCurrentFiberIsRendering,
74 - current as ReactCurrentFiberCurrent,
75 -} from './ReactCurrentFiber';
76 -import {StrictMode} from './ReactTypeOfMode';
18 + createContainer as createContainer_old,
19 + updateContainer as updateContainer_old,
20 + batchedEventUpdates as batchedEventUpdates_old,
21 + batchedUpdates as batchedUpdates_old,
22 + unbatchedUpdates as unbatchedUpdates_old,
23 + deferredUpdates as deferredUpdates_old,
24 + syncUpdates as syncUpdates_old,
25 + discreteUpdates as discreteUpdates_old,
26 + flushDiscreteUpdates as flushDiscreteUpdates_old,
27 + flushControlled as flushControlled_old,
28 + flushSync as flushSync_old,
29 + flushPassiveEffects as flushPassiveEffects_old,
30 + IsThisRendererActing as IsThisRendererActing_old,
31 + getPublicRootInstance as getPublicRootInstance_old,
32 + attemptSynchronousHydration as attemptSynchronousHydration_old,
33 + attemptUserBlockingHydration as attemptUserBlockingHydration_old,
34 + attemptContinuousHydration as attemptContinuousHydration_old,
35 + attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_old,
36 + findHostInstance as findHostInstance_old,
37 + findHostInstanceWithWarning as findHostInstanceWithWarning_old,
38 + findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_old,
39 + shouldSuspend as shouldSuspend_old,
40 + injectIntoDevTools as injectIntoDevTools_old,
41 + act as act_old,
42 +} from './ReactFiberReconciler.old';
43 +
44 +// TODO: Update these to point to the fork.
45 import {
78 - Sync,
79 - ContinuousHydration,
80 - computeInteractiveExpiration,
81 -} from './ReactFiberExpirationTime';
82 -import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
83 -import {
84 - scheduleRefresh,
85 - scheduleRoot,
86 - setRefreshHandler,
87 - findHostInstancesForRefresh,
88 -} from './ReactFiberHotReloading';
89 -
90 -// used by isTestEnvironment builds
91 -import enqueueTask from 'shared/enqueueTask';
92 -import * as Scheduler from 'scheduler';
93 -// end isTestEnvironment imports
94 -
95 -type OpaqueRoot = FiberRoot;
96 -
97 -// 0 is PROD, 1 is DEV.
98 -// Might add PROFILE later.
99 -type BundleType = 0 | 1;
100 -
101 -type DevToolsConfig = {|
102 - bundleType: BundleType,
103 - version: string,
104 - rendererPackageName: string,
105 - // Note: this actually *does* depend on Fiber internal fields.
106 - // Used by "inspect clicked DOM element" in React DevTools.
107 - findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
108 - // Used by RN in-app inspector.
109 - // This API is unfortunately RN-specific.
110 - // TODO: Change it to accept Fiber instead and type it properly.
111 - getInspectorDataForViewTag?: (tag: number) => Object,
112 -|};
113 -
114 -let didWarnAboutNestedUpdates;
115 -let didWarnAboutFindNodeInStrictMode;
116 -
117 -if (__DEV__) {
118 - didWarnAboutNestedUpdates = false;
119 - didWarnAboutFindNodeInStrictMode = {};
120 -}
121 -
122 -function getContextForSubtree(
123 - parentComponent: ?React$Component<any, any>,
124 -): Object {
125 - if (!parentComponent) {
126 - return emptyContextObject;
127 - }
128 -
129 - const fiber = getInstance(parentComponent);
130 - const parentContext = findCurrentUnmaskedContext(fiber);
131 -
132 - if (fiber.tag === ClassComponent) {
133 - const Component = fiber.type;
134 - if (isLegacyContextProvider(Component)) {
135 - return processChildContext(fiber, Component, parentContext);
136 - }
137 - }
138 -
139 - return parentContext;
140 -}
141 -
142 -function findHostInstance(component: Object): PublicInstance | null {
143 - const fiber = getInstance(component);
144 - if (fiber === undefined) {
145 - if (typeof component.render === 'function') {
146 - invariant(false, 'Unable to find node on an unmounted component.');
147 - } else {
148 - invariant(
149 - false,
150 - 'Argument appears to not be a ReactComponent. Keys: %s',
151 - Object.keys(component),
152 - );
153 - }
154 - }
155 - const hostFiber = findCurrentHostFiber(fiber);
156 - if (hostFiber === null) {
157 - return null;
158 - }
159 - return hostFiber.stateNode;
160 -}
161 -
162 -function findHostInstanceWithWarning(
163 - component: Object,
164 - methodName: string,
165 -): PublicInstance | null {
166 - if (__DEV__) {
167 - const fiber = getInstance(component);
168 - if (fiber === undefined) {
169 - if (typeof component.render === 'function') {
170 - invariant(false, 'Unable to find node on an unmounted component.');
171 - } else {
172 - invariant(
173 - false,
174 - 'Argument appears to not be a ReactComponent. Keys: %s',
175 - Object.keys(component),
176 - );
177 - }
178 - }
179 - const hostFiber = findCurrentHostFiber(fiber);
180 - if (hostFiber === null) {
181 - return null;
182 - }
183 - if (hostFiber.mode & StrictMode) {
184 - const componentName = getComponentName(fiber.type) || 'Component';
185 - if (!didWarnAboutFindNodeInStrictMode[componentName]) {
186 - didWarnAboutFindNodeInStrictMode[componentName] = true;
187 - if (fiber.mode & StrictMode) {
188 - console.error(
189 - '%s is deprecated in StrictMode. ' +
190 - '%s was passed an instance of %s which is inside StrictMode. ' +
191 - 'Instead, add a ref directly to the element you want to reference. ' +
192 - 'Learn more about using refs safely here: ' +
193 - 'https://fb.me/react-strict-mode-find-node%s',
194 - methodName,
195 - methodName,
196 - componentName,
197 - getStackByFiberInDevAndProd(hostFiber),
198 - );
199 - } else {
200 - console.error(
201 - '%s is deprecated in StrictMode. ' +
202 - '%s was passed an instance of %s which renders StrictMode children. ' +
203 - 'Instead, add a ref directly to the element you want to reference. ' +
204 - 'Learn more about using refs safely here: ' +
205 - 'https://fb.me/react-strict-mode-find-node%s',
206 - methodName,
207 - methodName,
208 - componentName,
209 - getStackByFiberInDevAndProd(hostFiber),
210 - );
211 - }
212 - }
213 - }
214 - return hostFiber.stateNode;
215 - }
216 - return findHostInstance(component);
217 -}
218 -
219 -export function createContainer(
220 - containerInfo: Container,
221 - tag: RootTag,
222 - hydrate: boolean,
223 - hydrationCallbacks: null | SuspenseHydrationCallbacks,
224 -): OpaqueRoot {
225 - return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
226 -}
227 -
228 -export function updateContainer(
229 - element: ReactNodeList,
230 - container: OpaqueRoot,
231 - parentComponent: ?React$Component<any, any>,
232 - callback: ?Function,
233 -): ExpirationTime {
234 - if (__DEV__) {
235 - onScheduleRoot(container, element);
236 - }
237 - const current = container.current;
238 - const currentTime = requestCurrentTimeForUpdate();
239 - if (__DEV__) {
240 - // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
241 - if ('undefined' !== typeof jest) {
242 - warnIfUnmockedScheduler(current);
243 - warnIfNotScopedWithMatchingAct(current);
244 - }
245 - }
246 - const suspenseConfig = requestCurrentSuspenseConfig();
247 - const expirationTime = computeExpirationForFiber(
248 - currentTime,
249 - current,
250 - suspenseConfig,
251 - );
252 -
253 - const context = getContextForSubtree(parentComponent);
254 - if (container.context === null) {
255 - container.context = context;
256 - } else {
257 - container.pendingContext = context;
258 - }
259 -
260 - if (__DEV__) {
261 - if (
262 - ReactCurrentFiberIsRendering &&
263 - ReactCurrentFiberCurrent !== null &&
264 - !didWarnAboutNestedUpdates
265 - ) {
266 - didWarnAboutNestedUpdates = true;
267 - console.error(
268 - 'Render methods should be a pure function of props and state; ' +
269 - 'triggering nested component updates from render is not allowed. ' +
270 - 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
271 - 'Check the render method of %s.',
272 - getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
273 - );
274 - }
275 - }
276 -
277 - const update = createUpdate(expirationTime, suspenseConfig);
278 - // Caution: React DevTools currently depends on this property
279 - // being called "element".
280 - update.payload = {element};
281 -
282 - callback = callback === undefined ? null : callback;
283 - if (callback !== null) {
284 - if (__DEV__) {
285 - if (typeof callback !== 'function') {
286 - console.error(
287 - 'render(...): Expected the last optional `callback` argument to be a ' +
288 - 'function. Instead received: %s.',
289 - callback,
290 - );
291 - }
292 - }
293 - update.callback = callback;
294 - }
295 -
296 - enqueueUpdate(current, update);
297 - scheduleUpdateOnFiber(current, expirationTime);
298 -
299 - return expirationTime;
300 -}
301 -
302 -export {
303 - batchedEventUpdates,
304 - batchedUpdates,
305 - unbatchedUpdates,
306 - deferredUpdates,
307 - syncUpdates,
308 - discreteUpdates,
309 - flushDiscreteUpdates,
310 - flushControlled,
311 - flushSync,
312 - flushPassiveEffects,
313 - IsThisRendererActing,
314 -};
315 -
316 -export function getPublicRootInstance(
317 - container: OpaqueRoot,
318 -): React$Component<any, any> | PublicInstance | null {
319 - const containerFiber = container.current;
320 - if (!containerFiber.child) {
321 - return null;
322 - }
323 - switch (containerFiber.child.tag) {
324 - case HostComponent:
325 - return getPublicInstance(containerFiber.child.stateNode);
326 - default:
327 - return containerFiber.child.stateNode;
328 - }
329 -}
330 -
331 -export function attemptSynchronousHydration(fiber: Fiber): void {
332 - switch (fiber.tag) {
333 - case HostRoot:
334 - let root: FiberRoot = fiber.stateNode;
335 - if (root.hydrate) {
336 - // Flush the first scheduled "update".
337 - flushRoot(root, root.firstPendingTime);
338 - }
339 - break;
340 - case SuspenseComponent:
341 - flushSync(() => scheduleUpdateOnFiber(fiber, Sync));
342 - // If we're still blocked after this, we need to increase
343 - // the priority of any promises resolving within this
344 - // boundary so that they next attempt also has higher pri.
345 - let retryExpTime = computeInteractiveExpiration(
346 - requestCurrentTimeForUpdate(),
347 - );
348 - markRetryTimeIfNotHydrated(fiber, retryExpTime);
349 - break;
350 - }
351 -}
352 -
353 -function markRetryTimeImpl(fiber: Fiber, retryTime: ExpirationTime) {
354 - let suspenseState: null | SuspenseState = fiber.memoizedState;
355 - if (suspenseState !== null && suspenseState.dehydrated !== null) {
356 - if (suspenseState.retryTime < retryTime) {
357 - suspenseState.retryTime = retryTime;
358 - }
359 - }
360 -}
361 -
362 -// Increases the priority of thennables when they resolve within this boundary.
363 -function markRetryTimeIfNotHydrated(fiber: Fiber, retryTime: ExpirationTime) {
364 - markRetryTimeImpl(fiber, retryTime);
365 - let alternate = fiber.alternate;
366 - if (alternate) {
367 - markRetryTimeImpl(alternate, retryTime);
368 - }
369 -}
370 -
371 -export function attemptUserBlockingHydration(fiber: Fiber): void {
372 - if (fiber.tag !== SuspenseComponent) {
373 - // We ignore HostRoots here because we can't increase
374 - // their priority and they should not suspend on I/O,
375 - // since you have to wrap anything that might suspend in
376 - // Suspense.
377 - return;
378 - }
379 - let expTime = computeInteractiveExpiration(requestCurrentTimeForUpdate());
380 - scheduleUpdateOnFiber(fiber, expTime);
381 - markRetryTimeIfNotHydrated(fiber, expTime);
382 -}
383 -
384 -export function attemptContinuousHydration(fiber: Fiber): void {
385 - if (fiber.tag !== SuspenseComponent) {
386 - // We ignore HostRoots here because we can't increase
387 - // their priority and they should not suspend on I/O,
388 - // since you have to wrap anything that might suspend in
389 - // Suspense.
390 - return;
391 - }
392 - scheduleUpdateOnFiber(fiber, ContinuousHydration);
393 - markRetryTimeIfNotHydrated(fiber, ContinuousHydration);
394 -}
395 -
396 -export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
397 - if (fiber.tag !== SuspenseComponent) {
398 - // We ignore HostRoots here because we can't increase
399 - // their priority other than synchronously flush it.
400 - return;
401 - }
402 - const currentTime = requestCurrentTimeForUpdate();
403 - const expTime = computeExpirationForFiber(currentTime, fiber, null);
404 - scheduleUpdateOnFiber(fiber, expTime);
405 - markRetryTimeIfNotHydrated(fiber, expTime);
406 -}
407 -
408 -export {findHostInstance};
409 -
410 -export {findHostInstanceWithWarning};
411 -
412 -export function findHostInstanceWithNoPortals(
413 - fiber: Fiber,
414 -): PublicInstance | null {
415 - const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
416 - if (hostFiber === null) {
417 - return null;
418 - }
419 - if (hostFiber.tag === FundamentalComponent) {
420 - return hostFiber.stateNode.instance;
421 - }
422 - return hostFiber.stateNode;
423 -}
424 -
425 -let shouldSuspendImpl = fiber => false;
426 -
427 -export function shouldSuspend(fiber: Fiber): boolean {
428 - return shouldSuspendImpl(fiber);
429 -}
430 -
431 -let overrideHookState = null;
432 -let overrideProps = null;
433 -let scheduleUpdate = null;
434 -let setSuspenseHandler = null;
435 -
436 -if (__DEV__) {
437 - const copyWithSetImpl = (
438 - obj: Object | Array<any>,
439 - path: Array<string | number>,
440 - idx: number,
441 - value: any,
442 - ) => {
443 - if (idx >= path.length) {
444 - return value;
445 - }
446 - const key = path[idx];
447 - const updated = Array.isArray(obj) ? obj.slice() : {...obj};
448 - // $FlowFixMe number or string is fine here
449 - updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
450 - return updated;
451 - };
452 -
453 - const copyWithSet = (
454 - obj: Object | Array<any>,
455 - path: Array<string | number>,
456 - value: any,
457 - ): Object | Array<any> => {
458 - return copyWithSetImpl(obj, path, 0, value);
459 - };
460 -
461 - // Support DevTools editable values for useState and useReducer.
462 - overrideHookState = (
463 - fiber: Fiber,
464 - id: number,
465 - path: Array<string | number>,
466 - value: any,
467 - ) => {
468 - // For now, the "id" of stateful hooks is just the stateful hook index.
469 - // This may change in the future with e.g. nested hooks.
470 - let currentHook = fiber.memoizedState;
471 - while (currentHook !== null && id > 0) {
472 - currentHook = currentHook.next;
473 - id--;
474 - }
475 - if (currentHook !== null) {
476 - const newState = copyWithSet(currentHook.memoizedState, path, value);
477 - currentHook.memoizedState = newState;
478 - currentHook.baseState = newState;
479 -
480 - // We aren't actually adding an update to the queue,
481 - // because there is no update we can add for useReducer hooks that won't trigger an error.
482 - // (There's no appropriate action type for DevTools overrides.)
483 - // As a result though, React will see the scheduled update as a noop and bailout.
484 - // Shallow cloning props works as a workaround for now to bypass the bailout check.
485 - fiber.memoizedProps = {...fiber.memoizedProps};
486 -
487 - scheduleUpdateOnFiber(fiber, Sync);
488 - }
489 - };
490 -
491 - // Support DevTools props for function components, forwardRef, memo, host components, etc.
492 - overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
493 - fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
494 - if (fiber.alternate) {
495 - fiber.alternate.pendingProps = fiber.pendingProps;
496 - }
497 - scheduleUpdateOnFiber(fiber, Sync);
498 - };
499 -
500 - scheduleUpdate = (fiber: Fiber) => {
501 - scheduleUpdateOnFiber(fiber, Sync);
502 - };
503 -
504 - setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
505 - shouldSuspendImpl = newShouldSuspendImpl;
506 - };
507 -}
508 -
509 -export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
510 - const {findFiberByHostInstance} = devToolsConfig;
511 - const {ReactCurrentDispatcher} = ReactSharedInternals;
512 -
513 - return injectInternals({
514 - bundleType: devToolsConfig.bundleType,
515 - version: devToolsConfig.version,
516 - rendererPackageName: devToolsConfig.rendererPackageName,
517 - getInspectorDataForViewTag: devToolsConfig.getInspectorDataForViewTag,
518 - overrideHookState,
519 - overrideProps,
520 - setSuspenseHandler,
521 - scheduleUpdate,
522 - currentDispatcherRef: ReactCurrentDispatcher,
523 - findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
524 - const hostFiber = findCurrentHostFiber(fiber);
525 - if (hostFiber === null) {
526 - return null;
527 - }
528 - return hostFiber.stateNode;
529 - },
530 - findFiberByHostInstance(instance: Instance | TextInstance): Fiber | null {
531 - if (!findFiberByHostInstance) {
532 - // Might not be implemented by the renderer.
533 - return null;
534 - }
535 - return findFiberByHostInstance(instance);
536 - },
537 - // React Refresh
538 - findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
539 - scheduleRefresh: __DEV__ ? scheduleRefresh : null,
540 - scheduleRoot: __DEV__ ? scheduleRoot : null,
541 - setRefreshHandler: __DEV__ ? setRefreshHandler : null,
542 - // Enables DevTools to append owner stacks to error messages in DEV mode.
543 - getCurrentFiber: __DEV__ ? () => ReactCurrentFiberCurrent : null,
544 - });
545 -}
546 -
547 -const {IsSomeRendererActing} = ReactSharedInternals;
548 -const isSchedulerMocked =
549 - typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
550 -const flushWork =
551 - Scheduler.unstable_flushAllWithoutAsserting ||
552 - function() {
553 - let didFlushWork = false;
554 - while (flushPassiveEffects()) {
555 - didFlushWork = true;
556 - }
557 -
558 - return didFlushWork;
559 - };
560 -
561 -function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
562 - try {
563 - flushWork();
564 - enqueueTask(() => {
565 - if (flushWork()) {
566 - flushWorkAndMicroTasks(onDone);
567 - } else {
568 - onDone();
569 - }
570 - });
571 - } catch (err) {
572 - onDone(err);
573 - }
574 -}
575 -
576 -// we track the 'depth' of the act() calls with this counter,
577 -// so we can tell if any async act() calls try to run in parallel.
578 -
579 -let actingUpdatesScopeDepth = 0;
580 -let didWarnAboutUsingActInProd = false;
581 -
582 -// eslint-disable-next-line no-inner-declarations
583 -export function act(callback: () => Thenable) {
584 - if (!__DEV__) {
585 - if (didWarnAboutUsingActInProd === false) {
586 - didWarnAboutUsingActInProd = true;
587 - // eslint-disable-next-line react-internal/no-production-logging
588 - console.error(
589 - 'act(...) is not supported in production builds of React, and might not behave as expected.',
590 - );
591 - }
592 - }
593 -
594 - let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
595 - let previousIsSomeRendererActing;
596 - let previousIsThisRendererActing;
597 - actingUpdatesScopeDepth++;
598 -
599 - previousIsSomeRendererActing = IsSomeRendererActing.current;
600 - previousIsThisRendererActing = IsThisRendererActing.current;
601 - IsSomeRendererActing.current = true;
602 - IsThisRendererActing.current = true;
603 -
604 - function onDone() {
605 - actingUpdatesScopeDepth--;
606 - IsSomeRendererActing.current = previousIsSomeRendererActing;
607 - IsThisRendererActing.current = previousIsThisRendererActing;
608 - if (__DEV__) {
609 - if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
610 - // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
611 - console.error(
612 - 'You seem to have overlapping act() calls, this is not supported. ' +
613 - 'Be sure to await previous act() calls before making a new one. ',
614 - );
615 - }
616 - }
617 - }
618 -
619 - let result;
620 - try {
621 - result = batchedUpdates(callback);
622 - } catch (error) {
623 - // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
624 - onDone();
625 - throw error;
626 - }
627 -
628 - if (
629 - result !== null &&
630 - typeof result === 'object' &&
631 - typeof result.then === 'function'
632 - ) {
633 - // setup a boolean that gets set to true only
634 - // once this act() call is await-ed
635 - let called = false;
636 - if (__DEV__) {
637 - if (typeof Promise !== 'undefined') {
638 - //eslint-disable-next-line no-undef
639 - Promise.resolve()
640 - .then(() => {})
641 - .then(() => {
642 - if (called === false) {
643 - console.error(
644 - 'You called act(async () => ...) without await. ' +
645 - 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
646 - 'calls and mixing their scopes. You should - await act(async () => ...);',
647 - );
648 - }
649 - });
650 - }
651 - }
652 -
653 - // in the async case, the returned thenable runs the callback, flushes
654 - // effects and microtasks in a loop until flushPassiveEffects() === false,
655 - // and cleans up
656 - return {
657 - then(resolve: () => void, reject: (?Error) => void) {
658 - called = true;
659 - result.then(
660 - () => {
661 - if (
662 - actingUpdatesScopeDepth > 1 ||
663 - (isSchedulerMocked === true &&
664 - previousIsSomeRendererActing === true)
665 - ) {
666 - onDone();
667 - resolve();
668 - return;
669 - }
670 - // we're about to exit the act() scope,
671 - // now's the time to flush tasks/effects
672 - flushWorkAndMicroTasks((err: ?Error) => {
673 - onDone();
674 - if (err) {
675 - reject(err);
676 - } else {
677 - resolve();
678 - }
679 - });
680 - },
681 - err => {
682 - onDone();
683 - reject(err);
684 - },
685 - );
686 - },
687 - };
688 - } else {
689 - if (__DEV__) {
690 - if (result !== undefined) {
691 - console.error(
692 - 'The callback passed to act(...) function ' +
693 - 'must return undefined, or a Promise. You returned %s',
694 - result,
695 - );
696 - }
697 - }
698 -
699 - // flush effects until none remain, and cleanup
700 - try {
701 - if (
702 - actingUpdatesScopeDepth === 1 &&
703 - (isSchedulerMocked === false || previousIsSomeRendererActing === false)
704 - ) {
705 - // we're about to exit the act() scope,
706 - // now's the time to flush effects
707 - flushWork();
708 - }
709 - onDone();
710 - } catch (err) {
711 - onDone();
712 - throw err;
713 - }
714 -
715 - // in the sync case, the returned thenable only warns *if* await-ed
716 - return {
717 - then(resolve: () => void) {
718 - if (__DEV__) {
719 - console.error(
720 - 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
721 - );
722 - }
723 - resolve();
724 - },
725 - };
726 - }
727 -}
46 + createContainer as createContainer_new,
47 + updateContainer as updateContainer_new,
48 + batchedEventUpdates as batchedEventUpdates_new,
49 + batchedUpdates as batchedUpdates_new,
50 + unbatchedUpdates as unbatchedUpdates_new,
51 + deferredUpdates as deferredUpdates_new,
52 + syncUpdates as syncUpdates_new,
53 + discreteUpdates as discreteUpdates_new,
54 + flushDiscreteUpdates as flushDiscreteUpdates_new,
55 + flushControlled as flushControlled_new,
56 + flushSync as flushSync_new,
57 + flushPassiveEffects as flushPassiveEffects_new,
58 + IsThisRendererActing as IsThisRendererActing_new,
59 + getPublicRootInstance as getPublicRootInstance_new,
60 + attemptSynchronousHydration as attemptSynchronousHydration_new,
61 + attemptUserBlockingHydration as attemptUserBlockingHydration_new,
62 + attemptContinuousHydration as attemptContinuousHydration_new,
63 + attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_new,
64 + findHostInstance as findHostInstance_new,
65 + findHostInstanceWithWarning as findHostInstanceWithWarning_new,
66 + findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_new,
67 + shouldSuspend as shouldSuspend_new,
68 + injectIntoDevTools as injectIntoDevTools_new,
69 + act as act_new,
70 +} from './ReactFiberReconciler.old';
71 +
72 +export const createContainer = enableNewReconciler
73 + ? createContainer_new
74 + : createContainer_old;
75 +export const updateContainer = enableNewReconciler
76 + ? updateContainer_new
77 + : updateContainer_old;
78 +export const batchedEventUpdates = enableNewReconciler
79 + ? batchedEventUpdates_new
80 + : batchedEventUpdates_old;
81 +export const batchedUpdates = enableNewReconciler
82 + ? batchedUpdates_new
83 + : batchedUpdates_old;
84 +export const unbatchedUpdates = enableNewReconciler
85 + ? unbatchedUpdates_new
86 + : unbatchedUpdates_old;
87 +export const deferredUpdates = enableNewReconciler
88 + ? deferredUpdates_new
89 + : deferredUpdates_old;
90 +export const syncUpdates = enableNewReconciler
91 + ? syncUpdates_new
92 + : syncUpdates_old;
93 +export const discreteUpdates = enableNewReconciler
94 + ? discreteUpdates_new
95 + : discreteUpdates_old;
96 +export const flushDiscreteUpdates = enableNewReconciler
97 + ? flushDiscreteUpdates_new
98 + : flushDiscreteUpdates_old;
99 +export const flushControlled = enableNewReconciler
100 + ? flushControlled_new
101 + : flushControlled_old;
102 +export const flushSync = enableNewReconciler ? flushSync_new : flushSync_old;
103 +export const flushPassiveEffects = enableNewReconciler
104 + ? flushPassiveEffects_new
105 + : flushPassiveEffects_old;
106 +export const IsThisRendererActing = enableNewReconciler
107 + ? IsThisRendererActing_new
108 + : IsThisRendererActing_old;
109 +export const getPublicRootInstance = enableNewReconciler
110 + ? getPublicRootInstance_new
111 + : getPublicRootInstance_old;
112 +export const attemptSynchronousHydration = enableNewReconciler
113 + ? attemptSynchronousHydration_new
114 + : attemptSynchronousHydration_old;
115 +export const attemptUserBlockingHydration = enableNewReconciler
116 + ? attemptUserBlockingHydration_new
117 + : attemptUserBlockingHydration_old;
118 +export const attemptContinuousHydration = enableNewReconciler
119 + ? attemptContinuousHydration_new
120 + : attemptContinuousHydration_old;
121 +export const attemptHydrationAtCurrentPriority = enableNewReconciler
122 + ? attemptHydrationAtCurrentPriority_new
123 + : attemptHydrationAtCurrentPriority_old;
124 +export const findHostInstance = enableNewReconciler
125 + ? findHostInstance_new
126 + : findHostInstance_old;
127 +export const findHostInstanceWithWarning = enableNewReconciler
128 + ? findHostInstanceWithWarning_new
129 + : findHostInstanceWithWarning_old;
130 +export const findHostInstanceWithNoPortals = enableNewReconciler
131 + ? findHostInstanceWithNoPortals_new
132 + : findHostInstanceWithNoPortals_old;
133 +export const shouldSuspend = enableNewReconciler
134 + ? shouldSuspend_new
135 + : shouldSuspend_old;
136 +export const injectIntoDevTools = enableNewReconciler
137 + ? injectIntoDevTools_new
138 + : injectIntoDevTools_old;
139 +export const act = enableNewReconciler ? act_new : act_old;
packages/react-reconciler/src/ReactFiberReconciler.old.js new
+727
@@ -0,0 +1,727 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Fiber} from './ReactFiber';
11 +import type {FiberRoot} from './ReactFiberRoot';
12 +import type {RootTag} from 'shared/ReactRootTags';
13 +import type {
14 + Instance,
15 + TextInstance,
16 + Container,
17 + PublicInstance,
18 +} from './ReactFiberHostConfig';
19 +import {FundamentalComponent} from 'shared/ReactWorkTags';
20 +import type {ReactNodeList} from 'shared/ReactTypes';
21 +import type {ExpirationTime} from './ReactFiberExpirationTime';
22 +import type {
23 + SuspenseHydrationCallbacks,
24 + SuspenseState,
25 +} from './ReactFiberSuspenseComponent';
26 +
27 +import {
28 + findCurrentHostFiber,
29 + findCurrentHostFiberWithNoPortals,
30 +} from 'react-reconciler/src/ReactFiberTreeReflection';
31 +import {get as getInstance} from 'shared/ReactInstanceMap';
32 +import {
33 + HostComponent,
34 + ClassComponent,
35 + HostRoot,
36 + SuspenseComponent,
37 +} from 'shared/ReactWorkTags';
38 +import getComponentName from 'shared/getComponentName';
39 +import invariant from 'shared/invariant';
40 +import ReactSharedInternals from 'shared/ReactSharedInternals';
41 +
42 +import {getPublicInstance} from './ReactFiberHostConfig';
43 +import {
44 + findCurrentUnmaskedContext,
45 + processChildContext,
46 + emptyContextObject,
47 + isContextProvider as isLegacyContextProvider,
48 +} from './ReactFiberContext';
49 +import {createFiberRoot} from './ReactFiberRoot';
50 +import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook';
51 +import {
52 + requestCurrentTimeForUpdate,
53 + computeExpirationForFiber,
54 + scheduleUpdateOnFiber,
55 + flushRoot,
56 + batchedEventUpdates,
57 + batchedUpdates,
58 + unbatchedUpdates,
59 + flushSync,
60 + flushControlled,
61 + deferredUpdates,
62 + syncUpdates,
63 + discreteUpdates,
64 + flushDiscreteUpdates,
65 + flushPassiveEffects,
66 + warnIfNotScopedWithMatchingAct,
67 + warnIfUnmockedScheduler,
68 + IsThisRendererActing,
69 +} from './ReactFiberWorkLoop';
70 +import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
71 +import {
72 + getStackByFiberInDevAndProd,
73 + isRendering as ReactCurrentFiberIsRendering,
74 + current as ReactCurrentFiberCurrent,
75 +} from './ReactCurrentFiber';
76 +import {StrictMode} from './ReactTypeOfMode';
77 +import {
78 + Sync,
79 + ContinuousHydration,
80 + computeInteractiveExpiration,
81 +} from './ReactFiberExpirationTime';
82 +import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
83 +import {
84 + scheduleRefresh,
85 + scheduleRoot,
86 + setRefreshHandler,
87 + findHostInstancesForRefresh,
88 +} from './ReactFiberHotReloading';
89 +
90 +// used by isTestEnvironment builds
91 +import enqueueTask from 'shared/enqueueTask';
92 +import * as Scheduler from 'scheduler';
93 +// end isTestEnvironment imports
94 +
95 +type OpaqueRoot = FiberRoot;
96 +
97 +// 0 is PROD, 1 is DEV.
98 +// Might add PROFILE later.
99 +type BundleType = 0 | 1;
100 +
101 +type DevToolsConfig = {|
102 + bundleType: BundleType,
103 + version: string,
104 + rendererPackageName: string,
105 + // Note: this actually *does* depend on Fiber internal fields.
106 + // Used by "inspect clicked DOM element" in React DevTools.
107 + findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
108 + // Used by RN in-app inspector.
109 + // This API is unfortunately RN-specific.
110 + // TODO: Change it to accept Fiber instead and type it properly.
111 + getInspectorDataForViewTag?: (tag: number) => Object,
112 +|};
113 +
114 +let didWarnAboutNestedUpdates;
115 +let didWarnAboutFindNodeInStrictMode;
116 +
117 +if (__DEV__) {
118 + didWarnAboutNestedUpdates = false;
119 + didWarnAboutFindNodeInStrictMode = {};
120 +}
121 +
122 +function getContextForSubtree(
123 + parentComponent: ?React$Component<any, any>,
124 +): Object {
125 + if (!parentComponent) {
126 + return emptyContextObject;
127 + }
128 +
129 + const fiber = getInstance(parentComponent);
130 + const parentContext = findCurrentUnmaskedContext(fiber);
131 +
132 + if (fiber.tag === ClassComponent) {
133 + const Component = fiber.type;
134 + if (isLegacyContextProvider(Component)) {
135 + return processChildContext(fiber, Component, parentContext);
136 + }
137 + }
138 +
139 + return parentContext;
140 +}
141 +
142 +function findHostInstance(component: Object): PublicInstance | null {
143 + const fiber = getInstance(component);
144 + if (fiber === undefined) {
145 + if (typeof component.render === 'function') {
146 + invariant(false, 'Unable to find node on an unmounted component.');
147 + } else {
148 + invariant(
149 + false,
150 + 'Argument appears to not be a ReactComponent. Keys: %s',
151 + Object.keys(component),
152 + );
153 + }
154 + }
155 + const hostFiber = findCurrentHostFiber(fiber);
156 + if (hostFiber === null) {
157 + return null;
158 + }
159 + return hostFiber.stateNode;
160 +}
161 +
162 +function findHostInstanceWithWarning(
163 + component: Object,
164 + methodName: string,
165 +): PublicInstance | null {
166 + if (__DEV__) {
167 + const fiber = getInstance(component);
168 + if (fiber === undefined) {
169 + if (typeof component.render === 'function') {
170 + invariant(false, 'Unable to find node on an unmounted component.');
171 + } else {
172 + invariant(
173 + false,
174 + 'Argument appears to not be a ReactComponent. Keys: %s',
175 + Object.keys(component),
176 + );
177 + }
178 + }
179 + const hostFiber = findCurrentHostFiber(fiber);
180 + if (hostFiber === null) {
181 + return null;
182 + }
183 + if (hostFiber.mode & StrictMode) {
184 + const componentName = getComponentName(fiber.type) || 'Component';
185 + if (!didWarnAboutFindNodeInStrictMode[componentName]) {
186 + didWarnAboutFindNodeInStrictMode[componentName] = true;
187 + if (fiber.mode & StrictMode) {
188 + console.error(
189 + '%s is deprecated in StrictMode. ' +
190 + '%s was passed an instance of %s which is inside StrictMode. ' +
191 + 'Instead, add a ref directly to the element you want to reference. ' +
192 + 'Learn more about using refs safely here: ' +
193 + 'https://fb.me/react-strict-mode-find-node%s',
194 + methodName,
195 + methodName,
196 + componentName,
197 + getStackByFiberInDevAndProd(hostFiber),
198 + );
199 + } else {
200 + console.error(
201 + '%s is deprecated in StrictMode. ' +
202 + '%s was passed an instance of %s which renders StrictMode children. ' +
203 + 'Instead, add a ref directly to the element you want to reference. ' +
204 + 'Learn more about using refs safely here: ' +
205 + 'https://fb.me/react-strict-mode-find-node%s',
206 + methodName,
207 + methodName,
208 + componentName,
209 + getStackByFiberInDevAndProd(hostFiber),
210 + );
211 + }
212 + }
213 + }
214 + return hostFiber.stateNode;
215 + }
216 + return findHostInstance(component);
217 +}
218 +
219 +export function createContainer(
220 + containerInfo: Container,
221 + tag: RootTag,
222 + hydrate: boolean,
223 + hydrationCallbacks: null | SuspenseHydrationCallbacks,
224 +): OpaqueRoot {
225 + return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
226 +}
227 +
228 +export function updateContainer(
229 + element: ReactNodeList,
230 + container: OpaqueRoot,
231 + parentComponent: ?React$Component<any, any>,
232 + callback: ?Function,
233 +): ExpirationTime {
234 + if (__DEV__) {
235 + onScheduleRoot(container, element);
236 + }
237 + const current = container.current;
238 + const currentTime = requestCurrentTimeForUpdate();
239 + if (__DEV__) {
240 + // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
241 + if ('undefined' !== typeof jest) {
242 + warnIfUnmockedScheduler(current);
243 + warnIfNotScopedWithMatchingAct(current);
244 + }
245 + }
246 + const suspenseConfig = requestCurrentSuspenseConfig();
247 + const expirationTime = computeExpirationForFiber(
248 + currentTime,
249 + current,
250 + suspenseConfig,
251 + );
252 +
253 + const context = getContextForSubtree(parentComponent);
254 + if (container.context === null) {
255 + container.context = context;
256 + } else {
257 + container.pendingContext = context;
258 + }
259 +
260 + if (__DEV__) {
261 + if (
262 + ReactCurrentFiberIsRendering &&
263 + ReactCurrentFiberCurrent !== null &&
264 + !didWarnAboutNestedUpdates
265 + ) {
266 + didWarnAboutNestedUpdates = true;
267 + console.error(
268 + 'Render methods should be a pure function of props and state; ' +
269 + 'triggering nested component updates from render is not allowed. ' +
270 + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
271 + 'Check the render method of %s.',
272 + getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
273 + );
274 + }
275 + }
276 +
277 + const update = createUpdate(expirationTime, suspenseConfig);
278 + // Caution: React DevTools currently depends on this property
279 + // being called "element".
280 + update.payload = {element};
281 +
282 + callback = callback === undefined ? null : callback;
283 + if (callback !== null) {
284 + if (__DEV__) {
285 + if (typeof callback !== 'function') {
286 + console.error(
287 + 'render(...): Expected the last optional `callback` argument to be a ' +
288 + 'function. Instead received: %s.',
289 + callback,
290 + );
291 + }
292 + }
293 + update.callback = callback;
294 + }
295 +
296 + enqueueUpdate(current, update);
297 + scheduleUpdateOnFiber(current, expirationTime);
298 +
299 + return expirationTime;
300 +}
301 +
302 +export {
303 + batchedEventUpdates,
304 + batchedUpdates,
305 + unbatchedUpdates,
306 + deferredUpdates,
307 + syncUpdates,
308 + discreteUpdates,
309 + flushDiscreteUpdates,
310 + flushControlled,
311 + flushSync,
312 + flushPassiveEffects,
313 + IsThisRendererActing,
314 +};
315 +
316 +export function getPublicRootInstance(
317 + container: OpaqueRoot,
318 +): React$Component<any, any> | PublicInstance | null {
319 + const containerFiber = container.current;
320 + if (!containerFiber.child) {
321 + return null;
322 + }
323 + switch (containerFiber.child.tag) {
324 + case HostComponent:
325 + return getPublicInstance(containerFiber.child.stateNode);
326 + default:
327 + return containerFiber.child.stateNode;
328 + }
329 +}
330 +
331 +export function attemptSynchronousHydration(fiber: Fiber): void {
332 + switch (fiber.tag) {
333 + case HostRoot:
334 + let root: FiberRoot = fiber.stateNode;
335 + if (root.hydrate) {
336 + // Flush the first scheduled "update".
337 + flushRoot(root, root.firstPendingTime);
338 + }
339 + break;
340 + case SuspenseComponent:
341 + flushSync(() => scheduleUpdateOnFiber(fiber, Sync));
342 + // If we're still blocked after this, we need to increase
343 + // the priority of any promises resolving within this
344 + // boundary so that they next attempt also has higher pri.
345 + let retryExpTime = computeInteractiveExpiration(
346 + requestCurrentTimeForUpdate(),
347 + );
348 + markRetryTimeIfNotHydrated(fiber, retryExpTime);
349 + break;
350 + }
351 +}
352 +
353 +function markRetryTimeImpl(fiber: Fiber, retryTime: ExpirationTime) {
354 + let suspenseState: null | SuspenseState = fiber.memoizedState;
355 + if (suspenseState !== null && suspenseState.dehydrated !== null) {
356 + if (suspenseState.retryTime < retryTime) {
357 + suspenseState.retryTime = retryTime;
358 + }
359 + }
360 +}
361 +
362 +// Increases the priority of thennables when they resolve within this boundary.
363 +function markRetryTimeIfNotHydrated(fiber: Fiber, retryTime: ExpirationTime) {
364 + markRetryTimeImpl(fiber, retryTime);
365 + let alternate = fiber.alternate;
366 + if (alternate) {
367 + markRetryTimeImpl(alternate, retryTime);
368 + }
369 +}
370 +
371 +export function attemptUserBlockingHydration(fiber: Fiber): void {
372 + if (fiber.tag !== SuspenseComponent) {
373 + // We ignore HostRoots here because we can't increase
374 + // their priority and they should not suspend on I/O,
375 + // since you have to wrap anything that might suspend in
376 + // Suspense.
377 + return;
378 + }
379 + let expTime = computeInteractiveExpiration(requestCurrentTimeForUpdate());
380 + scheduleUpdateOnFiber(fiber, expTime);
381 + markRetryTimeIfNotHydrated(fiber, expTime);
382 +}
383 +
384 +export function attemptContinuousHydration(fiber: Fiber): void {
385 + if (fiber.tag !== SuspenseComponent) {
386 + // We ignore HostRoots here because we can't increase
387 + // their priority and they should not suspend on I/O,
388 + // since you have to wrap anything that might suspend in
389 + // Suspense.
390 + return;
391 + }
392 + scheduleUpdateOnFiber(fiber, ContinuousHydration);
393 + markRetryTimeIfNotHydrated(fiber, ContinuousHydration);
394 +}
395 +
396 +export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
397 + if (fiber.tag !== SuspenseComponent) {
398 + // We ignore HostRoots here because we can't increase
399 + // their priority other than synchronously flush it.
400 + return;
401 + }
402 + const currentTime = requestCurrentTimeForUpdate();
403 + const expTime = computeExpirationForFiber(currentTime, fiber, null);
404 + scheduleUpdateOnFiber(fiber, expTime);
405 + markRetryTimeIfNotHydrated(fiber, expTime);
406 +}
407 +
408 +export {findHostInstance};
409 +
410 +export {findHostInstanceWithWarning};
411 +
412 +export function findHostInstanceWithNoPortals(
413 + fiber: Fiber,
414 +): PublicInstance | null {
415 + const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
416 + if (hostFiber === null) {
417 + return null;
418 + }
419 + if (hostFiber.tag === FundamentalComponent) {
420 + return hostFiber.stateNode.instance;
421 + }
422 + return hostFiber.stateNode;
423 +}
424 +
425 +let shouldSuspendImpl = fiber => false;
426 +
427 +export function shouldSuspend(fiber: Fiber): boolean {
428 + return shouldSuspendImpl(fiber);
429 +}
430 +
431 +let overrideHookState = null;
432 +let overrideProps = null;
433 +let scheduleUpdate = null;
434 +let setSuspenseHandler = null;
435 +
436 +if (__DEV__) {
437 + const copyWithSetImpl = (
438 + obj: Object | Array<any>,
439 + path: Array<string | number>,
440 + idx: number,
441 + value: any,
442 + ) => {
443 + if (idx >= path.length) {
444 + return value;
445 + }
446 + const key = path[idx];
447 + const updated = Array.isArray(obj) ? obj.slice() : {...obj};
448 + // $FlowFixMe number or string is fine here
449 + updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
450 + return updated;
451 + };
452 +
453 + const copyWithSet = (
454 + obj: Object | Array<any>,
455 + path: Array<string | number>,
456 + value: any,
457 + ): Object | Array<any> => {
458 + return copyWithSetImpl(obj, path, 0, value);
459 + };
460 +
461 + // Support DevTools editable values for useState and useReducer.
462 + overrideHookState = (
463 + fiber: Fiber,
464 + id: number,
465 + path: Array<string | number>,
466 + value: any,
467 + ) => {
468 + // For now, the "id" of stateful hooks is just the stateful hook index.
469 + // This may change in the future with e.g. nested hooks.
470 + let currentHook = fiber.memoizedState;
471 + while (currentHook !== null && id > 0) {
472 + currentHook = currentHook.next;
473 + id--;
474 + }
475 + if (currentHook !== null) {
476 + const newState = copyWithSet(currentHook.memoizedState, path, value);
477 + currentHook.memoizedState = newState;
478 + currentHook.baseState = newState;
479 +
480 + // We aren't actually adding an update to the queue,
481 + // because there is no update we can add for useReducer hooks that won't trigger an error.
482 + // (There's no appropriate action type for DevTools overrides.)
483 + // As a result though, React will see the scheduled update as a noop and bailout.
484 + // Shallow cloning props works as a workaround for now to bypass the bailout check.
485 + fiber.memoizedProps = {...fiber.memoizedProps};
486 +
487 + scheduleUpdateOnFiber(fiber, Sync);
488 + }
489 + };
490 +
491 + // Support DevTools props for function components, forwardRef, memo, host components, etc.
492 + overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
493 + fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
494 + if (fiber.alternate) {
495 + fiber.alternate.pendingProps = fiber.pendingProps;
496 + }
497 + scheduleUpdateOnFiber(fiber, Sync);
498 + };
499 +
500 + scheduleUpdate = (fiber: Fiber) => {
501 + scheduleUpdateOnFiber(fiber, Sync);
502 + };
503 +
504 + setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
505 + shouldSuspendImpl = newShouldSuspendImpl;
506 + };
507 +}
508 +
509 +export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
510 + const {findFiberByHostInstance} = devToolsConfig;
511 + const {ReactCurrentDispatcher} = ReactSharedInternals;
512 +
513 + return injectInternals({
514 + bundleType: devToolsConfig.bundleType,
515 + version: devToolsConfig.version,
516 + rendererPackageName: devToolsConfig.rendererPackageName,
517 + getInspectorDataForViewTag: devToolsConfig.getInspectorDataForViewTag,
518 + overrideHookState,
519 + overrideProps,
520 + setSuspenseHandler,
521 + scheduleUpdate,
522 + currentDispatcherRef: ReactCurrentDispatcher,
523 + findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
524 + const hostFiber = findCurrentHostFiber(fiber);
525 + if (hostFiber === null) {
526 + return null;
527 + }
528 + return hostFiber.stateNode;
529 + },
530 + findFiberByHostInstance(instance: Instance | TextInstance): Fiber | null {
531 + if (!findFiberByHostInstance) {
532 + // Might not be implemented by the renderer.
533 + return null;
534 + }
535 + return findFiberByHostInstance(instance);
536 + },
537 + // React Refresh
538 + findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
539 + scheduleRefresh: __DEV__ ? scheduleRefresh : null,
540 + scheduleRoot: __DEV__ ? scheduleRoot : null,
541 + setRefreshHandler: __DEV__ ? setRefreshHandler : null,
542 + // Enables DevTools to append owner stacks to error messages in DEV mode.
543 + getCurrentFiber: __DEV__ ? () => ReactCurrentFiberCurrent : null,
544 + });
545 +}
546 +
547 +const {IsSomeRendererActing} = ReactSharedInternals;
548 +const isSchedulerMocked =
549 + typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
550 +const flushWork =
551 + Scheduler.unstable_flushAllWithoutAsserting ||
552 + function() {
553 + let didFlushWork = false;
554 + while (flushPassiveEffects()) {
555 + didFlushWork = true;
556 + }
557 +
558 + return didFlushWork;
559 + };
560 +
561 +function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
562 + try {
563 + flushWork();
564 + enqueueTask(() => {
565 + if (flushWork()) {
566 + flushWorkAndMicroTasks(onDone);
567 + } else {
568 + onDone();
569 + }
570 + });
571 + } catch (err) {
572 + onDone(err);
573 + }
574 +}
575 +
576 +// we track the 'depth' of the act() calls with this counter,
577 +// so we can tell if any async act() calls try to run in parallel.
578 +
579 +let actingUpdatesScopeDepth = 0;
580 +let didWarnAboutUsingActInProd = false;
581 +
582 +// eslint-disable-next-line no-inner-declarations
583 +export function act(callback: () => Thenable) {
584 + if (!__DEV__) {
585 + if (didWarnAboutUsingActInProd === false) {
586 + didWarnAboutUsingActInProd = true;
587 + // eslint-disable-next-line react-internal/no-production-logging
588 + console.error(
589 + 'act(...) is not supported in production builds of React, and might not behave as expected.',
590 + );
591 + }
592 + }
593 +
594 + let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
595 + let previousIsSomeRendererActing;
596 + let previousIsThisRendererActing;
597 + actingUpdatesScopeDepth++;
598 +
599 + previousIsSomeRendererActing = IsSomeRendererActing.current;
600 + previousIsThisRendererActing = IsThisRendererActing.current;
601 + IsSomeRendererActing.current = true;
602 + IsThisRendererActing.current = true;
603 +
604 + function onDone() {
605 + actingUpdatesScopeDepth--;
606 + IsSomeRendererActing.current = previousIsSomeRendererActing;
607 + IsThisRendererActing.current = previousIsThisRendererActing;
608 + if (__DEV__) {
609 + if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
610 + // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
611 + console.error(
612 + 'You seem to have overlapping act() calls, this is not supported. ' +
613 + 'Be sure to await previous act() calls before making a new one. ',
614 + );
615 + }
616 + }
617 + }
618 +
619 + let result;
620 + try {
621 + result = batchedUpdates(callback);
622 + } catch (error) {
623 + // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
624 + onDone();
625 + throw error;
626 + }
627 +
628 + if (
629 + result !== null &&
630 + typeof result === 'object' &&
631 + typeof result.then === 'function'
632 + ) {
633 + // setup a boolean that gets set to true only
634 + // once this act() call is await-ed
635 + let called = false;
636 + if (__DEV__) {
637 + if (typeof Promise !== 'undefined') {
638 + //eslint-disable-next-line no-undef
639 + Promise.resolve()
640 + .then(() => {})
641 + .then(() => {
642 + if (called === false) {
643 + console.error(
644 + 'You called act(async () => ...) without await. ' +
645 + 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
646 + 'calls and mixing their scopes. You should - await act(async () => ...);',
647 + );
648 + }
649 + });
650 + }
651 + }
652 +
653 + // in the async case, the returned thenable runs the callback, flushes
654 + // effects and microtasks in a loop until flushPassiveEffects() === false,
655 + // and cleans up
656 + return {
657 + then(resolve: () => void, reject: (?Error) => void) {
658 + called = true;
659 + result.then(
660 + () => {
661 + if (
662 + actingUpdatesScopeDepth > 1 ||
663 + (isSchedulerMocked === true &&
664 + previousIsSomeRendererActing === true)
665 + ) {
666 + onDone();
667 + resolve();
668 + return;
669 + }
670 + // we're about to exit the act() scope,
671 + // now's the time to flush tasks/effects
672 + flushWorkAndMicroTasks((err: ?Error) => {
673 + onDone();
674 + if (err) {
675 + reject(err);
676 + } else {
677 + resolve();
678 + }
679 + });
680 + },
681 + err => {
682 + onDone();
683 + reject(err);
684 + },
685 + );
686 + },
687 + };
688 + } else {
689 + if (__DEV__) {
690 + if (result !== undefined) {
691 + console.error(
692 + 'The callback passed to act(...) function ' +
693 + 'must return undefined, or a Promise. You returned %s',
694 + result,
695 + );
696 + }
697 + }
698 +
699 + // flush effects until none remain, and cleanup
700 + try {
701 + if (
702 + actingUpdatesScopeDepth === 1 &&
703 + (isSchedulerMocked === false || previousIsSomeRendererActing === false)
704 + ) {
705 + // we're about to exit the act() scope,
706 + // now's the time to flush effects
707 + flushWork();
708 + }
709 + onDone();
710 + } catch (err) {
711 + onDone();
712 + throw err;
713 + }
714 +
715 + // in the sync case, the returned thenable only warns *if* await-ed
716 + return {
717 + then(resolve: () => void) {
718 + if (__DEV__) {
719 + console.error(
720 + 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
721 + );
722 + }
723 + resolve();
724 + },
725 + };
726 + }
727 +}
packages/shared/ReactFeatureFlags.js
+2
@@ -111,6 +111,8 @@ export const warnAboutSpreadingKeyToJSX = false;
111 // Internal-only attempt to debug a React Native issue. See D20130868.
112 export const throwEarlyForMysteriousError = false;
113
114 +export const enableNewReconciler = false;
115 +
116 // --------------------------
117 // Future APIs to be deprecated
118 // --------------------------
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -52,6 +52,8 @@ export const enableLegacyFBPrimerSupport = false;
52 // Internal-only attempt to debug a React Native issue. See D20130868.
53 export const throwEarlyForMysteriousError = true;
54
55 +export const enableNewReconciler = false;
56 +
57 // Only used in www builds.
58 export function addUserTimingListener() {
59 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -51,6 +51,8 @@ export const enableLegacyFBPrimerSupport = false;
51 // Internal-only attempt to debug a React Native issue. See D20130868.
52 export const throwEarlyForMysteriousError = false;
53
54 +export const enableNewReconciler = false;
55 +
56 // Only used in www builds.
57 export function addUserTimingListener() {
58 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2
@@ -51,6 +51,8 @@ export const enableLegacyFBPrimerSupport = false;
51 // Internal-only attempt to debug a React Native issue. See D20130868.
52 export const throwEarlyForMysteriousError = false;
53
54 +export const enableNewReconciler = false;
55 +
56 // Only used in www builds.
57 export function addUserTimingListener() {
58 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -51,6 +51,8 @@ export const enableLegacyFBPrimerSupport = false;
51 // Internal-only attempt to debug a React Native issue. See D20130868.
52 export const throwEarlyForMysteriousError = false;
53
54 +export const enableNewReconciler = false;
55 +
56 // Only used in www builds.
57 export function addUserTimingListener() {
58 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.testing.js
+2
@@ -51,6 +51,8 @@ export const enableLegacyFBPrimerSupport = false;
51 // Internal-only attempt to debug a React Native issue. See D20130868.
52 export const throwEarlyForMysteriousError = false;
53
54 +export const enableNewReconciler = false;
55 +
56 // Only used in www builds.
57 export function addUserTimingListener() {
58 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.testing.www.js
+2
@@ -51,6 +51,8 @@ export const enableLegacyFBPrimerSupport = !__EXPERIMENTAL__;
51 // Internal-only attempt to debug a React Native issue. See D20130868.
52 export const throwEarlyForMysteriousError = false;
53
54 +export const enableNewReconciler = false;
55 +
56 // Only used in www builds.
57 export function addUserTimingListener() {
58 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.www.js
+5
@@ -109,6 +109,11 @@ export const enableLegacyFBPrimerSupport = !__EXPERIMENTAL__;
109 // Internal-only attempt to debug a React Native issue. See D20130868.
110 export const throwEarlyForMysteriousError = false;
111
112 +// Enable forked reconciler. Piggy-backing on the "variant" global so that we
113 +// don't have to add another test dimension. The build system will compile this
114 +// to the correct value.
115 +export const enableNewReconciler = __VARIANT__;
116 +
117 // Flow magic to verify the exports of this file match the original version.
118 // eslint-disable-next-line no-unused-vars
119 type Check<_X, Y: _X, X: Y = _X> = null;
scripts/jest/setupHostConfigs.js
+9
@@ -2,6 +2,15 @@
2
3 const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
4
5 +jest.mock('react-reconciler/src/ReactFiberReconciler', () => {
6 + return require.requireActual(
7 + __VARIANT__
8 + ? // TODO: Update this to point to the new module, once it exists
9 + 'react-reconciler/src/ReactFiberReconciler.old'
10 + : 'react-reconciler/src/ReactFiberReconciler.old'
11 + );
12 +});
13 +
14 // When testing the custom renderer code path through `react-reconciler`,
15 // turn the export into a function, and use the argument as host config.
16 const shimHostConfigPath = 'react-reconciler/src/ReactFiberHostConfig';
scripts/rollup/build.js
+8 -4
@@ -324,10 +324,11 @@ function getPlugins(
324 bundleType,
325 globalName,
326 moduleType,
327 - pureExternalModules
327 + pureExternalModules,
328 + bundle
329 ) {
330 const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
330 - const forks = Modules.getForks(bundleType, entry, moduleType);
331 + const forks = Modules.getForks(bundleType, entry, moduleType, bundle);
332 const isProduction = isProductionBundleType(bundleType);
333 const isProfiling = isProfilingBundleType(bundleType);
334 const isUMDBundle =
@@ -391,7 +392,9 @@ function getPlugins(
392 __UMD__: isUMDBundle ? 'true' : 'false',
393 'process.env.NODE_ENV': isProduction ? "'production'" : "'development'",
394 __EXPERIMENTAL__,
394 - __VARIANT__: false,
395 + // Enable forked reconciler.
396 + // NOTE: I did not put much thought into how to configure this.
397 + __VARIANT__: bundle.enableNewReconciler === true,
398 }),
399 // The CommonJS plugin *only* exists to pull "art" into "react-art".
400 // I'm going to port "art" to ES modules to avoid this problem.
@@ -578,7 +581,8 @@ async function createBundle(bundle, bundleType) {
581 bundleType,
582 bundle.global,
583 bundle.moduleType,
581 - pureExternalModules
584 + pureExternalModules,
585 + bundle
586 ),
587 output: {
588 externalLiveBindings: false,
scripts/rollup/bundles.js
+10
@@ -95,6 +95,16 @@ const bundles = [
95 externals: ['react'],
96 },
97
98 + /******* React DOM - www - Uses forked reconciler *******/
99 + {
100 + moduleType: RENDERER,
101 + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD, FB_WWW_PROFILING],
102 + entry: 'react-dom',
103 + global: 'ReactDOMForked',
104 + enableNewReconciler: true,
105 + externals: ['react'],
106 + },
107 +
108 /******* Test Utils *******/
109 {
110 moduleType: RENDERER_UTILS,
scripts/rollup/forks.js
+21
@@ -246,6 +246,27 @@ const forks = Object.freeze({
246 }
247 },
248
249 + 'react-reconciler/src/ReactFiberReconciler': (
250 + bundleType,
251 + entry,
252 + dependencies,
253 + moduleType,
254 + bundle
255 + ) => {
256 + if (bundle.enableNewReconciler) {
257 + switch (bundleType) {
258 + case FB_WWW_DEV:
259 + case FB_WWW_PROD:
260 + case FB_WWW_PROFILING:
261 + // Use the forked version of the reconciler
262 + // TODO: Update this to point to the new module, once it exists
263 + return 'react-reconciler/src/ReactFiberReconciler.old.js';
264 + }
265 + }
266 + // Otherwise, use the non-forked version.
267 + return 'react-reconciler/src/ReactFiberReconciler.old.js';
268 + },
269 +
270 // Different dialogs for caught errors.
271 'react-reconciler/src/ReactFiberErrorDialog': (bundleType, entry) => {
272 switch (bundleType) {
scripts/rollup/modules.js
+3 -2
@@ -60,7 +60,7 @@ function getDependencies(bundleType, entry) {
60 }
61
62 // Hijacks some modules for optimization and integration reasons.
63 -function getForks(bundleType, entry, moduleType) {
63 +function getForks(bundleType, entry, moduleType, bundle) {
64 const forksForBundle = {};
65 Object.keys(forks).forEach(srcModule => {
66 const dependencies = getDependencies(bundleType, entry);
@@ -68,7 +68,8 @@ function getForks(bundleType, entry, moduleType) {
68 bundleType,
69 entry,
70 dependencies,
71 - moduleType
71 + moduleType,
72 + bundle
73 );
74 if (targetModule === null) {
75 return;