@samitouri / QOS-React / commits / 1566141177

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 {