@samitouri / QOS-React-2 / commits / 22dc2e42bd

Add experimental DebugTracing logger for internal use (#18531)

Brian Vaughn committed Apr 15, 2020 at 19:10 UTC 22dc2e42bdc00d87fc19c5e75fc7c0b3fdcdc572
33 files changed +1050 -19
packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js
+50
@@ -39,6 +39,56 @@ describe('ReactDOMServerIntegration', () => {
39 resetModules();
40 });
41
42 + // Test pragmas don't support itRenders abstraction
43 + if (
44 + __EXPERIMENTAL__ &&
45 + require('shared/ReactFeatureFlags').enableDebugTracing
46 + ) {
47 + describe('React.unstable_DebugTracingMode', () => {
48 + beforeEach(() => {
49 + spyOnDevAndProd(console, 'log');
50 + });
51 +
52 + itRenders('with one child', async render => {
53 + const e = await render(
54 + <React.unstable_DebugTracingMode>
55 + <div>text1</div>
56 + </React.unstable_DebugTracingMode>,
57 + );
58 + const parent = e.parentNode;
59 + expect(parent.childNodes[0].tagName).toBe('DIV');
60 + });
61 +
62 + itRenders('mode with several children', async render => {
63 + const Header = props => {
64 + return <p>header</p>;
65 + };
66 + const Footer = props => {
67 + return (
68 + <React.unstable_DebugTracingMode>
69 + <h2>footer</h2>
70 + <h3>about</h3>
71 + </React.unstable_DebugTracingMode>
72 + );
73 + };
74 + const e = await render(
75 + <React.unstable_DebugTracingMode>
76 + <div>text1</div>
77 + <span>text2</span>
78 + <Header />
79 + <Footer />
80 + </React.unstable_DebugTracingMode>,
81 + );
82 + const parent = e.parentNode;
83 + expect(parent.childNodes[0].tagName).toBe('DIV');
84 + expect(parent.childNodes[1].tagName).toBe('SPAN');
85 + expect(parent.childNodes[2].tagName).toBe('P');
86 + expect(parent.childNodes[3].tagName).toBe('H2');
87 + expect(parent.childNodes[4].tagName).toBe('H3');
88 + });
89 + });
90 + }
91 +
92 describe('React.StrictMode', () => {
93 itRenders('a strict mode with one child', async render => {
94 const e = await render(
packages/react-dom/src/server/ReactPartialRenderer.js
+2
@@ -28,6 +28,7 @@ import {
28 } from 'shared/ReactFeatureFlags';
29
30 import {
31 + REACT_DEBUG_TRACING_MODE_TYPE,
32 REACT_FORWARD_REF_TYPE,
33 REACT_FRAGMENT_TYPE,
34 REACT_STRICT_MODE_TYPE,
@@ -1002,6 +1003,7 @@ class ReactDOMServerRenderer {
1003 }
1004
1005 switch (elementType) {
1006 + case REACT_DEBUG_TRACING_MODE_TYPE:
1007 case REACT_STRICT_MODE_TYPE:
1008 case REACT_PROFILER_TYPE:
1009 case REACT_SUSPENSE_LIST_TYPE:
packages/react-reconciler/src/DebugTracing.js new
+226
@@ -0,0 +1,226 @@
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 {Wakeable} from 'shared/ReactTypes';
11 +
12 +import {enableDebugTracing} from 'shared/ReactFeatureFlags';
13 +
14 +const nativeConsole: Object = console;
15 +let nativeConsoleLog: null | Function = null;
16 +
17 +const pendingGroupArgs: Array<any> = [];
18 +let printedGroupIndex: number = -1;
19 +
20 +function group(...groupArgs): void {
21 + pendingGroupArgs.push(groupArgs);
22 +
23 + if (nativeConsoleLog === null) {
24 + nativeConsoleLog = nativeConsole.log;
25 + nativeConsole.log = log;
26 + }
27 +}
28 +
29 +function groupEnd(): void {
30 + pendingGroupArgs.pop();
31 + while (printedGroupIndex >= pendingGroupArgs.length) {
32 + nativeConsole.groupEnd();
33 + printedGroupIndex--;
34 + }
35 +
36 + if (pendingGroupArgs.length === 0) {
37 + nativeConsole.log = nativeConsoleLog;
38 + nativeConsoleLog = null;
39 + }
40 +}
41 +
42 +function log(...logArgs): void {
43 + if (printedGroupIndex < pendingGroupArgs.length - 1) {
44 + for (let i = printedGroupIndex + 1; i < pendingGroupArgs.length; i++) {
45 + const groupArgs = pendingGroupArgs[i];
46 + nativeConsole.group(...groupArgs);
47 + }
48 + printedGroupIndex = pendingGroupArgs.length - 1;
49 + }
50 + if (typeof nativeConsoleLog === 'function') {
51 + nativeConsoleLog(...logArgs);
52 + } else {
53 + nativeConsole.log(...logArgs);
54 + }
55 +}
56 +
57 +const REACT_LOGO_STYLE =
58 + 'background-color: #20232a; color: #61dafb; padding: 0 2px;';
59 +
60 +export function logCommitStarted(priorityLabel: string): void {
61 + if (__DEV__) {
62 + if (enableDebugTracing) {
63 + group(
64 + `%c⚛️%c commit%c (priority: ${priorityLabel})`,
65 + REACT_LOGO_STYLE,
66 + '',
67 + 'font-weight: normal;',
68 + );
69 + }
70 + }
71 +}
72 +
73 +export function logCommitStopped(): void {
74 + if (__DEV__) {
75 + if (enableDebugTracing) {
76 + groupEnd();
77 + }
78 + }
79 +}
80 +
81 +const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
82 +// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
83 +const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
84 +let wakeableID: number = 0;
85 +function getWakeableID(wakeable: Wakeable): number {
86 + if (!wakeableIDs.has(wakeable)) {
87 + wakeableIDs.set(wakeable, wakeableID++);
88 + }
89 + return ((wakeableIDs.get(wakeable): any): number);
90 +}
91 +
92 +export function logComponentSuspended(
93 + componentName: string,
94 + wakeable: Wakeable,
95 +): void {
96 + if (__DEV__) {
97 + if (enableDebugTracing) {
98 + const id = getWakeableID(wakeable);
99 + const display = (wakeable: any).displayName || wakeable;
100 + log(
101 + `%c⚛️%c ${componentName} suspended`,
102 + REACT_LOGO_STYLE,
103 + 'color: #80366d; font-weight: bold;',
104 + id,
105 + display,
106 + );
107 + wakeable.then(
108 + () => {
109 + log(
110 + `%c⚛️%c ${componentName} resolved`,
111 + REACT_LOGO_STYLE,
112 + 'color: #80366d; font-weight: bold;',
113 + id,
114 + display,
115 + );
116 + },
117 + () => {
118 + log(
119 + `%c⚛️%c ${componentName} rejected`,
120 + REACT_LOGO_STYLE,
121 + 'color: #80366d; font-weight: bold;',
122 + id,
123 + display,
124 + );
125 + },
126 + );
127 + }
128 + }
129 +}
130 +
131 +export function logLayoutEffectsStarted(priorityLabel: string): void {
132 + if (__DEV__) {
133 + if (enableDebugTracing) {
134 + group(
135 + `%c⚛️%c layout effects%c (priority: ${priorityLabel})`,
136 + REACT_LOGO_STYLE,
137 + '',
138 + 'font-weight: normal;',
139 + );
140 + }
141 + }
142 +}
143 +
144 +export function logLayoutEffectsStopped(): void {
145 + if (__DEV__) {
146 + if (enableDebugTracing) {
147 + groupEnd();
148 + }
149 + }
150 +}
151 +
152 +export function logPassiveEffectsStarted(priorityLabel: string): void {
153 + if (__DEV__) {
154 + if (enableDebugTracing) {
155 + group(
156 + `%c⚛️%c passive effects%c (priority: ${priorityLabel})`,
157 + REACT_LOGO_STYLE,
158 + '',
159 + 'font-weight: normal;',
160 + );
161 + }
162 + }
163 +}
164 +
165 +export function logPassiveEffectsStopped(): void {
166 + if (__DEV__) {
167 + if (enableDebugTracing) {
168 + groupEnd();
169 + }
170 + }
171 +}
172 +
173 +export function logRenderStarted(priorityLabel: string): void {
174 + if (__DEV__) {
175 + if (enableDebugTracing) {
176 + group(
177 + `%c⚛️%c render%c (priority: ${priorityLabel})`,
178 + REACT_LOGO_STYLE,
179 + '',
180 + 'font-weight: normal;',
181 + );
182 + }
183 + }
184 +}
185 +
186 +export function logRenderStopped(): void {
187 + if (__DEV__) {
188 + if (enableDebugTracing) {
189 + groupEnd();
190 + }
191 + }
192 +}
193 +
194 +export function logForceUpdateScheduled(
195 + componentName: string,
196 + priorityLabel: string,
197 +): void {
198 + if (__DEV__) {
199 + if (enableDebugTracing) {
200 + log(
201 + `%c⚛️%c ${componentName} forced update %c(priority: ${priorityLabel})`,
202 + REACT_LOGO_STYLE,
203 + 'color: #db2e1f; font-weight: bold;',
204 + '',
205 + );
206 + }
207 + }
208 +}
209 +
210 +export function logStateUpdateScheduled(
211 + componentName: string,
212 + priorityLabel: string,
213 + payloadOrAction: any,
214 +): void {
215 + if (__DEV__) {
216 + if (enableDebugTracing) {
217 + log(
218 + `%c⚛️%c ${componentName} updated state %c(priority: ${priorityLabel})`,
219 + REACT_LOGO_STYLE,
220 + 'color: #01a252; font-weight: bold;',
221 + '',
222 + payloadOrAction,
223 + );
224 + }
225 + }
226 +}
packages/react-reconciler/src/ReactFiber.new.js
+6
@@ -67,6 +67,7 @@ import {NoWork} from './ReactFiberExpirationTime.new';
67 import {
68 NoMode,
69 ConcurrentMode,
70 + DebugTracingMode,
71 ProfileMode,
72 StrictMode,
73 BlockingMode,
@@ -74,6 +75,7 @@ import {
75 import {
76 REACT_FORWARD_REF_TYPE,
77 REACT_FRAGMENT_TYPE,
78 + REACT_DEBUG_TRACING_MODE_TYPE,
79 REACT_STRICT_MODE_TYPE,
80 REACT_PROFILER_TYPE,
81 REACT_PROVIDER_TYPE,
@@ -488,6 +490,10 @@ export function createFiberFromTypeAndProps(
490 expirationTime,
491 key,
492 );
493 + case REACT_DEBUG_TRACING_MODE_TYPE:
494 + fiberTag = Mode;
495 + mode |= DebugTracingMode;
496 + break;
497 case REACT_STRICT_MODE_TYPE:
498 fiberTag = Mode;
499 mode |= StrictMode;
packages/react-reconciler/src/ReactFiber.old.js
+6
@@ -67,6 +67,7 @@ import {NoWork} from './ReactFiberExpirationTime.old';
67 import {
68 NoMode,
69 ConcurrentMode,
70 + DebugTracingMode,
71 ProfileMode,
72 StrictMode,
73 BlockingMode,
@@ -74,6 +75,7 @@ import {
75 import {
76 REACT_FORWARD_REF_TYPE,
77 REACT_FRAGMENT_TYPE,
78 + REACT_DEBUG_TRACING_MODE_TYPE,
79 REACT_STRICT_MODE_TYPE,
80 REACT_PROFILER_TYPE,
81 REACT_PROVIDER_TYPE,
@@ -488,6 +490,10 @@ export function createFiberFromTypeAndProps(
490 expirationTime,
491 key,
492 );
493 + case REACT_DEBUG_TRACING_MODE_TYPE:
494 + fiberTag = Mode;
495 + mode |= DebugTracingMode;
496 + break;
497 case REACT_STRICT_MODE_TYPE:
498 fiberTag = Mode;
499 mode |= StrictMode;
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+41 -1
@@ -10,12 +10,14 @@
10 import type {Fiber} from './ReactInternalTypes';
11 import type {ExpirationTime} from './ReactFiberExpirationTime.new';
12 import type {UpdateQueue} from './ReactUpdateQueue.new';
13 +import type {ReactPriorityLevel} from './ReactInternalTypes';
14
15 import * as React from 'react';
16 import {Update, Snapshot} from './ReactSideEffectTags';
17 import {
18 debugRenderPhaseSideEffectsForStrictMode,
19 disableLegacyContext,
20 + enableDebugTracing,
21 warnAboutDeprecatedLifecycles,
22 } from 'shared/ReactFeatureFlags';
23 import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
@@ -27,7 +29,7 @@ import invariant from 'shared/invariant';
29 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
30
31 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
30 -import {StrictMode} from './ReactTypeOfMode';
32 +import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33
34 import {
35 enqueueUpdate,
@@ -53,8 +55,10 @@ import {
55 requestCurrentTimeForUpdate,
56 computeExpirationForFiber,
57 scheduleUpdateOnFiber,
58 + priorityLevelToLabel,
59 } from './ReactFiberWorkLoop.new';
60 import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
61 +import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
62
63 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
64
@@ -207,6 +211,18 @@ const classComponentUpdater = {
211
212 enqueueUpdate(fiber, update);
213 scheduleUpdateOnFiber(fiber, expirationTime);
214 +
215 + if (__DEV__) {
216 + if (enableDebugTracing) {
217 + if (fiber.mode & DebugTracingMode) {
218 + const label = priorityLevelToLabel(
219 + ((update.priority: any): ReactPriorityLevel),
220 + );
221 + const name = getComponentName(fiber.type) || 'Unknown';
222 + logStateUpdateScheduled(name, label, payload);
223 + }
224 + }
225 + }
226 },
227 enqueueReplaceState(inst, payload, callback) {
228 const fiber = getInstance(inst);
@@ -231,6 +247,18 @@ const classComponentUpdater = {
247
248 enqueueUpdate(fiber, update);
249 scheduleUpdateOnFiber(fiber, expirationTime);
250 +
251 + if (__DEV__) {
252 + if (enableDebugTracing) {
253 + if (fiber.mode & DebugTracingMode) {
254 + const label = priorityLevelToLabel(
255 + ((update.priority: any): ReactPriorityLevel),
256 + );
257 + const name = getComponentName(fiber.type) || 'Unknown';
258 + logStateUpdateScheduled(name, label, payload);
259 + }
260 + }
261 + }
262 },
263 enqueueForceUpdate(inst, callback) {
264 const fiber = getInstance(inst);
@@ -254,6 +282,18 @@ const classComponentUpdater = {
282
283 enqueueUpdate(fiber, update);
284 scheduleUpdateOnFiber(fiber, expirationTime);
285 +
286 + if (__DEV__) {
287 + if (enableDebugTracing) {
288 + if (fiber.mode & DebugTracingMode) {
289 + const label = priorityLevelToLabel(
290 + ((update.priority: any): ReactPriorityLevel),
291 + );
292 + const name = getComponentName(fiber.type) || 'Unknown';
293 + logForceUpdateScheduled(name, label);
294 + }
295 + }
296 + }
297 },
298 };
299
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+41 -1
@@ -10,12 +10,14 @@
10 import type {Fiber} from './ReactInternalTypes';
11 import type {ExpirationTime} from './ReactFiberExpirationTime.old';
12 import type {UpdateQueue} from './ReactUpdateQueue.old';
13 +import type {ReactPriorityLevel} from './ReactInternalTypes';
14
15 import * as React from 'react';
16 import {Update, Snapshot} from './ReactSideEffectTags';
17 import {
18 debugRenderPhaseSideEffectsForStrictMode,
19 disableLegacyContext,
20 + enableDebugTracing,
21 warnAboutDeprecatedLifecycles,
22 } from 'shared/ReactFeatureFlags';
23 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
@@ -27,7 +29,7 @@ import invariant from 'shared/invariant';
29 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
30
31 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
30 -import {StrictMode} from './ReactTypeOfMode';
32 +import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33
34 import {
35 enqueueUpdate,
@@ -53,8 +55,10 @@ import {
55 requestCurrentTimeForUpdate,
56 computeExpirationForFiber,
57 scheduleUpdateOnFiber,
58 + priorityLevelToLabel,
59 } from './ReactFiberWorkLoop.old';
60 import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
61 +import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
62
63 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
64
@@ -207,6 +211,18 @@ const classComponentUpdater = {
211
212 enqueueUpdate(fiber, update);
213 scheduleUpdateOnFiber(fiber, expirationTime);
214 +
215 + if (__DEV__) {
216 + if (enableDebugTracing) {
217 + if (fiber.mode & DebugTracingMode) {
218 + const label = priorityLevelToLabel(
219 + ((update.priority: any): ReactPriorityLevel),
220 + );
221 + const name = getComponentName(fiber.type) || 'Unknown';
222 + logStateUpdateScheduled(name, label, payload);
223 + }
224 + }
225 + }
226 },
227 enqueueReplaceState(inst, payload, callback) {
228 const fiber = getInstance(inst);
@@ -231,6 +247,18 @@ const classComponentUpdater = {
247
248 enqueueUpdate(fiber, update);
249 scheduleUpdateOnFiber(fiber, expirationTime);
250 +
251 + if (__DEV__) {
252 + if (enableDebugTracing) {
253 + if (fiber.mode & DebugTracingMode) {
254 + const label = priorityLevelToLabel(
255 + ((update.priority: any): ReactPriorityLevel),
256 + );
257 + const name = getComponentName(fiber.type) || 'Unknown';
258 + logStateUpdateScheduled(name, label, payload);
259 + }
260 + }
261 + }
262 },
263 enqueueForceUpdate(inst, callback) {
264 const fiber = getInstance(inst);
@@ -254,6 +282,18 @@ const classComponentUpdater = {
282
283 enqueueUpdate(fiber, update);
284 scheduleUpdateOnFiber(fiber, expirationTime);
285 +
286 + if (__DEV__) {
287 + if (enableDebugTracing) {
288 + if (fiber.mode & DebugTracingMode) {
289 + const label = priorityLevelToLabel(
290 + ((update.priority: any): ReactPriorityLevel),
291 + );
292 + const name = getComponentName(fiber.type) || 'Unknown';
293 + logForceUpdateScheduled(name, label);
294 + }
295 + }
296 + }
297 },
298 };
299
packages/react-reconciler/src/ReactFiberHooks.new.js
+23 -3
@@ -30,11 +30,15 @@ import type {
30 } from './ReactFiberHostConfig';
31
32 import ReactSharedInternals from 'shared/ReactSharedInternals';
33 -import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
33 +import {enableDebugTracing, enableUseEventAPI} from 'shared/ReactFeatureFlags';
34
35 import {markRootExpiredAtTime} from './ReactFiberRoot.new';
36 -import {NoWork, Sync} from './ReactFiberExpirationTime.new';
37 -import {NoMode, BlockingMode} from './ReactTypeOfMode';
36 +import {
37 + inferPriorityFromExpirationTime,
38 + NoWork,
39 + Sync,
40 +} from './ReactFiberExpirationTime.new';
41 +import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
42 import {readContext} from './ReactFiberNewContext.new';
43 import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
44 import {
@@ -57,6 +61,7 @@ import {
61 warnIfNotScopedWithMatchingAct,
62 markRenderEventTimeAndConfig,
63 markUnprocessedUpdateTime,
64 + priorityLevelToLabel,
65 } from './ReactFiberWorkLoop.new';
66 import {
67 registerEvent,
@@ -92,6 +97,7 @@ import {
97 } from './ReactMutableSource.new';
98 import {getRootHostContainer} from './ReactFiberHostContext.new';
99 import {getIsRendering} from './ReactCurrentFiber';
100 +import {logStateUpdateScheduled} from './DebugTracing';
101
102 const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
103
@@ -1738,6 +1744,20 @@ function dispatchAction<S, A>(
1744 }
1745 scheduleUpdateOnFiber(fiber, expirationTime);
1746 }
1747 +
1748 + if (__DEV__) {
1749 + if (enableDebugTracing) {
1750 + if (fiber.mode & DebugTracingMode) {
1751 + const priorityLevel = inferPriorityFromExpirationTime(
1752 + currentTime,
1753 + expirationTime,
1754 + );
1755 + const label = priorityLevelToLabel(priorityLevel);
1756 + const name = getComponentName(fiber.type) || 'Unknown';
1757 + logStateUpdateScheduled(name, label, action);
1758 + }
1759 + }
1760 + }
1761 }
1762
1763 const noOpMount = () => {};
packages/react-reconciler/src/ReactFiberHooks.old.js
+23 -3
@@ -30,11 +30,15 @@ import type {
30 } from './ReactFiberHostConfig';
31
32 import ReactSharedInternals from 'shared/ReactSharedInternals';
33 -import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
33 +import {enableDebugTracing, enableUseEventAPI} from 'shared/ReactFeatureFlags';
34
35 import {markRootExpiredAtTime} from './ReactFiberRoot.old';
36 -import {NoWork, Sync} from './ReactFiberExpirationTime.old';
37 -import {NoMode, BlockingMode} from './ReactTypeOfMode';
36 +import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
37 +import {
38 + inferPriorityFromExpirationTime,
39 + NoWork,
40 + Sync,
41 +} from './ReactFiberExpirationTime.old';
42 import {readContext} from './ReactFiberNewContext.old';
43 import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.old';
44 import {
@@ -57,6 +61,7 @@ import {
61 warnIfNotScopedWithMatchingAct,
62 markRenderEventTimeAndConfig,
63 markUnprocessedUpdateTime,
64 + priorityLevelToLabel,
65 } from './ReactFiberWorkLoop.old';
66 import {
67 registerEvent,
@@ -92,6 +97,7 @@ import {
97 } from './ReactMutableSource.old';
98 import {getRootHostContainer} from './ReactFiberHostContext.old';
99 import {getIsRendering} from './ReactCurrentFiber';
100 +import {logStateUpdateScheduled} from './DebugTracing';
101
102 const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
103
@@ -1733,6 +1739,20 @@ function dispatchAction<S, A>(
1739 }
1740 scheduleUpdateOnFiber(fiber, expirationTime);
1741 }
1742 +
1743 + if (__DEV__) {
1744 + if (enableDebugTracing) {
1745 + if (fiber.mode & DebugTracingMode) {
1746 + const priorityLevel = inferPriorityFromExpirationTime(
1747 + currentTime,
1748 + expirationTime,
1749 + );
1750 + const label = priorityLevelToLabel(priorityLevel);
1751 + const name = getComponentName(fiber.type) || 'Unknown';
1752 + logStateUpdateScheduled(name, label, action);
1753 + }
1754 + }
1755 + }
1756 }
1757
1758 const noOpMount = () => {};
packages/react-reconciler/src/ReactFiberReconciler.new.js
-1
@@ -36,7 +36,6 @@ import {
36 import getComponentName from 'shared/getComponentName';
37 import invariant from 'shared/invariant';
38 import ReactSharedInternals from 'shared/ReactSharedInternals';
39 -
39 import {getPublicInstance} from './ReactFiberHostConfig';
40 import {
41 findCurrentUnmaskedContext,
packages/react-reconciler/src/ReactFiberReconciler.old.js
-1
@@ -36,7 +36,6 @@ import {
36 import getComponentName from 'shared/getComponentName';
37 import invariant from 'shared/invariant';
38 import ReactSharedInternals from 'shared/ReactSharedInternals';
39 -
39 import {getPublicInstance} from './ReactFiberHostConfig';
40 import {
41 findCurrentUnmaskedContext,
packages/react-reconciler/src/ReactFiberThrow.new.js
+12 -2
@@ -29,9 +29,9 @@ import {
29 ShouldCapture,
30 LifecycleEffectMask,
31 } from './ReactSideEffectTags';
32 -import {NoMode, BlockingMode} from './ReactTypeOfMode';
32 import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
34 -
33 +import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
34 +import {enableDebugTracing} from 'shared/ReactFeatureFlags';
35 import {createCapturedValue} from './ReactCapturedValue';
36 import {
37 enqueueCapturedUpdate,
@@ -55,6 +55,7 @@ import {
55 pingSuspendedRoot,
56 } from './ReactFiberWorkLoop.new';
57 import {logCapturedError} from './ReactFiberErrorLogger';
58 +import {logComponentSuspended} from './DebugTracing';
59
60 import {Sync, NoWork} from './ReactFiberExpirationTime.new';
61
@@ -194,6 +195,15 @@ function throwException(
195 // This is a wakeable.
196 const wakeable: Wakeable = (value: any);
197
198 + if (__DEV__) {
199 + if (enableDebugTracing) {
200 + if (sourceFiber.mode & DebugTracingMode) {
201 + const name = getComponentName(sourceFiber.type) || 'Unknown';
202 + logComponentSuspended(name, wakeable);
203 + }
204 + }
205 + }
206 +
207 if ((sourceFiber.mode & BlockingMode) === NoMode) {
208 // Reset the memoizedState to what it was before we attempted
209 // to render it.
packages/react-reconciler/src/ReactFiberThrow.old.js
+12 -2
@@ -29,9 +29,9 @@ import {
29 ShouldCapture,
30 LifecycleEffectMask,
31 } from './ReactSideEffectTags';
32 -import {NoMode, BlockingMode} from './ReactTypeOfMode';
32 import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.old';
34 -
33 +import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
34 +import {enableDebugTracing} from 'shared/ReactFeatureFlags';
35 import {createCapturedValue} from './ReactCapturedValue';
36 import {
37 enqueueCapturedUpdate,
@@ -55,6 +55,7 @@ import {
55 pingSuspendedRoot,
56 } from './ReactFiberWorkLoop.old';
57 import {logCapturedError} from './ReactFiberErrorLogger';
58 +import {logComponentSuspended} from './DebugTracing';
59
60 import {Sync} from './ReactFiberExpirationTime.old';
61
@@ -194,6 +195,15 @@ function throwException(
195 // This is a wakeable.
196 const wakeable: Wakeable = (value: any);
197
198 + if (__DEV__) {
199 + if (enableDebugTracing) {
200 + if (sourceFiber.mode & DebugTracingMode) {
201 + const name = getComponentName(sourceFiber.type) || 'Unknown';
202 + logComponentSuspended(name, wakeable);
203 + }
204 + }
205 + }
206 +
207 if ((sourceFiber.mode & BlockingMode) === NoMode) {
208 // Reset the memoizedState to what it was before we attempted
209 // to render it.
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+116
@@ -27,6 +27,7 @@ import {
27 enableSchedulerTracing,
28 warnAboutUnmockedScheduler,
29 disableSchedulerTimeoutBasedOnReactExpirationTime,
30 + enableDebugTracing,
31 } from 'shared/ReactFeatureFlags';
32 import ReactSharedInternals from 'shared/ReactSharedInternals';
33 import invariant from 'shared/invariant';
@@ -48,6 +49,16 @@ import {
49 flushSyncCallbackQueue,
50 scheduleSyncCallback,
51 } from './SchedulerWithReactIntegration.new';
52 +import {
53 + logCommitStarted,
54 + logCommitStopped,
55 + logLayoutEffectsStarted,
56 + logLayoutEffectsStopped,
57 + logPassiveEffectsStarted,
58 + logPassiveEffectsStopped,
59 + logRenderStarted,
60 + logRenderStopped,
61 +} from './DebugTracing';
62
63 // The scheduler is imported here *only* to detect whether it's been mocked
64 import * as Scheduler from 'scheduler';
@@ -378,6 +389,29 @@ export function computeExpirationForFiber(
389 return expirationTime;
390 }
391
392 +export function priorityLevelToLabel(
393 + priorityLevel: ReactPriorityLevel,
394 +): string {
395 + if (__DEV__ && enableDebugTracing) {
396 + switch (priorityLevel) {
397 + case ImmediatePriority:
398 + return 'immediate';
399 + case UserBlockingPriority:
400 + return 'user-blocking';
401 + case NormalPriority:
402 + return 'normal';
403 + case LowPriority:
404 + return 'low';
405 + case IdlePriority:
406 + return 'idle';
407 + default:
408 + return 'other';
409 + }
410 + } else {
411 + return '';
412 + }
413 +}
414 +
415 export function scheduleUpdateOnFiber(
416 fiber: Fiber,
417 expirationTime: ExpirationTime,
@@ -1386,6 +1420,15 @@ function renderRootSync(root, expirationTime) {
1420 }
1421
1422 const prevInteractions = pushInteractions(root);
1423 +
1424 + if (__DEV__) {
1425 + if (enableDebugTracing) {
1426 + const priorityLevel = getCurrentPriorityLevel();
1427 + const label = priorityLevelToLabel(priorityLevel);
1428 + logRenderStarted(label);
1429 + }
1430 + }
1431 +
1432 do {
1433 try {
1434 workLoopSync();
@@ -1411,6 +1454,12 @@ function renderRootSync(root, expirationTime) {
1454 );
1455 }
1456
1457 + if (__DEV__) {
1458 + if (enableDebugTracing) {
1459 + logRenderStopped();
1460 + }
1461 + }
1462 +
1463 // Set this to null to indicate there's no in-progress render.
1464 workInProgressRoot = null;
1465
@@ -1439,6 +1488,15 @@ function renderRootConcurrent(root, expirationTime) {
1488 }
1489
1490 const prevInteractions = pushInteractions(root);
1491 +
1492 + if (__DEV__) {
1493 + if (enableDebugTracing) {
1494 + const priorityLevel = getCurrentPriorityLevel();
1495 + const label = priorityLevelToLabel(priorityLevel);
1496 + logRenderStarted(label);
1497 + }
1498 + }
1499 +
1500 do {
1501 try {
1502 workLoopConcurrent();
@@ -1455,6 +1513,12 @@ function renderRootConcurrent(root, expirationTime) {
1513 popDispatcher(prevDispatcher);
1514 executionContext = prevExecutionContext;
1515
1516 + if (__DEV__) {
1517 + if (enableDebugTracing) {
1518 + logRenderStopped();
1519 + }
1520 + }
1521 +
1522 // Check if the tree has completed.
1523 if (workInProgress !== null) {
1524 // Still work remaining.
@@ -1722,6 +1786,12 @@ function commitRoot(root) {
1786 }
1787
1788 function commitRootImpl(root, renderPriorityLevel) {
1789 + if (__DEV__) {
1790 + if (enableDebugTracing) {
1791 + const label = priorityLevelToLabel(renderPriorityLevel);
1792 + logCommitStarted(label);
1793 + }
1794 + }
1795 do {
1796 // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
1797 // means `flushPassiveEffects` will sometimes result in additional
@@ -1741,6 +1811,11 @@ function commitRootImpl(root, renderPriorityLevel) {
1811 const finishedWork = root.finishedWork;
1812 const expirationTime = root.finishedExpirationTime;
1813 if (finishedWork === null) {
1814 + if (__DEV__) {
1815 + if (enableDebugTracing) {
1816 + logCommitStopped();
1817 + }
1818 + }
1819 return null;
1820 }
1821 root.finishedWork = null;
@@ -2021,6 +2096,12 @@ function commitRootImpl(root, renderPriorityLevel) {
2096 }
2097
2098 if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
2099 + if (__DEV__) {
2100 + if (enableDebugTracing) {
2101 + logCommitStopped();
2102 + }
2103 + }
2104 +
2105 // This is a legacy edge case. We just committed the initial mount of
2106 // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
2107 // synchronously, but layout updates should be deferred until the end
@@ -2030,6 +2111,13 @@ function commitRootImpl(root, renderPriorityLevel) {
2111
2112 // If layout work was scheduled, flush it now.
2113 flushSyncCallbackQueue();
2114 +
2115 + if (__DEV__) {
2116 + if (enableDebugTracing) {
2117 + logCommitStopped();
2118 + }
2119 + }
2120 +
2121 return null;
2122 }
2123
@@ -2137,6 +2225,14 @@ function commitLayoutEffects(
2225 root: FiberRoot,
2226 committedExpirationTime: ExpirationTime,
2227 ) {
2228 + if (__DEV__) {
2229 + if (enableDebugTracing) {
2230 + const priorityLevel = getCurrentPriorityLevel();
2231 + const label = priorityLevelToLabel(priorityLevel);
2232 + logLayoutEffectsStarted(label);
2233 + }
2234 + }
2235 +
2236 // TODO: Should probably move the bulk of this function to commitWork.
2237 while (nextEffect !== null) {
2238 setCurrentDebugFiberInDEV(nextEffect);
@@ -2160,6 +2256,12 @@ function commitLayoutEffects(
2256 resetCurrentDebugFiberInDEV();
2257 nextEffect = nextEffect.nextEffect;
2258 }
2259 +
2260 + if (__DEV__) {
2261 + if (enableDebugTracing) {
2262 + logLayoutEffectsStopped();
2263 + }
2264 + }
2265 }
2266
2267 export function flushPassiveEffects() {
@@ -2238,6 +2340,14 @@ function flushPassiveEffectsImpl() {
2340 'Cannot flush passive effects while already rendering.',
2341 );
2342
2343 + if (__DEV__) {
2344 + if (enableDebugTracing) {
2345 + const priorityLevel = getCurrentPriorityLevel();
2346 + const label = priorityLevelToLabel(priorityLevel);
2347 + logPassiveEffectsStarted(label);
2348 + }
2349 + }
2350 +
2351 if (__DEV__) {
2352 isFlushingPassiveEffects = true;
2353 }
@@ -2405,6 +2515,12 @@ function flushPassiveEffectsImpl() {
2515 isFlushingPassiveEffects = false;
2516 }
2517
2518 + if (__DEV__) {
2519 + if (enableDebugTracing) {
2520 + logPassiveEffectsStopped();
2521 + }
2522 + }
2523 +
2524 executionContext = prevExecutionContext;
2525
2526 flushSyncCallbackQueue();
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+116
@@ -27,6 +27,7 @@ import {
27 enableSchedulerTracing,
28 warnAboutUnmockedScheduler,
29 disableSchedulerTimeoutBasedOnReactExpirationTime,
30 + enableDebugTracing,
31 } from 'shared/ReactFeatureFlags';
32 import ReactSharedInternals from 'shared/ReactSharedInternals';
33 import invariant from 'shared/invariant';
@@ -48,6 +49,16 @@ import {
49 flushSyncCallbackQueue,
50 scheduleSyncCallback,
51 } from './SchedulerWithReactIntegration.old';
52 +import {
53 + logCommitStarted,
54 + logCommitStopped,
55 + logLayoutEffectsStarted,
56 + logLayoutEffectsStopped,
57 + logPassiveEffectsStarted,
58 + logPassiveEffectsStopped,
59 + logRenderStarted,
60 + logRenderStopped,
61 +} from './DebugTracing';
62
63 // The scheduler is imported here *only* to detect whether it's been mocked
64 import * as Scheduler from 'scheduler';
@@ -371,6 +382,29 @@ export function computeExpirationForFiber(
382 return expirationTime;
383 }
384
385 +export function priorityLevelToLabel(
386 + priorityLevel: ReactPriorityLevel,
387 +): string {
388 + if (__DEV__ && enableDebugTracing) {
389 + switch (priorityLevel) {
390 + case ImmediatePriority:
391 + return 'immediate';
392 + case UserBlockingPriority:
393 + return 'user-blocking';
394 + case NormalPriority:
395 + return 'normal';
396 + case LowPriority:
397 + return 'low';
398 + case IdlePriority:
399 + return 'idle';
400 + default:
401 + return 'other';
402 + }
403 + } else {
404 + return '';
405 + }
406 +}
407 +
408 export function scheduleUpdateOnFiber(
409 fiber: Fiber,
410 expirationTime: ExpirationTime,
@@ -1405,6 +1439,15 @@ function renderRootSync(root, expirationTime) {
1439 }
1440
1441 const prevInteractions = pushInteractions(root);
1442 +
1443 + if (__DEV__) {
1444 + if (enableDebugTracing) {
1445 + const priorityLevel = getCurrentPriorityLevel();
1446 + const label = priorityLevelToLabel(priorityLevel);
1447 + logRenderStarted(label);
1448 + }
1449 + }
1450 +
1451 do {
1452 try {
1453 workLoopSync();
@@ -1430,6 +1473,12 @@ function renderRootSync(root, expirationTime) {
1473 );
1474 }
1475
1476 + if (__DEV__) {
1477 + if (enableDebugTracing) {
1478 + logRenderStopped();
1479 + }
1480 + }
1481 +
1482 // Set this to null to indicate there's no in-progress render.
1483 workInProgressRoot = null;
1484
@@ -1458,6 +1507,15 @@ function renderRootConcurrent(root, expirationTime) {
1507 }
1508
1509 const prevInteractions = pushInteractions(root);
1510 +
1511 + if (__DEV__) {
1512 + if (enableDebugTracing) {
1513 + const priorityLevel = getCurrentPriorityLevel();
1514 + const label = priorityLevelToLabel(priorityLevel);
1515 + logRenderStarted(label);
1516 + }
1517 + }
1518 +
1519 do {
1520 try {
1521 workLoopConcurrent();
@@ -1474,6 +1532,12 @@ function renderRootConcurrent(root, expirationTime) {
1532 popDispatcher(prevDispatcher);
1533 executionContext = prevExecutionContext;
1534
1535 + if (__DEV__) {
1536 + if (enableDebugTracing) {
1537 + logRenderStopped();
1538 + }
1539 + }
1540 +
1541 // Check if the tree has completed.
1542 if (workInProgress !== null) {
1543 // Still work remaining.
@@ -1741,6 +1805,12 @@ function commitRoot(root) {
1805 }
1806
1807 function commitRootImpl(root, renderPriorityLevel) {
1808 + if (__DEV__) {
1809 + if (enableDebugTracing) {
1810 + const label = priorityLevelToLabel(renderPriorityLevel);
1811 + logCommitStarted(label);
1812 + }
1813 + }
1814 do {
1815 // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
1816 // means `flushPassiveEffects` will sometimes result in additional
@@ -1760,6 +1830,11 @@ function commitRootImpl(root, renderPriorityLevel) {
1830 const finishedWork = root.finishedWork;
1831 const expirationTime = root.finishedExpirationTime;
1832 if (finishedWork === null) {
1833 + if (__DEV__) {
1834 + if (enableDebugTracing) {
1835 + logCommitStopped();
1836 + }
1837 + }
1838 return null;
1839 }
1840 root.finishedWork = null;
@@ -2040,6 +2115,12 @@ function commitRootImpl(root, renderPriorityLevel) {
2115 }
2116
2117 if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
2118 + if (__DEV__) {
2119 + if (enableDebugTracing) {
2120 + logCommitStopped();
2121 + }
2122 + }
2123 +
2124 // This is a legacy edge case. We just committed the initial mount of
2125 // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
2126 // synchronously, but layout updates should be deferred until the end
@@ -2049,6 +2130,13 @@ function commitRootImpl(root, renderPriorityLevel) {
2130
2131 // If layout work was scheduled, flush it now.
2132 flushSyncCallbackQueue();
2133 +
2134 + if (__DEV__) {
2135 + if (enableDebugTracing) {
2136 + logCommitStopped();
2137 + }
2138 + }
2139 +
2140 return null;
2141 }
2142
@@ -2156,6 +2244,14 @@ function commitLayoutEffects(
2244 root: FiberRoot,
2245 committedExpirationTime: ExpirationTime,
2246 ) {
2247 + if (__DEV__) {
2248 + if (enableDebugTracing) {
2249 + const priorityLevel = getCurrentPriorityLevel();
2250 + const label = priorityLevelToLabel(priorityLevel);
2251 + logLayoutEffectsStarted(label);
2252 + }
2253 + }
2254 +
2255 // TODO: Should probably move the bulk of this function to commitWork.
2256 while (nextEffect !== null) {
2257 setCurrentDebugFiberInDEV(nextEffect);
@@ -2179,6 +2275,12 @@ function commitLayoutEffects(
2275 resetCurrentDebugFiberInDEV();
2276 nextEffect = nextEffect.nextEffect;
2277 }
2278 +
2279 + if (__DEV__) {
2280 + if (enableDebugTracing) {
2281 + logLayoutEffectsStopped();
2282 + }
2283 + }
2284 }
2285
2286 export function flushPassiveEffects() {
@@ -2257,6 +2359,14 @@ function flushPassiveEffectsImpl() {
2359 'Cannot flush passive effects while already rendering.',
2360 );
2361
2362 + if (__DEV__) {
2363 + if (enableDebugTracing) {
2364 + const priorityLevel = getCurrentPriorityLevel();
2365 + const label = priorityLevelToLabel(priorityLevel);
2366 + logPassiveEffectsStarted(label);
2367 + }
2368 + }
2369 +
2370 if (__DEV__) {
2371 isFlushingPassiveEffects = true;
2372 }
@@ -2424,6 +2534,12 @@ function flushPassiveEffectsImpl() {
2534 isFlushingPassiveEffects = false;
2535 }
2536
2537 + if (__DEV__) {
2538 + if (enableDebugTracing) {
2539 + logPassiveEffectsStopped();
2540 + }
2541 + }
2542 +
2543 executionContext = prevExecutionContext;
2544
2545 flushSyncCallbackQueue();
packages/react-reconciler/src/ReactTypeOfMode.js
+6 -5
@@ -9,10 +9,11 @@
9
10 export type TypeOfMode = number;
11
12 -export const NoMode = 0b0000;
13 -export const StrictMode = 0b0001;
12 +export const NoMode = 0b00000;
13 +export const StrictMode = 0b00001;
14 // TODO: Remove BlockingMode and ConcurrentMode by reading from the root
15 // tag instead
16 -export const BlockingMode = 0b0010;
17 -export const ConcurrentMode = 0b0100;
18 -export const ProfileMode = 0b1000;
16 +export const BlockingMode = 0b00010;
17 +export const ConcurrentMode = 0b00100;
18 +export const ProfileMode = 0b01000;
19 +export const DebugTracingMode = 0b10000;
packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js new
+345
@@ -0,0 +1,345 @@
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 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +describe('DebugTracing', () => {
13 + let React;
14 + let ReactTestRenderer;
15 + let Scheduler;
16 +
17 + let logs;
18 +
19 + beforeEach(() => {
20 + jest.resetModules();
21 +
22 + React = require('react');
23 + ReactTestRenderer = require('react-test-renderer');
24 + Scheduler = require('scheduler');
25 +
26 + logs = [];
27 +
28 + const groups = [];
29 +
30 + spyOnDevAndProd(console, 'log').and.callFake(message => {
31 + logs.push(`log: ${message.replace(/%c/g, '')}`);
32 + });
33 + spyOnDevAndProd(console, 'group').and.callFake(message => {
34 + logs.push(`group: ${message.replace(/%c/g, '')}`);
35 + groups.push(message);
36 + });
37 + spyOnDevAndProd(console, 'groupEnd').and.callFake(() => {
38 + const message = groups.pop();
39 + logs.push(`groupEnd: ${message.replace(/%c/g, '')}`);
40 + });
41 + });
42 +
43 + // @gate experimental
44 + it('should not log anything for sync render without suspends or state updates', () => {
45 + ReactTestRenderer.create(
46 + <React.unstable_DebugTracingMode>
47 + <div />
48 + </React.unstable_DebugTracingMode>,
49 + );
50 +
51 + expect(logs).toEqual([]);
52 + });
53 +
54 + // @gate experimental
55 + it('should not log anything for concurrent render without suspends or state updates', () => {
56 + ReactTestRenderer.create(
57 + <React.unstable_DebugTracingMode>
58 + <div />
59 + </React.unstable_DebugTracingMode>,
60 + {unstable_isConcurrent: true},
61 + );
62 +
63 + expect(logs).toEqual([]);
64 +
65 + logs.splice(0);
66 +
67 + expect(Scheduler).toFlushUntilNextPaint([]);
68 +
69 + expect(logs).toEqual([]);
70 + });
71 +
72 + // @gate experimental && enableDebugTracing
73 + it('should log sync render with suspense', async () => {
74 + const fakeSuspensPromise = Promise.resolve(true);
75 + function Example() {
76 + throw fakeSuspensPromise;
77 + }
78 +
79 + ReactTestRenderer.create(
80 + <React.unstable_DebugTracingMode>
81 + <React.Suspense fallback={null}>
82 + <Example />
83 + </React.Suspense>
84 + </React.unstable_DebugTracingMode>,
85 + );
86 +
87 + expect(logs).toEqual([
88 + 'group: ⚛️ render (priority: immediate)',
89 + 'log: ⚛️ Example suspended',
90 + 'groupEnd: ⚛️ render (priority: immediate)',
91 + ]);
92 +
93 + logs.splice(0);
94 +
95 + await fakeSuspensPromise;
96 + expect(logs).toEqual(['log: ⚛️ Example resolved']);
97 + });
98 +
99 + // @gate experimental && enableDebugTracing
100 + it('should log concurrent render with suspense', async () => {
101 + const fakeSuspensPromise = Promise.resolve(true);
102 + function Example() {
103 + throw fakeSuspensPromise;
104 + }
105 +
106 + ReactTestRenderer.create(
107 + <React.unstable_DebugTracingMode>
108 + <React.Suspense fallback={null}>
109 + <Example />
110 + </React.Suspense>
111 + </React.unstable_DebugTracingMode>,
112 + {unstable_isConcurrent: true},
113 + );
114 +
115 + expect(logs).toEqual([]);
116 +
117 + logs.splice(0);
118 +
119 + expect(Scheduler).toFlushUntilNextPaint([]);
120 +
121 + expect(logs).toEqual([
122 + 'group: ⚛️ render (priority: normal)',
123 + 'log: ⚛️ Example suspended',
124 + 'groupEnd: ⚛️ render (priority: normal)',
125 + ]);
126 +
127 + logs.splice(0);
128 +
129 + await fakeSuspensPromise;
130 + expect(logs).toEqual(['log: ⚛️ Example resolved']);
131 + });
132 +
133 + // @gate experimental && enableDebugTracing
134 + it('should log cascading class component updates', () => {
135 + class Example extends React.Component {
136 + state = {didMount: false};
137 + componentDidMount() {
138 + this.setState({didMount: true});
139 + }
140 + render() {
141 + return null;
142 + }
143 + }
144 +
145 + ReactTestRenderer.create(
146 + <React.unstable_DebugTracingMode>
147 + <Example />
148 + </React.unstable_DebugTracingMode>,
149 + {unstable_isConcurrent: true},
150 + );
151 +
152 + expect(logs).toEqual([]);
153 +
154 + logs.splice(0);
155 +
156 + expect(Scheduler).toFlushUntilNextPaint([]);
157 +
158 + expect(logs).toEqual([
159 + 'group: ⚛️ commit (priority: normal)',
160 + 'group: ⚛️ layout effects (priority: immediate)',
161 + 'log: ⚛️ Example updated state (priority: immediate)',
162 + 'groupEnd: ⚛️ layout effects (priority: immediate)',
163 + 'groupEnd: ⚛️ commit (priority: normal)',
164 + ]);
165 + });
166 +
167 + // @gate experimental && enableDebugTracing
168 + it('should log render phase state updates for class component', () => {
169 + class Example extends React.Component {
170 + state = {didRender: false};
171 + render() {
172 + if (this.state.didRender === false) {
173 + this.setState({didRender: true});
174 + }
175 + return null;
176 + }
177 + }
178 +
179 + ReactTestRenderer.create(
180 + <React.unstable_DebugTracingMode>
181 + <Example />
182 + </React.unstable_DebugTracingMode>,
183 + {unstable_isConcurrent: true},
184 + );
185 +
186 + expect(logs).toEqual([]);
187 +
188 + logs.splice(0);
189 +
190 + expect(() => {
191 + expect(Scheduler).toFlushUntilNextPaint([]);
192 + }).toErrorDev('Cannot update during an existing state transition');
193 +
194 + expect(logs).toEqual([
195 + 'group: ⚛️ render (priority: normal)',
196 + 'log: ⚛️ Example updated state (priority: normal)',
197 + 'log: ⚛️ Example updated state (priority: normal)',
198 + 'groupEnd: ⚛️ render (priority: normal)',
199 + ]);
200 + });
201 +
202 + // @gate experimental && enableDebugTracing
203 + it('should log cascading layout updates', () => {
204 + function Example() {
205 + const [didMount, setDidMount] = React.useState(false);
206 + React.useLayoutEffect(() => {
207 + setDidMount(true);
208 + }, []);
209 + return didMount;
210 + }
211 +
212 + ReactTestRenderer.create(
213 + <React.unstable_DebugTracingMode>
214 + <Example />
215 + </React.unstable_DebugTracingMode>,
216 + {unstable_isConcurrent: true},
217 + );
218 +
219 + expect(logs).toEqual([]);
220 +
221 + logs.splice(0);
222 +
223 + expect(Scheduler).toFlushUntilNextPaint([]);
224 +
225 + expect(logs).toEqual([
226 + 'group: ⚛️ commit (priority: normal)',
227 + 'group: ⚛️ layout effects (priority: immediate)',
228 + 'log: ⚛️ Example updated state (priority: immediate)',
229 + 'groupEnd: ⚛️ layout effects (priority: immediate)',
230 + 'groupEnd: ⚛️ commit (priority: normal)',
231 + ]);
232 + });
233 +
234 + // @gate experimental && enableDebugTracing
235 + it('should log cascading passive updates', () => {
236 + function Example() {
237 + const [didMount, setDidMount] = React.useState(false);
238 + React.useEffect(() => {
239 + setDidMount(true);
240 + }, []);
241 + return didMount;
242 + }
243 +
244 + ReactTestRenderer.act(() => {
245 + ReactTestRenderer.create(
246 + <React.unstable_DebugTracingMode>
247 + <Example />
248 + </React.unstable_DebugTracingMode>,
249 + {unstable_isConcurrent: true},
250 + );
251 + });
252 + expect(logs).toEqual([
253 + 'group: ⚛️ passive effects (priority: normal)',
254 + 'log: ⚛️ Example updated state (priority: normal)',
255 + 'groupEnd: ⚛️ passive effects (priority: normal)',
256 + ]);
257 + });
258 +
259 + // @gate experimental && enableDebugTracing
260 + it('should log render phase updates', () => {
261 + function Example() {
262 + const [didRender, setDidRender] = React.useState(false);
263 + if (!didRender) {
264 + setDidRender(true);
265 + }
266 + return didRender;
267 + }
268 +
269 + ReactTestRenderer.act(() => {
270 + ReactTestRenderer.create(
271 + <React.unstable_DebugTracingMode>
272 + <Example />
273 + </React.unstable_DebugTracingMode>,
274 + {unstable_isConcurrent: true},
275 + );
276 + });
277 + expect(logs).toEqual([
278 + 'group: ⚛️ render (priority: normal)',
279 + 'log: ⚛️ Example updated state (priority: normal)',
280 + 'log: ⚛️ Example updated state (priority: normal)', // debugRenderPhaseSideEffectsForStrictMode
281 + 'groupEnd: ⚛️ render (priority: normal)',
282 + ]);
283 + });
284 +
285 + // @gate experimental && enableDebugTracing
286 + it('should log when user code logs', () => {
287 + function Example() {
288 + console.log('Hello from user code');
289 + return null;
290 + }
291 +
292 + ReactTestRenderer.create(
293 + <React.unstable_DebugTracingMode>
294 + <Example />
295 + </React.unstable_DebugTracingMode>,
296 + {unstable_isConcurrent: true},
297 + );
298 +
299 + expect(logs).toEqual([]);
300 +
301 + logs.splice(0);
302 +
303 + expect(Scheduler).toFlushUntilNextPaint([]);
304 +
305 + expect(logs).toEqual([
306 + 'group: ⚛️ render (priority: normal)',
307 + 'log: Hello from user code',
308 + 'groupEnd: ⚛️ render (priority: normal)',
309 + ]);
310 + });
311 +
312 + // @gate experimental
313 + it('should not log anything outside of a unstable_DebugTracingMode subtree', () => {
314 + function ExampleThatCascades() {
315 + const [didMount, setDidMount] = React.useState(false);
316 + React.useLayoutEffect(() => {
317 + setDidMount(true);
318 + }, []);
319 + return didMount;
320 + }
321 +
322 + const fakeSuspensPromise = new Promise(() => {});
323 + function ExampleThatSuspends() {
324 + throw fakeSuspensPromise;
325 + }
326 +
327 + function Example() {
328 + return null;
329 + }
330 +
331 + ReactTestRenderer.create(
332 + <React.Fragment>
333 + <ExampleThatCascades />
334 + <React.Suspense fallback={null}>
335 + <ExampleThatSuspends />
336 + </React.Suspense>
337 + <React.unstable_DebugTracingMode>
338 + <Example />
339 + </React.unstable_DebugTracingMode>
340 + </React.Fragment>,
341 + );
342 +
343 + expect(logs).toEqual([]);
344 + });
345 +});
packages/react/index.classic.fb.js
+2
@@ -51,5 +51,7 @@ export {
51 // enableScopeAPI
52 unstable_createScope,
53 unstable_useOpaqueIdentifier,
54 + // enableDebugTracing
55 + unstable_DebugTracingMode,
56 } from './src/React';
57 export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';
packages/react/index.experimental.js
+2
@@ -46,4 +46,6 @@ export {
46 // enableBlocksAPI
47 block,
48 unstable_useOpaqueIdentifier,
49 + // enableDebugTracing
50 + unstable_DebugTracingMode,
51 } from './src/React';
packages/react/index.js
+1
@@ -59,6 +59,7 @@ export {
59 createMutableSource,
60 Fragment,
61 Profiler,
62 + unstable_DebugTracingMode,
63 StrictMode,
64 Suspense,
65 createElement,
packages/react/index.modern.fb.js
+2
@@ -50,5 +50,7 @@ export {
50 // enableScopeAPI
51 unstable_createScope,
52 unstable_useOpaqueIdentifier,
53 + // enableDebugTracing
54 + unstable_DebugTracingMode,
55 } from './src/React';
56 export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';
packages/react/src/React.js
+2
@@ -10,6 +10,7 @@
10 import ReactVersion from 'shared/ReactVersion';
11 import {
12 REACT_FRAGMENT_TYPE,
13 + REACT_DEBUG_TRACING_MODE_TYPE,
14 REACT_PROFILER_TYPE,
15 REACT_STRICT_MODE_TYPE,
16 REACT_SUSPENSE_TYPE,
@@ -96,6 +97,7 @@ export {
97 REACT_FRAGMENT_TYPE as Fragment,
98 REACT_PROFILER_TYPE as Profiler,
99 REACT_STRICT_MODE_TYPE as StrictMode,
100 + REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode,
101 REACT_SUSPENSE_TYPE as Suspense,
102 createElement,
103 cloneElement,
packages/shared/ReactFeatureFlags.js
+4
@@ -11,6 +11,10 @@
11 // This prevents e.g. <img src=""> from making an unnecessar HTTP request for certain browsers.
12 export const enableFilterEmptyStringAttributesDOM = false;
13
14 +// Adds verbose console logging for e.g. state updates, suspense, and work loop stuff.
15 +// Intended to enable React core members to more easily debug scheduling issues in DEV builds.
16 +export const enableDebugTracing = false;
17 +
18 // Helps identify side effects in render-phase lifecycle hooks and setState
19 // reducers by double invoking them in Strict Mode.
20 export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
packages/shared/ReactSymbols.js
+2
@@ -27,6 +27,7 @@ export let REACT_FUNDAMENTAL_TYPE = 0xead5;
27 export let REACT_RESPONDER_TYPE = 0xead6;
28 export let REACT_SCOPE_TYPE = 0xead7;
29 export let REACT_OPAQUE_ID_TYPE = 0xeae0;
30 +export let REACT_DEBUG_TRACING_MODE_TYPE = 0xeae1;
31
32 if (typeof Symbol === 'function' && Symbol.for) {
33 const symbolFor = Symbol.for;
@@ -48,6 +49,7 @@ if (typeof Symbol === 'function' && Symbol.for) {
49 REACT_RESPONDER_TYPE = symbolFor('react.responder');
50 REACT_SCOPE_TYPE = symbolFor('react.scope');
51 REACT_OPAQUE_ID_TYPE = symbolFor('react.opaque.id');
52 + REACT_DEBUG_TRACING_MODE_TYPE = symbolFor('react.debug_trace_mode');
53 }
54
55 const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.native-fb';
12
13 // The rest of the flags are static for better dead code elimination.
14 +export const enableDebugTracing = false;
15 export const enableProfilerTimer = __PROFILE__;
16 export const enableProfilerCommitHooks = false;
17 export const enableSchedulerTracing = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.native-oss';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 +export const enableDebugTracing = false;
15 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
16 export const warnAboutDeprecatedLifecycles = true;
17 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.test-renderer';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 +export const enableDebugTracing = false;
15 export const warnAboutDeprecatedLifecycles = true;
16 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
17 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.test-renderer.www';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 +export const enableDebugTracing = false;
15 export const warnAboutDeprecatedLifecycles = true;
16 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
17 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.testing';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 +export const enableDebugTracing = false;
15 export const warnAboutDeprecatedLifecycles = true;
16 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
17 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -11,6 +11,7 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as ExportsType from './ReactFeatureFlags.testing.www';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 +export const enableDebugTracing = false;
15 export const warnAboutDeprecatedLifecycles = true;
16 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
17 export const enableProfilerTimer = false;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -20,6 +20,7 @@ export const disableInputAttributeSyncing = __VARIANT__;
20 export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
21 export const enableModernEventSystem = __VARIANT__;
22 export const enableLegacyFBSupport = __VARIANT__;
23 +export const enableDebugTracing = __VARIANT__;
24
25 // These are already tested in both modes using the build type dimension,
26 // so we don't need to use __VARIANT__ to get extra coverage.
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -26,6 +26,7 @@ export const {
26 enableModernEventSystem,
27 enableFilterEmptyStringAttributesDOM,
28 enableLegacyFBSupport,
29 + enableDebugTracing,
30 } = dynamicFeatureFlags;
31
32 // On WWW, __EXPERIMENTAL__ is used for a new modern build.
packages/shared/isValidElementType.js
+2
@@ -13,6 +13,7 @@ import {
13 REACT_FRAGMENT_TYPE,
14 REACT_PROFILER_TYPE,
15 REACT_PROVIDER_TYPE,
16 + REACT_DEBUG_TRACING_MODE_TYPE,
17 REACT_STRICT_MODE_TYPE,
18 REACT_SUSPENSE_TYPE,
19 REACT_SUSPENSE_LIST_TYPE,
@@ -32,6 +33,7 @@ export default function isValidElementType(type: mixed) {
33 // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
34 type === REACT_FRAGMENT_TYPE ||
35 type === REACT_PROFILER_TYPE ||
36 + type === REACT_DEBUG_TRACING_MODE_TYPE ||
37 type === REACT_STRICT_MODE_TYPE ||
38 type === REACT_SUSPENSE_TYPE ||
39 type === REACT_SUSPENSE_LIST_TYPE ||