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 ||