@samitouri / QOS-React-2 / commits / 36cab2720a

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,