Strip React elements from serialized data (and display <name/> instead
Brian Vaughn committed
Feb 5, 2019 at 10:25 UTC
15661411778f016c299816e1b03c342f9664c137
5 files changed
+121
-79
src/backend/renderer.js
+3
-3
@@ -11,8 +11,8 @@ import {
11
ElementTypeRoot,
12
ElementTypeSuspense,
13
} from 'src/devtools/types';
14
-import { utfEncodeString } from '../utils';
15
-import { cleanForBridge, cleanPropsForBridge, getDisplayName } from './utils';
14
+import { getDisplayName, utfEncodeString } from '../utils';
15
+import { cleanForBridge } from './utils';
16
import {
17
__DEBUG__,
18
TREE_OPERATION_ADD,
@@ -1110,7 +1110,7 @@ export function attach(
1110
inspectHooksOfFiber(fiber, (renderer.currentDispatcherRef: any))
1111
)
1112
: null,
1113
- props: cleanPropsForBridge(memoizedProps),
1113
+ props: cleanForBridge(memoizedProps),
1114
state: usesHooks ? null : cleanForBridge(memoizedState),
1115
1116
// List of owners
src/backend/utils.js
-69
@@ -1,55 +1,9 @@
1
// @flow
2
3
-import { isElement } from 'react-is';
3
import { dehydrate } from '../hydration';
4
5
import type { DehydratedData } from 'src/devtools/types';
6
8
-const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
9
-const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
10
-
11
-export function getDisplayName(
12
- type: Function,
13
- fallbackName: string = 'Unknown'
14
-): string {
15
- const nameFromCache = cachedDisplayNames.get(type);
16
- if (nameFromCache != null) {
17
- return nameFromCache;
18
- }
19
-
20
- let displayName;
21
-
22
- // The displayName property is not guaranteed to be a string.
23
- // It's only safe to use for our purposes if it's a string.
24
- // github.com/facebook/react-devtools/issues/803
25
- if (typeof type.displayName === 'string') {
26
- displayName = type.displayName;
27
- }
28
-
29
- if (!displayName) {
30
- displayName = type.name || fallbackName;
31
- }
32
-
33
- // Facebook-specific hack to turn "Image [from Image.react]" into just "Image".
34
- // We need displayName with module name for error reports but it clutters the DevTools.
35
- const match = displayName.match(FB_MODULE_RE);
36
- if (match) {
37
- const componentName = match[1];
38
- const moduleName = match[2];
39
- if (componentName && moduleName) {
40
- if (
41
- moduleName === componentName ||
42
- moduleName.startsWith(componentName + '.')
43
- ) {
44
- displayName = componentName;
45
- }
46
- }
47
- }
48
-
49
- cachedDisplayNames.set(type, displayName);
50
- return displayName;
51
-}
52
-
7
export function cleanForBridge(data: Object | null): DehydratedData | null {
8
if (data !== null) {
9
const cleaned = [];
@@ -62,26 +16,3 @@ export function cleanForBridge(data: Object | null): DehydratedData | null {
16
return null;
17
}
18
}
65
-
66
-export function cleanPropsForBridge(props: Object): DehydratedData | null {
67
- if (props !== null) {
68
- const dehydratedData = cleanForBridge(props);
69
- if (dehydratedData !== null) {
70
- // For now, let's just filter children that are React elements.
71
- const { children } = props;
72
- if (children != null) {
73
- if (isElement(children)) {
74
- delete dehydratedData.data.children;
75
- } else if (Array.isArray(children)) {
76
- dehydratedData.data.children = children.filter(
77
- child => !isElement(child)
78
- );
79
- }
80
- }
81
-
82
- return dehydratedData;
83
- }
84
- }
85
-
86
- return null;
87
-}
src/devtools/views/utils.js
+2
@@ -4,6 +4,8 @@ import { meta } from '../../hydration';
4
5
export function getMetaValueLabel(data: Object): string | null {
6
switch (data[meta.type]) {
7
+ case 'react_element':
8
+ return `<${data[meta.name]} />`;
9
case 'function':
10
return `${data[meta.name] || 'fn'}()`;
11
case 'object':
src/hydration.js
+71
-7
@@ -1,5 +1,23 @@
1
// @flow
2
3
+import {
4
+ isElement,
5
+ typeOf,
6
+ AsyncMode,
7
+ ConcurrentMode,
8
+ ContextConsumer,
9
+ ContextProvider,
10
+ ForwardRef,
11
+ Fragment,
12
+ Lazy,
13
+ Memo,
14
+ Portal,
15
+ Profiler,
16
+ StrictMode,
17
+ Suspense,
18
+} from 'react-is';
19
+import { getDisplayName } from './utils';
20
+
21
export const meta = {
22
name: Symbol('name'),
23
type: Symbol('type'),
@@ -27,12 +45,13 @@ function getPropType(data: Object): string | null {
45
if (!data) {
46
return null;
47
}
30
- const type = typeof data;
48
49
+ if (isElement(data)) {
50
+ return 'react_element';
51
+ }
52
+
53
+ const type = typeof data;
54
if (type === 'object') {
33
- if (data._reactFragment) {
34
- return 'react_fragment';
35
- }
55
if (Array.isArray(data)) {
56
return 'array';
57
}
@@ -132,9 +151,14 @@ export function dehydrate(
151
name: data.toString(),
152
};
153
135
- // React Fragments error if you try to inspect them.
136
- case 'react_fragment':
137
- return 'A React Fragment';
154
+ // React Elements aren't very inspector-friendly,
155
+ // and often contain private fields or circular references.
156
+ case 'react_element':
157
+ cleaned.push(path);
158
+ return {
159
+ name: getDisplayNameForReactElement(data),
160
+ type: 'react_element',
161
+ };
162
163
// ArrayBuffers error if you try to inspect them.
164
case 'array_buffer':
@@ -214,3 +238,43 @@ export function hydrate(data: Object, cleaned: Array<Array<string>>): Object {
238
});
239
return data;
240
}
241
+
242
+export function getDisplayNameForReactElement(
243
+ element: React$Element<any>
244
+): string | null {
245
+ const elementType = typeOf(element);
246
+ switch (elementType) {
247
+ case AsyncMode:
248
+ case ConcurrentMode:
249
+ return 'ConcurrentMode';
250
+ case ContextConsumer:
251
+ return 'ContextConsumer';
252
+ case ContextProvider:
253
+ return 'ContextProvider';
254
+ case ForwardRef:
255
+ return 'ForwardRef';
256
+ case Fragment:
257
+ return 'Fragment';
258
+ case Lazy:
259
+ return 'Lazy';
260
+ case Memo:
261
+ return 'Memo';
262
+ case Portal:
263
+ return 'Portal';
264
+ case Profiler:
265
+ return 'Profiler';
266
+ case StrictMode:
267
+ return 'StrictMode';
268
+ case Suspense:
269
+ return 'Suspense';
270
+ default:
271
+ const { type } = element;
272
+ if (typeof type === 'string') {
273
+ return type;
274
+ } else if (type != null) {
275
+ return getDisplayName(type, 'Unknown');
276
+ } else {
277
+ return 'Element';
278
+ }
279
+ }
280
+}
src/utils.js
+45
@@ -1,5 +1,50 @@
1
// @flow
2
3
+const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
4
+const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
5
+
6
+export function getDisplayName(
7
+ type: Function,
8
+ fallbackName: string = 'Unknown'
9
+): string {
10
+ const nameFromCache = cachedDisplayNames.get(type);
11
+ if (nameFromCache != null) {
12
+ return nameFromCache;
13
+ }
14
+
15
+ let displayName;
16
+
17
+ // The displayName property is not guaranteed to be a string.
18
+ // It's only safe to use for our purposes if it's a string.
19
+ // github.com/facebook/react-devtools/issues/803
20
+ if (typeof type.displayName === 'string') {
21
+ displayName = type.displayName;
22
+ }
23
+
24
+ if (!displayName) {
25
+ displayName = type.name || fallbackName;
26
+ }
27
+
28
+ // Facebook-specific hack to turn "Image [from Image.react]" into just "Image".
29
+ // We need displayName with module name for error reports but it clutters the DevTools.
30
+ const match = displayName.match(FB_MODULE_RE);
31
+ if (match) {
32
+ const componentName = match[1];
33
+ const moduleName = match[2];
34
+ if (componentName && moduleName) {
35
+ if (
36
+ moduleName === componentName ||
37
+ moduleName.startsWith(componentName + '.')
38
+ ) {
39
+ displayName = componentName;
40
+ }
41
+ }
42
+ }
43
+
44
+ cachedDisplayNames.set(type, displayName);
45
+ return displayName;
46
+}
47
+
48
let uidCounter: number = 0;
49
50
export function getUID(): number {