DevTools: Improved "native" component stacks (#18656)
* DevTools console override handles new component stack format DevTools does not attempt to mimic the default browser console format for its component stacks but it does properly detect the new format for Chrome, Firefox, and Safari.
Brian Vaughn committed
Apr 21, 2020 at 11:46 UTC
36cab2720ae2dbcaa63a90562ddb042101944f86
17 files changed
+630
-223
packages/react-devtools-core/webpack.backend.js
+2
@@ -43,6 +43,8 @@ module.exports = {
43
plugins: [
44
new DefinePlugin({
45
__DEV__: true,
46
+ __PROFILE__: false,
47
+ __EXPERIMENTAL__: true,
48
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
49
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
50
}),
packages/react-devtools-core/webpack.standalone.js
+2
@@ -48,6 +48,8 @@ module.exports = {
48
plugins: [
49
new DefinePlugin({
50
__DEV__: false,
51
+ __PROFILE__: false,
52
+ __EXPERIMENTAL__: true,
53
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
54
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
55
'process.env.NODE_ENV': `"${NODE_ENV}"`,
packages/react-devtools-extensions/webpack.backend.js
+2
@@ -38,6 +38,8 @@ module.exports = {
38
plugins: [
39
new DefinePlugin({
40
__DEV__: true,
41
+ __PROFILE__: false,
42
+ __EXPERIMENTAL__: true,
43
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
44
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
45
}),
packages/react-devtools-extensions/webpack.config.js
+2
@@ -43,6 +43,8 @@ module.exports = {
43
plugins: [
44
new DefinePlugin({
45
__DEV__: false,
46
+ __PROFILE__: false,
47
+ __EXPERIMENTAL__: true,
48
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
49
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
50
'process.env.NODE_ENV': `"${NODE_ENV}"`,
packages/react-devtools-inline/webpack.config.js
+2
@@ -39,6 +39,8 @@ module.exports = {
39
plugins: [
40
new DefinePlugin({
41
__DEV__,
42
+ __PROFILE__: false,
43
+ __EXPERIMENTAL__: true,
44
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
45
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
46
'process.env.NODE_ENV': `"${NODE_ENV}"`,
packages/react-devtools-shared/src/__tests__/console-test.js
+20
-20
@@ -114,7 +114,7 @@ describe('console', () => {
114
});
115
116
it('should not append multiple stacks', () => {
117
- const Child = () => {
117
+ const Child = ({children}) => {
118
fakeConsole.warn('warn\n in Child (at fake.js:123)');
119
fakeConsole.error('error', '\n in Child (at fake.js:123)');
120
return null;
@@ -135,12 +135,12 @@ describe('console', () => {
135
136
it('should append component stacks to errors and warnings logged during render', () => {
137
const Intermediate = ({children}) => children;
138
- const Parent = () => (
138
+ const Parent = ({children}) => (
139
<Intermediate>
140
<Child />
141
</Intermediate>
142
);
143
- const Child = () => {
143
+ const Child = ({children}) => {
144
fakeConsole.error('error');
145
fakeConsole.log('log');
146
fakeConsole.warn('warn');
@@ -156,24 +156,24 @@ describe('console', () => {
156
expect(mockWarn.mock.calls[0]).toHaveLength(2);
157
expect(mockWarn.mock.calls[0][0]).toBe('warn');
158
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
159
- '\n in Child (at **)\n in Parent (at **)',
159
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
160
);
161
expect(mockError).toHaveBeenCalledTimes(1);
162
expect(mockError.mock.calls[0]).toHaveLength(2);
163
expect(mockError.mock.calls[0][0]).toBe('error');
164
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
165
- '\n in Child (at **)\n in Parent (at **)',
165
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
166
);
167
});
168
169
it('should append component stacks to errors and warnings logged from effects', () => {
170
const Intermediate = ({children}) => children;
171
- const Parent = () => (
171
+ const Parent = ({children}) => (
172
<Intermediate>
173
<Child />
174
</Intermediate>
175
);
176
- const Child = () => {
176
+ const Child = ({children}) => {
177
React.useLayoutEffect(() => {
178
fakeConsole.error('active error');
179
fakeConsole.log('active log');
@@ -198,29 +198,29 @@ describe('console', () => {
198
expect(mockWarn.mock.calls[0]).toHaveLength(2);
199
expect(mockWarn.mock.calls[0][0]).toBe('active warn');
200
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
201
- '\n in Child (at **)\n in Parent (at **)',
201
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
202
);
203
expect(mockWarn.mock.calls[1]).toHaveLength(2);
204
expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
205
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
206
- '\n in Child (at **)\n in Parent (at **)',
206
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
207
);
208
expect(mockError).toHaveBeenCalledTimes(2);
209
expect(mockError.mock.calls[0]).toHaveLength(2);
210
expect(mockError.mock.calls[0][0]).toBe('active error');
211
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
212
- '\n in Child (at **)\n in Parent (at **)',
212
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
213
);
214
expect(mockError.mock.calls[1]).toHaveLength(2);
215
expect(mockError.mock.calls[1][0]).toBe('passive error');
216
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
217
- '\n in Child (at **)\n in Parent (at **)',
217
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
218
);
219
});
220
221
it('should append component stacks to errors and warnings logged from commit hooks', () => {
222
const Intermediate = ({children}) => children;
223
- const Parent = () => (
223
+ const Parent = ({children}) => (
224
<Intermediate>
225
<Child />
226
</Intermediate>
@@ -254,29 +254,29 @@ describe('console', () => {
254
expect(mockWarn.mock.calls[0]).toHaveLength(2);
255
expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
256
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
257
- '\n in Child (at **)\n in Parent (at **)',
257
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
258
);
259
expect(mockWarn.mock.calls[1]).toHaveLength(2);
260
expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
261
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
262
- '\n in Child (at **)\n in Parent (at **)',
262
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
263
);
264
expect(mockError).toHaveBeenCalledTimes(2);
265
expect(mockError.mock.calls[0]).toHaveLength(2);
266
expect(mockError.mock.calls[0][0]).toBe('didMount error');
267
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
268
- '\n in Child (at **)\n in Parent (at **)',
268
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
269
);
270
expect(mockError.mock.calls[1]).toHaveLength(2);
271
expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
272
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
273
- '\n in Child (at **)\n in Parent (at **)',
273
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
274
);
275
});
276
277
it('should append component stacks to errors and warnings logged from gDSFP', () => {
278
const Intermediate = ({children}) => children;
279
- const Parent = () => (
279
+ const Parent = ({children}) => (
280
<Intermediate>
281
<Child />
282
</Intermediate>
@@ -303,18 +303,18 @@ describe('console', () => {
303
expect(mockWarn.mock.calls[0]).toHaveLength(2);
304
expect(mockWarn.mock.calls[0][0]).toBe('warn');
305
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
306
- '\n in Child (at **)\n in Parent (at **)',
306
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
307
);
308
expect(mockError).toHaveBeenCalledTimes(1);
309
expect(mockError.mock.calls[0]).toHaveLength(2);
310
expect(mockError.mock.calls[0][0]).toBe('error');
311
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
312
- '\n in Child (at **)\n in Parent (at **)',
312
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
313
);
314
});
315
316
it('should append stacks after being uninstalled and reinstalled', () => {
317
- const Child = () => {
317
+ const Child = ({children}) => {
318
fakeConsole.warn('warn');
319
fakeConsole.error('error');
320
return null;
packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js
new
+298
@@ -0,0 +1,298 @@
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
+// This is a DevTools fork of ReactComponentStackFrame.
11
+// This fork enables DevTools to use the same "native" component stack format,
12
+// while still maintaining support for multiple renderer versions
13
+// (which use different values for ReactTypeOfWork).
14
+
15
+import type {Source} from 'shared/ReactElementType';
16
+import type {LazyComponent} from 'react/src/ReactLazy';
17
+import type {CurrentDispatcherRef} from './types';
18
+
19
+import {
20
+ BLOCK_NUMBER,
21
+ BLOCK_SYMBOL_STRING,
22
+ FORWARD_REF_NUMBER,
23
+ FORWARD_REF_SYMBOL_STRING,
24
+ LAZY_NUMBER,
25
+ LAZY_SYMBOL_STRING,
26
+ MEMO_NUMBER,
27
+ MEMO_SYMBOL_STRING,
28
+ SUSPENSE_NUMBER,
29
+ SUSPENSE_SYMBOL_STRING,
30
+ SUSPENSE_LIST_NUMBER,
31
+ SUSPENSE_LIST_SYMBOL_STRING,
32
+} from './ReactSymbols';
33
+
34
+// These methods are safe to import from shared;
35
+// there is no React-specific logic here.
36
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
37
+
38
+let prefix;
39
+export function describeBuiltInComponentFrame(
40
+ name: string,
41
+ source: void | null | Source,
42
+ ownerFn: void | null | Function,
43
+): string {
44
+ if (prefix === undefined) {
45
+ // Extract the VM specific prefix used by each line.
46
+ try {
47
+ throw Error();
48
+ } catch (x) {
49
+ const match = x.stack.trim().match(/\n( *(at )?)/);
50
+ prefix = (match && match[1]) || '';
51
+ }
52
+ }
53
+ // We use the prefix to ensure our stacks line up with native stack frames.
54
+ return '\n' + prefix + name;
55
+}
56
+
57
+let reentry = false;
58
+let componentFrameCache;
59
+if (__DEV__) {
60
+ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
61
+ componentFrameCache = new PossiblyWeakMap();
62
+}
63
+
64
+export function describeNativeComponentFrame(
65
+ fn: Function,
66
+ construct: boolean,
67
+ currentDispatcherRef: CurrentDispatcherRef,
68
+): string {
69
+ // If something asked for a stack inside a fake render, it should get ignored.
70
+ if (!fn || reentry) {
71
+ return '';
72
+ }
73
+
74
+ if (__DEV__) {
75
+ const frame = componentFrameCache.get(fn);
76
+ if (frame !== undefined) {
77
+ return frame;
78
+ }
79
+ }
80
+
81
+ let control;
82
+
83
+ reentry = true;
84
+ let previousDispatcher;
85
+ if (__DEV__) {
86
+ previousDispatcher = currentDispatcherRef.current;
87
+ // Set the dispatcher in DEV because this might be call in the render function
88
+ // for warnings.
89
+ currentDispatcherRef.current = null;
90
+ disableLogs();
91
+ }
92
+ try {
93
+ // This should throw.
94
+ if (construct) {
95
+ // Something should be setting the props in the constructor.
96
+ const Fake = function() {
97
+ throw Error();
98
+ };
99
+ // $FlowFixMe
100
+ Object.defineProperty(Fake.prototype, 'props', {
101
+ set: function() {
102
+ // We use a throwing setter instead of frozen or non-writable props
103
+ // because that won't throw in a non-strict mode function.
104
+ throw Error();
105
+ },
106
+ });
107
+ if (typeof Reflect === 'object' && Reflect.construct) {
108
+ // We construct a different control for this case to include any extra
109
+ // frames added by the construct call.
110
+ try {
111
+ Reflect.construct(Fake, []);
112
+ } catch (x) {
113
+ control = x;
114
+ }
115
+ Reflect.construct(fn, [], Fake);
116
+ } else {
117
+ try {
118
+ Fake.call();
119
+ } catch (x) {
120
+ control = x;
121
+ }
122
+ fn.call(Fake.prototype);
123
+ }
124
+ } else {
125
+ try {
126
+ throw Error();
127
+ } catch (x) {
128
+ control = x;
129
+ }
130
+ fn();
131
+ }
132
+ } catch (sample) {
133
+ // This is inlined manually because closure doesn't do it for us.
134
+ if (sample && control && typeof sample.stack === 'string') {
135
+ // This extracts the first frame from the sample that isn't also in the control.
136
+ // Skipping one frame that we assume is the frame that calls the two.
137
+ const sampleLines = sample.stack.split('\n');
138
+ const controlLines = control.stack.split('\n');
139
+ let s = sampleLines.length - 1;
140
+ let c = controlLines.length - 1;
141
+ while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
142
+ // We expect at least one stack frame to be shared.
143
+ // Typically this will be the root most one. However, stack frames may be
144
+ // cut off due to maximum stack limits. In this case, one maybe cut off
145
+ // earlier than the other. We assume that the sample is longer or the same
146
+ // and there for cut off earlier. So we should find the root most frame in
147
+ // the sample somewhere in the control.
148
+ c--;
149
+ }
150
+ for (; s >= 1 && c >= 0; s--, c--) {
151
+ // Next we find the first one that isn't the same which should be the
152
+ // frame that called our sample function and the control.
153
+ if (sampleLines[s] !== controlLines[c]) {
154
+ // In V8, the first line is describing the message but other VMs don't.
155
+ // If we're about to return the first line, and the control is also on the same
156
+ // line, that's a pretty good indicator that our sample threw at same line as
157
+ // the control. I.e. before we entered the sample frame. So we ignore this result.
158
+ // This can happen if you passed a class to function component, or non-function.
159
+ if (s !== 1 || c !== 1) {
160
+ do {
161
+ s--;
162
+ c--;
163
+ // We may still have similar intermediate frames from the construct call.
164
+ // The next one that isn't the same should be our match though.
165
+ if (c < 0 || sampleLines[s] !== controlLines[c]) {
166
+ // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
167
+ const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
168
+ if (__DEV__) {
169
+ if (typeof fn === 'function') {
170
+ componentFrameCache.set(fn, frame);
171
+ }
172
+ }
173
+ // Return the line we found.
174
+ return frame;
175
+ }
176
+ } while (s >= 1 && c >= 0);
177
+ }
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ } finally {
183
+ reentry = false;
184
+ if (__DEV__) {
185
+ currentDispatcherRef.current = previousDispatcher;
186
+ reenableLogs();
187
+ }
188
+ }
189
+ // Fallback to just using the name if we couldn't make it throw.
190
+ const name = fn ? fn.displayName || fn.name : '';
191
+ const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
192
+ if (__DEV__) {
193
+ if (typeof fn === 'function') {
194
+ componentFrameCache.set(fn, syntheticFrame);
195
+ }
196
+ }
197
+ return syntheticFrame;
198
+}
199
+
200
+export function describeClassComponentFrame(
201
+ ctor: Function,
202
+ source: void | null | Source,
203
+ ownerFn: void | null | Function,
204
+ currentDispatcherRef: CurrentDispatcherRef,
205
+): string {
206
+ return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
207
+}
208
+
209
+export function describeFunctionComponentFrame(
210
+ fn: Function,
211
+ source: void | null | Source,
212
+ ownerFn: void | null | Function,
213
+ currentDispatcherRef: CurrentDispatcherRef,
214
+): string {
215
+ return describeNativeComponentFrame(fn, false, currentDispatcherRef);
216
+}
217
+
218
+function shouldConstruct(Component: Function) {
219
+ const prototype = Component.prototype;
220
+ return !!(prototype && prototype.isReactComponent);
221
+}
222
+
223
+export function describeUnknownElementTypeFrameInDEV(
224
+ type: any,
225
+ source: void | null | Source,
226
+ ownerFn: void | null | Function,
227
+ currentDispatcherRef: CurrentDispatcherRef,
228
+): string {
229
+ if (!__DEV__) {
230
+ return '';
231
+ }
232
+ if (type == null) {
233
+ return '';
234
+ }
235
+ if (typeof type === 'function') {
236
+ return describeNativeComponentFrame(
237
+ type,
238
+ shouldConstruct(type),
239
+ currentDispatcherRef,
240
+ );
241
+ }
242
+ if (typeof type === 'string') {
243
+ return describeBuiltInComponentFrame(type, source, ownerFn);
244
+ }
245
+ switch (type) {
246
+ case SUSPENSE_NUMBER:
247
+ case SUSPENSE_SYMBOL_STRING:
248
+ return describeBuiltInComponentFrame('Suspense', source, ownerFn);
249
+ case SUSPENSE_LIST_NUMBER:
250
+ case SUSPENSE_LIST_SYMBOL_STRING:
251
+ return describeBuiltInComponentFrame('SuspenseList', source, ownerFn);
252
+ }
253
+ if (typeof type === 'object') {
254
+ switch (type.$$typeof) {
255
+ case FORWARD_REF_NUMBER:
256
+ case FORWARD_REF_SYMBOL_STRING:
257
+ return describeFunctionComponentFrame(
258
+ type.render,
259
+ source,
260
+ ownerFn,
261
+ currentDispatcherRef,
262
+ );
263
+ case MEMO_NUMBER:
264
+ case MEMO_SYMBOL_STRING:
265
+ // Memo may contain any component type so we recursively resolve it.
266
+ return describeUnknownElementTypeFrameInDEV(
267
+ type.type,
268
+ source,
269
+ ownerFn,
270
+ currentDispatcherRef,
271
+ );
272
+ case BLOCK_NUMBER:
273
+ case BLOCK_SYMBOL_STRING:
274
+ return describeFunctionComponentFrame(
275
+ type._render,
276
+ source,
277
+ ownerFn,
278
+ currentDispatcherRef,
279
+ );
280
+ case LAZY_NUMBER:
281
+ case LAZY_SYMBOL_STRING: {
282
+ const lazyComponent: LazyComponent<any, any> = (type: any);
283
+ const payload = lazyComponent._payload;
284
+ const init = lazyComponent._init;
285
+ try {
286
+ // Lazy may contain any component type so we recursively resolve it.
287
+ return describeUnknownElementTypeFrameInDEV(
288
+ init(payload),
289
+ source,
290
+ ownerFn,
291
+ currentDispatcherRef,
292
+ );
293
+ } catch (x) {}
294
+ }
295
+ }
296
+ }
297
+ return '';
298
+}
packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
new
+108
@@ -0,0 +1,108 @@
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
+// This is a DevTools fork of ReactFiberComponentStack.
11
+// This fork enables DevTools to use the same "native" component stack format,
12
+// while still maintaining support for multiple renderer versions
13
+// (which use different values for ReactTypeOfWork).
14
+
15
+import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
16
+import type {CurrentDispatcherRef, WorkTagMap} from './types';
17
+
18
+import {
19
+ describeBuiltInComponentFrame,
20
+ describeFunctionComponentFrame,
21
+ describeClassComponentFrame,
22
+} from './DevToolsComponentStackFrame';
23
+
24
+function describeFiber(
25
+ workTagMap: WorkTagMap,
26
+ workInProgress: Fiber,
27
+ currentDispatcherRef: CurrentDispatcherRef,
28
+): string {
29
+ const {
30
+ HostComponent,
31
+ LazyComponent,
32
+ SuspenseComponent,
33
+ SuspenseListComponent,
34
+ FunctionComponent,
35
+ IndeterminateComponent,
36
+ SimpleMemoComponent,
37
+ ForwardRef,
38
+ Block,
39
+ ClassComponent,
40
+ } = workTagMap;
41
+
42
+ const owner: null | Function = __DEV__
43
+ ? workInProgress._debugOwner
44
+ ? workInProgress._debugOwner.type
45
+ : null
46
+ : null;
47
+ const source = __DEV__ ? workInProgress._debugSource : null;
48
+ switch (workInProgress.tag) {
49
+ case HostComponent:
50
+ return describeBuiltInComponentFrame(workInProgress.type, source, owner);
51
+ case LazyComponent:
52
+ return describeBuiltInComponentFrame('Lazy', source, owner);
53
+ case SuspenseComponent:
54
+ return describeBuiltInComponentFrame('Suspense', source, owner);
55
+ case SuspenseListComponent:
56
+ return describeBuiltInComponentFrame('SuspenseList', source, owner);
57
+ case FunctionComponent:
58
+ case IndeterminateComponent:
59
+ case SimpleMemoComponent:
60
+ return describeFunctionComponentFrame(
61
+ workInProgress.type,
62
+ source,
63
+ owner,
64
+ currentDispatcherRef,
65
+ );
66
+ case ForwardRef:
67
+ return describeFunctionComponentFrame(
68
+ workInProgress.type.render,
69
+ source,
70
+ owner,
71
+ currentDispatcherRef,
72
+ );
73
+ case Block:
74
+ return describeFunctionComponentFrame(
75
+ workInProgress.type._render,
76
+ source,
77
+ owner,
78
+ currentDispatcherRef,
79
+ );
80
+ case ClassComponent:
81
+ return describeClassComponentFrame(
82
+ workInProgress.type,
83
+ source,
84
+ owner,
85
+ currentDispatcherRef,
86
+ );
87
+ default:
88
+ return '';
89
+ }
90
+}
91
+
92
+export function getStackByFiberInDevAndProd(
93
+ workTagMap: WorkTagMap,
94
+ workInProgress: Fiber,
95
+ currentDispatcherRef: CurrentDispatcherRef,
96
+): string {
97
+ try {
98
+ let info = '';
99
+ let node = workInProgress;
100
+ do {
101
+ info += describeFiber(workTagMap, node, currentDispatcherRef);
102
+ node = node.return;
103
+ } while (node);
104
+ return info;
105
+ } catch (x) {
106
+ return '\nError generating stack: ' + x.message + '\n' + x.stack;
107
+ }
108
+}
packages/react-devtools-shared/src/backend/ReactSymbols.js
new
+77
@@ -0,0 +1,77 @@
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
+// This list should be kept updated to reflect additions to 'shared/ReactSymbols'.
11
+// DevTools can't import symbols from 'shared/ReactSymbols' directly for two reasons:
12
+// 1. DevTools requires symbols which may have been deleted in more recent versions (e.g. concurrent mode)
13
+// 2. DevTools must support both Symbol and numeric forms of each symbol;
14
+// Since e.g. standalone DevTools runs in a separate process, it can't rely on its own ES capabilities.
15
+
16
+export const BLOCK_NUMBER = 0xead9;
17
+export const BLOCK_SYMBOL_STRING = 'Symbol(react.block)';
18
+
19
+export const CONCURRENT_MODE_NUMBER = 0xeacf;
20
+export const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)';
21
+
22
+export const CONTEXT_NUMBER = 0xeace;
23
+export const CONTEXT_SYMBOL_STRING = 'Symbol(react.context)';
24
+
25
+export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)';
26
+
27
+export const ELEMENT_NUMBER = 0xeac7;
28
+export const ELEMENT_SYMBOL_STRING = 'Symbol(react.element)';
29
+
30
+export const DEBUG_TRACING_MODE_NUMBER = 0xeae1;
31
+export const DEBUG_TRACING_MODE_SYMBOL_STRING =
32
+ 'Symbol(react.debug_trace_mode)';
33
+
34
+export const FORWARD_REF_NUMBER = 0xead0;
35
+export const FORWARD_REF_SYMBOL_STRING = 'Symbol(react.forward_ref)';
36
+
37
+export const FRAGMENT_NUMBER = 0xeacb;
38
+export const FRAGMENT_SYMBOL_STRING = 'Symbol(react.fragment)';
39
+
40
+export const FUNDAMENTAL_NUMBER = 0xead5;
41
+export const FUNDAMENTAL_SYMBOL_STRING = 'Symbol(react.fundamental)';
42
+
43
+export const LAZY_NUMBER = 0xead4;
44
+export const LAZY_SYMBOL_STRING = 'Symbol(react.lazy)';
45
+
46
+export const MEMO_NUMBER = 0xead3;
47
+export const MEMO_SYMBOL_STRING = 'Symbol(react.memo)';
48
+
49
+export const OPAQUE_ID_NUMBER = 0xeae0;
50
+export const OPAQUE_ID_SYMBOL_STRING = 'Symbol(react.opaque.id)';
51
+
52
+export const PORTAL_NUMBER = 0xeaca;
53
+export const PORTAL_SYMBOL_STRING = 'Symbol(react.portal)';
54
+
55
+export const PROFILER_NUMBER = 0xead2;
56
+export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
57
+
58
+export const PROVIDER_NUMBER = 0xeacd;
59
+export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
60
+
61
+export const RESPONDER_NUMBER = 0xead6;
62
+export const RESPONDER_SYMBOL_STRING = 'Symbol(react.responder)';
63
+
64
+export const SCOPE_NUMBER = 0xead7;
65
+export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';
66
+
67
+export const SERVER_BLOCK_NUMBER = 0xeada;
68
+export const SERVER_BLOCK_SYMBOL_STRING = 'Symbol(react.server.block)';
69
+
70
+export const STRICT_MODE_NUMBER = 0xeacc;
71
+export const STRICT_MODE_SYMBOL_STRING = 'Symbol(react.strict_mode)';
72
+
73
+export const SUSPENSE_NUMBER = 0xead1;
74
+export const SUSPENSE_SYMBOL_STRING = 'Symbol(react.suspense)';
75
+
76
+export const SUSPENSE_LIST_NUMBER = 0xead8;
77
+export const SUSPENSE_LIST_SYMBOL_STRING = 'Symbol(react.suspense_list)';
packages/react-devtools-shared/src/backend/console.js
+39
-30
@@ -7,21 +7,27 @@
7
* @flow
8
*/
9
10
-import {getInternalReactConstants} from './renderer';
11
-import describeComponentFrame from './describeComponentFrame';
12
-
10
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
14
-import type {ReactRenderer} from './types';
11
+import type {CurrentDispatcherRef, ReactRenderer, WorkTagMap} from './types';
12
+
13
+import {getInternalReactConstants} from './renderer';
14
+import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
15
16
const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn'];
17
18
-const FRAME_REGEX = /\n {4}in /;
18
+// React's custom built component stack strings match "\s{4}in"
19
+// Chrome's prefix matches "\s{4}at"
20
+const PREFIX_REGEX = /\s{4}(in|at)\s{1}/;
21
+// Firefox and Safari have no prefix ("")
22
+// but we can fallback to looking for location info (e.g. "foo.js:12:345")
23
+const ROW_COLUMN_NUMBER_REGEX = /:\d+:\d+(\n|$)/;
24
25
const injectedRenderers: Map<
26
ReactRenderer,
27
{|
28
+ currentDispatcherRef: CurrentDispatcherRef,
29
getCurrentFiber: () => Fiber | null,
24
- getDisplayNameForFiber: (fiber: Fiber) => string | null,
30
+ workTagMap: WorkTagMap,
31
|},
32
> = new Map();
33
@@ -49,19 +55,27 @@ export function dangerous_setTargetConsoleForTesting(
55
// These internals will be used if the console is patched.
56
// Injecting them separately allows the console to easily be patched or un-patched later (at runtime).
57
export function registerRenderer(renderer: ReactRenderer): void {
52
- const {getCurrentFiber, findFiberByHostInstance, version} = renderer;
58
+ const {
59
+ currentDispatcherRef,
60
+ getCurrentFiber,
61
+ findFiberByHostInstance,
62
+ version,
63
+ } = renderer;
64
65
// Ignore React v15 and older because they don't expose a component stack anyway.
66
if (typeof findFiberByHostInstance !== 'function') {
67
return;
68
}
69
59
- if (typeof getCurrentFiber === 'function') {
60
- const {getDisplayNameForFiber} = getInternalReactConstants(version);
70
+ // currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
71
+ // getCurrentFiber gets injected for v16.9+.
72
+ if (currentDispatcherRef != null && typeof getCurrentFiber === 'function') {
73
+ const {ReactTypeOfWork} = getInternalReactConstants(version);
74
75
injectedRenderers.set(renderer, {
76
+ currentDispatcherRef,
77
getCurrentFiber,
64
- getDisplayNameForFiber,
78
+ workTagMap: ReactTypeOfWork,
79
});
80
}
81
}
@@ -94,36 +108,31 @@ export function patch(): void {
108
try {
109
// If we are ever called with a string that already has a component stack, e.g. a React error/warning,
110
// don't append a second stack.
111
+ const lastArg = args.length > 0 ? args[args.length - 1] : null;
112
const alreadyHasComponentStack =
98
- args.length > 0 && FRAME_REGEX.exec(args[args.length - 1]);
113
+ lastArg !== null &&
114
+ (PREFIX_REGEX.test(lastArg) ||
115
+ ROW_COLUMN_NUMBER_REGEX.test(lastArg));
116
117
if (!alreadyHasComponentStack) {
118
// If there's a component stack for at least one of the injected renderers, append it.
119
// We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
120
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
121
for (const {
122
+ currentDispatcherRef,
123
getCurrentFiber,
106
- getDisplayNameForFiber,
124
+ workTagMap,
125
} of injectedRenderers.values()) {
108
- let current: ?Fiber = getCurrentFiber();
109
- let ownerStack: string = '';
110
- while (current != null) {
111
- const name = getDisplayNameForFiber(current);
112
- const owner = current._debugOwner;
113
- const ownerName =
114
- owner != null ? getDisplayNameForFiber(owner) : null;
115
-
116
- ownerStack += describeComponentFrame(
117
- name,
118
- current._debugSource,
119
- ownerName,
126
+ const current: ?Fiber = getCurrentFiber();
127
+ if (current != null) {
128
+ const componentStack = getStackByFiberInDevAndProd(
129
+ workTagMap,
130
+ current,
131
+ currentDispatcherRef,
132
);
121
-
122
- current = owner;
123
- }
124
-
125
- if (ownerStack !== '') {
126
- args.push(ownerStack);
133
+ if (componentStack !== '') {
134
+ args.push(componentStack);
135
+ }
136
break;
137
}
138
}
packages/react-devtools-shared/src/backend/describeComponentFrame.js
deleted
-48
@@ -1,48 +0,0 @@
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
-// This file was forked from the React GitHub repo:
11
-// https://raw.githubusercontent.com/facebook/react/master/packages/shared/describeComponentFrame.js
12
-//
13
-// It has been modified slightly to add a zero width space as commented below.
14
-
15
-const BEFORE_SLASH_RE = /^(.*)[\\/]/;
16
-
17
-export default function describeComponentFrame(
18
- name: null | string,
19
- source: any,
20
- ownerName: null | string,
21
-) {
22
- let sourceInfo = '';
23
- if (source) {
24
- const path = source.fileName;
25
- let fileName = path.replace(BEFORE_SLASH_RE, '');
26
- if (__DEV__) {
27
- // In DEV, include code for a common special case:
28
- // prefer "folder/index.js" instead of just "index.js".
29
- if (/^index\./.test(fileName)) {
30
- const match = path.match(BEFORE_SLASH_RE);
31
- if (match) {
32
- const pathBeforeSlash = match[1];
33
- if (pathBeforeSlash) {
34
- const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
35
- // Note the below string contains a zero width space after the "/" character.
36
- // This is to prevent browsers like Chrome from formatting the file name as a link.
37
- // (Since this is a source link, it would not work to open the source file anyway.)
38
- fileName = folderName + '/' + fileName;
39
- }
40
- }
41
- }
42
- }
43
- sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
44
- } else if (ownerName) {
45
- sourceInfo = ' (created by ' + ownerName + ')';
46
- }
47
- return '\n in ' + (name || 'Unknown') + sourceInfo;
48
-}
packages/react-devtools-shared/src/backend/renderer.js
+37
-116
@@ -49,6 +49,25 @@ import {
49
patch as patchConsole,
50
registerRenderer as registerRendererWithConsole,
51
} from './console';
52
+import {
53
+ CONCURRENT_MODE_NUMBER,
54
+ CONCURRENT_MODE_SYMBOL_STRING,
55
+ DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
56
+ PROVIDER_NUMBER,
57
+ PROVIDER_SYMBOL_STRING,
58
+ CONTEXT_NUMBER,
59
+ CONTEXT_SYMBOL_STRING,
60
+ STRICT_MODE_NUMBER,
61
+ STRICT_MODE_SYMBOL_STRING,
62
+ PROFILER_NUMBER,
63
+ PROFILER_SYMBOL_STRING,
64
+ SCOPE_NUMBER,
65
+ SCOPE_SYMBOL_STRING,
66
+ FORWARD_REF_NUMBER,
67
+ FORWARD_REF_SYMBOL_STRING,
68
+ MEMO_NUMBER,
69
+ MEMO_SYMBOL_STRING,
70
+} from './ReactSymbols';
71
72
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
73
import type {
@@ -66,6 +85,7 @@ import type {
85
ProfilingDataForRootBackend,
86
ReactRenderer,
87
RendererInterface,
88
+ WorkTagMap,
89
} from './types';
90
import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types';
91
import type {
@@ -76,26 +96,6 @@ import type {
96
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
97
type getTypeSymbolType = (type: any) => Symbol | number;
98
79
-type ReactSymbolsType = {|
80
- CONCURRENT_MODE_NUMBER: number,
81
- CONCURRENT_MODE_SYMBOL_STRING: string,
82
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING: string,
83
- CONTEXT_CONSUMER_NUMBER: number,
84
- CONTEXT_CONSUMER_SYMBOL_STRING: string,
85
- CONTEXT_PROVIDER_NUMBER: number,
86
- CONTEXT_PROVIDER_SYMBOL_STRING: string,
87
- FORWARD_REF_NUMBER: number,
88
- FORWARD_REF_SYMBOL_STRING: string,
89
- MEMO_NUMBER: number,
90
- MEMO_SYMBOL_STRING: string,
91
- PROFILER_NUMBER: number,
92
- PROFILER_SYMBOL_STRING: string,
93
- STRICT_MODE_NUMBER: number,
94
- STRICT_MODE_SYMBOL_STRING: string,
95
- SCOPE_NUMBER: number,
96
- SCOPE_SYMBOL_STRING: string,
97
-|};
98
-
99
type ReactPriorityLevelsType = {|
100
ImmediatePriority: number,
101
UserBlockingPriority: number,
@@ -105,32 +105,6 @@ type ReactPriorityLevelsType = {|
105
NoPriority: number,
106
|};
107
108
-type ReactTypeOfWorkType = {|
109
- ClassComponent: number,
110
- ContextConsumer: number,
111
- ContextProvider: number,
112
- CoroutineComponent: number,
113
- CoroutineHandlerPhase: number,
114
- DehydratedSuspenseComponent: number,
115
- ForwardRef: number,
116
- Fragment: number,
117
- FunctionComponent: number,
118
- HostComponent: number,
119
- HostPortal: number,
120
- HostRoot: number,
121
- HostText: number,
122
- IncompleteClassComponent: number,
123
- IndeterminateComponent: number,
124
- LazyComponent: number,
125
- MemoComponent: number,
126
- Mode: number,
127
- Profiler: number,
128
- SimpleMemoComponent: number,
129
- SuspenseComponent: number,
130
- SuspenseListComponent: number,
131
- YieldComponent: number,
132
-|};
133
-
108
type ReactTypeOfSideEffectType = {|
109
NoEffect: number,
110
PerformedWork: number,
@@ -149,30 +123,9 @@ export function getInternalReactConstants(
123
getDisplayNameForFiber: getDisplayNameForFiberType,
124
getTypeSymbol: getTypeSymbolType,
125
ReactPriorityLevels: ReactPriorityLevelsType,
152
- ReactSymbols: ReactSymbolsType,
126
ReactTypeOfSideEffect: ReactTypeOfSideEffectType,
154
- ReactTypeOfWork: ReactTypeOfWorkType,
127
+ ReactTypeOfWork: WorkTagMap,
128
|} {
156
- const ReactSymbols: ReactSymbolsType = {
157
- CONCURRENT_MODE_NUMBER: 0xeacf,
158
- CONCURRENT_MODE_SYMBOL_STRING: 'Symbol(react.concurrent_mode)',
159
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)',
160
- CONTEXT_CONSUMER_NUMBER: 0xeace,
161
- CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)',
162
- CONTEXT_PROVIDER_NUMBER: 0xeacd,
163
- CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)',
164
- FORWARD_REF_NUMBER: 0xead0,
165
- FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)',
166
- MEMO_NUMBER: 0xead3,
167
- MEMO_SYMBOL_STRING: 'Symbol(react.memo)',
168
- PROFILER_NUMBER: 0xead2,
169
- PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)',
170
- STRICT_MODE_NUMBER: 0xeacc,
171
- STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)',
172
- SCOPE_NUMBER: 0xead7,
173
- SCOPE_SYMBOL_STRING: 'Symbol(react.scope)',
174
- };
175
-
129
const ReactTypeOfSideEffect: ReactTypeOfSideEffectType = {
130
NoEffect: 0b00,
131
PerformedWork: 0b01,
@@ -195,13 +148,14 @@ export function getInternalReactConstants(
148
NoPriority: 90,
149
};
150
198
- let ReactTypeOfWork: ReactTypeOfWorkType = ((null: any): ReactTypeOfWorkType);
151
+ let ReactTypeOfWork: WorkTagMap = ((null: any): WorkTagMap);
152
153
// **********************************************************
154
// The section below is copied from files in React repo.
155
// Keep it in sync, and add version guards if it changes.
156
if (gte(version, '16.6.0-beta.0')) {
157
ReactTypeOfWork = {
158
+ Block: 22,
159
ClassComponent: 1,
160
ContextConsumer: 9,
161
ContextProvider: 10,
@@ -228,6 +182,7 @@ export function getInternalReactConstants(
182
};
183
} else if (gte(version, '16.4.3-alpha')) {
184
ReactTypeOfWork = {
185
+ Block: -1, // Doesn't exist yet
186
ClassComponent: 2,
187
ContextConsumer: 11,
188
ContextProvider: 12,
@@ -254,6 +209,7 @@ export function getInternalReactConstants(
209
};
210
} else {
211
ReactTypeOfWork = {
212
+ Block: -1, // Doesn't exist yet
213
ClassComponent: 2,
214
ContextConsumer: 12,
215
ContextProvider: 13,
@@ -310,26 +266,6 @@ export function getInternalReactConstants(
266
SuspenseListComponent,
267
} = ReactTypeOfWork;
268
313
- const {
314
- CONCURRENT_MODE_NUMBER,
315
- CONCURRENT_MODE_SYMBOL_STRING,
316
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
317
- CONTEXT_PROVIDER_NUMBER,
318
- CONTEXT_PROVIDER_SYMBOL_STRING,
319
- CONTEXT_CONSUMER_NUMBER,
320
- CONTEXT_CONSUMER_SYMBOL_STRING,
321
- STRICT_MODE_NUMBER,
322
- STRICT_MODE_SYMBOL_STRING,
323
- PROFILER_NUMBER,
324
- PROFILER_SYMBOL_STRING,
325
- SCOPE_NUMBER,
326
- SCOPE_SYMBOL_STRING,
327
- FORWARD_REF_NUMBER,
328
- FORWARD_REF_SYMBOL_STRING,
329
- MEMO_NUMBER,
330
- MEMO_SYMBOL_STRING,
331
- } = ReactSymbols;
332
-
269
function resolveFiberType(type: any) {
270
const typeSymbol = getTypeSymbol(type);
271
switch (typeSymbol) {
@@ -392,15 +328,15 @@ export function getInternalReactConstants(
328
case CONCURRENT_MODE_SYMBOL_STRING:
329
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
330
return null;
395
- case CONTEXT_PROVIDER_NUMBER:
396
- case CONTEXT_PROVIDER_SYMBOL_STRING:
331
+ case PROVIDER_NUMBER:
332
+ case PROVIDER_SYMBOL_STRING:
333
// 16.3.0 exposed the context object as "context"
334
// PR #12501 changed it to "_context" for 16.3.1+
335
// NOTE Keep in sync with inspectElementRaw()
336
resolvedContext = fiber.type._context || fiber.type.context;
337
return `${resolvedContext.displayName || 'Context'}.Provider`;
402
- case CONTEXT_CONSUMER_NUMBER:
403
- case CONTEXT_CONSUMER_SYMBOL_STRING:
338
+ case CONTEXT_NUMBER:
339
+ case CONTEXT_SYMBOL_STRING:
340
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
341
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
342
// NOTE Keep in sync with inspectElementRaw()
@@ -431,7 +367,6 @@ export function getInternalReactConstants(
367
getTypeSymbol,
368
ReactPriorityLevels,
369
ReactTypeOfWork,
434
- ReactSymbols,
370
ReactTypeOfSideEffect,
371
};
372
}
@@ -447,7 +382,6 @@ export function attach(
382
getTypeSymbol,
383
ReactPriorityLevels,
384
ReactTypeOfWork,
450
- ReactSymbols,
385
ReactTypeOfSideEffect,
386
} = getInternalReactConstants(renderer.version);
387
const {NoEffect, PerformedWork, Placement} = ReactTypeOfSideEffect;
@@ -477,19 +411,6 @@ export function attach(
411
IdlePriority,
412
NoPriority,
413
} = ReactPriorityLevels;
480
- const {
481
- CONCURRENT_MODE_NUMBER,
482
- CONCURRENT_MODE_SYMBOL_STRING,
483
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
484
- CONTEXT_CONSUMER_NUMBER,
485
- CONTEXT_CONSUMER_SYMBOL_STRING,
486
- CONTEXT_PROVIDER_NUMBER,
487
- CONTEXT_PROVIDER_SYMBOL_STRING,
488
- PROFILER_NUMBER,
489
- PROFILER_SYMBOL_STRING,
490
- STRICT_MODE_NUMBER,
491
- STRICT_MODE_SYMBOL_STRING,
492
- } = ReactSymbols;
414
415
const {
416
overrideHookState,
@@ -731,11 +652,11 @@ export function attach(
652
case CONCURRENT_MODE_SYMBOL_STRING:
653
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
654
return ElementTypeOtherOrUnknown;
734
- case CONTEXT_PROVIDER_NUMBER:
735
- case CONTEXT_PROVIDER_SYMBOL_STRING:
655
+ case PROVIDER_NUMBER:
656
+ case PROVIDER_SYMBOL_STRING:
657
return ElementTypeContext;
737
- case CONTEXT_CONSUMER_NUMBER:
738
- case CONTEXT_CONSUMER_SYMBOL_STRING:
658
+ case CONTEXT_NUMBER:
659
+ case CONTEXT_SYMBOL_STRING:
660
return ElementTypeContext;
661
case STRICT_MODE_NUMBER:
662
case STRICT_MODE_SYMBOL_STRING:
@@ -2262,8 +2183,8 @@ export function attach(
2183
}
2184
}
2185
} else if (
2265
- typeSymbol === CONTEXT_CONSUMER_NUMBER ||
2266
- typeSymbol === CONTEXT_CONSUMER_SYMBOL_STRING
2186
+ typeSymbol === CONTEXT_NUMBER ||
2187
+ typeSymbol === CONTEXT_SYMBOL_STRING
2188
) {
2189
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
2190
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
@@ -2279,8 +2200,8 @@ export function attach(
2200
const currentType = current.type;
2201
const currentTypeSymbol = getTypeSymbol(currentType);
2202
if (
2282
- currentTypeSymbol === CONTEXT_PROVIDER_NUMBER ||
2283
- currentTypeSymbol === CONTEXT_PROVIDER_SYMBOL_STRING
2203
+ currentTypeSymbol === PROVIDER_NUMBER ||
2204
+ currentTypeSymbol === PROVIDER_SYMBOL_STRING
2205
) {
2206
// 16.3.0 exposed the context object as "context"
2207
// PR #12501 changed it to "_context" for 16.3.1+
packages/react-devtools-shared/src/backend/types.js
+29
-1
@@ -25,6 +25,33 @@ export type WorkTag = number;
25
export type SideEffectTag = number;
26
export type ExpirationTime = number;
27
28
+export type WorkTagMap = {|
29
+ Block: WorkTag,
30
+ ClassComponent: WorkTag,
31
+ ContextConsumer: WorkTag,
32
+ ContextProvider: WorkTag,
33
+ CoroutineComponent: WorkTag,
34
+ CoroutineHandlerPhase: WorkTag,
35
+ DehydratedSuspenseComponent: WorkTag,
36
+ ForwardRef: WorkTag,
37
+ Fragment: WorkTag,
38
+ FunctionComponent: WorkTag,
39
+ HostComponent: WorkTag,
40
+ HostPortal: WorkTag,
41
+ HostRoot: WorkTag,
42
+ HostText: WorkTag,
43
+ IncompleteClassComponent: WorkTag,
44
+ IndeterminateComponent: WorkTag,
45
+ LazyComponent: WorkTag,
46
+ MemoComponent: WorkTag,
47
+ Mode: WorkTag,
48
+ Profiler: WorkTag,
49
+ SimpleMemoComponent: WorkTag,
50
+ SuspenseComponent: WorkTag,
51
+ SuspenseListComponent: WorkTag,
52
+ YieldComponent: WorkTag,
53
+|};
54
+
55
// TODO: If it's useful for the frontend to know which types of data an Element has
56
// (e.g. props, state, context, hooks) then we could add a bitmask field for this
57
// to keep the number of attributes small.
@@ -38,6 +65,7 @@ export type NativeType = Object;
65
export type RendererID = number;
66
67
type Dispatcher = any;
68
+export type CurrentDispatcherRef = {|current: null | Dispatcher|};
69
70
export type GetDisplayNameForFiberID = (
71
id: number,
@@ -77,7 +105,7 @@ export type ReactRenderer = {
105
scheduleUpdate?: ?(fiber: Object) => void,
106
setSuspenseHandler?: ?(shouldSuspend: (fiber: Object) => boolean) => void,
107
// Only injected by React v16.8+ in order to support hooks inspection.
80
- currentDispatcherRef?: {|current: null | Dispatcher|},
108
+ currentDispatcherRef?: CurrentDispatcherRef,
109
// Only injected by React v16.9+ in DEV mode.
110
// Enables DevTools to append owners-only component stack to error messages.
111
getCurrentFiber?: () => Fiber | null,
packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
+1
-1
@@ -463,7 +463,7 @@ function InspectedElementView({
463
);
464
}
465
466
-// This function is based on packages/shared/describeComponentFrame.js
466
+// This function is based on describeComponentFrame() in packages/shared/ReactComponentStackFrame
467
function formatSourceForDisplay(fileName: string, lineNumber: string) {
468
const BEFORE_SLASH_RE = /^(.*)[\\\/]/;
469
packages/react-devtools-shell/webpack.config.js
+2
@@ -42,6 +42,8 @@ const config = {
42
plugins: [
43
new DefinePlugin({
44
__DEV__,
45
+ __PROFILE__: false,
46
+ __EXPERIMENTAL__: true,
47
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
48
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
49
}),
packages/shared/ReactSymbols.js
+4
@@ -7,6 +7,10 @@
7
* @flow
8
*/
9
10
+// ATTENTION
11
+// When adding new symbols to this file,
12
+// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
13
+
14
// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
15
// nor polyfill, then a plain number is used for performance.
16
export let REACT_ELEMENT_TYPE = 0xeac7;
scripts/jest/config.build-devtools.js
+5
-7
@@ -26,13 +26,6 @@ const packages = readdirSync(packagesRoot).filter(dir => {
26
// Create a module map to point React packages to the build output
27
const moduleNameMapper = {};
28
29
-// Allow bundle tests to read (but not write!) default feature flags.
30
-// This lets us determine whether we're running in different modes
31
-// without making relevant tests internal-only.
32
-moduleNameMapper[
33
- '^shared/ReactFeatureFlags'
34
-] = `<rootDir>/packages/shared/forks/ReactFeatureFlags.readonly`;
35
-
29
// Map packages to bundles
30
packages.forEach(name => {
31
// Root entry point
@@ -43,6 +36,11 @@ packages.forEach(name => {
36
] = `<rootDir>/build/node_modules/${name}/$1`;
37
});
38
39
+// Allow tests to import shared code (e.g. feature flags, getStackByFiberInDevAndProd)
40
+moduleNameMapper['^shared/([^/]+)$'] = '<rootDir>/packages/shared/$1';
41
+moduleNameMapper['^react-reconciler/([^/]+)$'] =
42
+ '<rootDir>/packages/react-reconciler/$1';
43
+
44
module.exports = Object.assign({}, baseConfig, {
45
// Redirect imports to the compiled bundles
46
moduleNameMapper,