mirror of
https://github.com/status-im/react-native.git
synced 2025-01-28 02:04:55 +00:00
Add support for dumping current React hierarchy
Reviewed By: sebmarkbage Differential Revision: D3287391 fbshipit-source-id: 0ef1cd25a910dcc0a50ed52a2cc0ae702a9654f4
This commit is contained in:
parent
610cfdc819
commit
f13322c820
@ -90,6 +90,7 @@ var AppRegistry = {
|
|||||||
console.log(msg);
|
console.log(msg);
|
||||||
BugReporting.init();
|
BugReporting.init();
|
||||||
BugReporting.addSource('AppRegistry.runApplication' + runCount++, () => msg);
|
BugReporting.addSource('AppRegistry.runApplication' + runCount++, () => msg);
|
||||||
|
BugReporting.addFileSource('react_hierarchy.txt', () => require('dumpReactTree')());
|
||||||
invariant(
|
invariant(
|
||||||
runnables[appKey] && runnables[appKey].run,
|
runnables[appKey] && runnables[appKey].run,
|
||||||
'Application ' + appKey + ' has not been registered. This ' +
|
'Application ' + appKey + ' has not been registered. This ' +
|
||||||
|
137
Libraries/BugReporting/dumpReactTree.js
Normal file
137
Libraries/BugReporting/dumpReactTree.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule dumpReactTree
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ReactNativeMount = require('ReactNativeMount');
|
||||||
|
const getReactData = require('getReactData');
|
||||||
|
|
||||||
|
const INDENTATION_SIZE = 2;
|
||||||
|
const MAX_DEPTH = 2;
|
||||||
|
const MAX_STRING_LENGTH = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump all React Native root views and their content. This function tries
|
||||||
|
* it best to get the content but ultimately relies on implementation details
|
||||||
|
* of React and will fail in future versions.
|
||||||
|
*/
|
||||||
|
function dumpReactTree() {
|
||||||
|
try {
|
||||||
|
return getReactTree();
|
||||||
|
} catch (e) {
|
||||||
|
return 'Failed to dump react tree: ' + e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReactTree() {
|
||||||
|
let output = '';
|
||||||
|
const rootIds = Object.getOwnPropertyNames(ReactNativeMount._instancesByContainerID);
|
||||||
|
for (const rootId of rootIds) {
|
||||||
|
const instance = ReactNativeMount._instancesByContainerID[rootId];
|
||||||
|
output += `============ Root ID: ${rootId} ============\n`;
|
||||||
|
output += dumpNode(instance, 0);
|
||||||
|
output += `============ End root ID: ${rootId} ============\n`;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dumpNode(node: Object, identation: number) {
|
||||||
|
const data = getReactData(node);
|
||||||
|
if (data.nodeType == 'Text') {
|
||||||
|
return indent(identation) + data.text + '\n';
|
||||||
|
} else if (data.nodeType == 'Empty') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let output = indent(identation) + `<${data.name}`;
|
||||||
|
if (data.nodeType == 'Composite') {
|
||||||
|
for (const propName of Object.getOwnPropertyNames(data.props || {})) {
|
||||||
|
if (isNormalProp(propName)) {
|
||||||
|
const value = convertValue(data.props[propName]);
|
||||||
|
if (value) {
|
||||||
|
output += ` ${propName}=${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let childOutput = '';
|
||||||
|
for (const child of data.children || []) {
|
||||||
|
childOutput += dumpNode(child, identation + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childOutput) {
|
||||||
|
output += '>\n' + childOutput + indent(identation) + `</${data.name}>\n`;;
|
||||||
|
} else {
|
||||||
|
output += ' />\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNormalProp(name: string): boolean {
|
||||||
|
switch (name) {
|
||||||
|
case 'children':
|
||||||
|
case 'key':
|
||||||
|
case 'ref':
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertObject(object: Object, depth: number) {
|
||||||
|
if (depth >= MAX_DEPTH) {
|
||||||
|
return '[...omitted]';
|
||||||
|
}
|
||||||
|
let output = '{'
|
||||||
|
let first = true;
|
||||||
|
for (const key of Object.getOwnPropertyNames(object)) {
|
||||||
|
if (!first) {
|
||||||
|
output += ', '
|
||||||
|
}
|
||||||
|
output += `${key}: ${convertValue(object[key], depth + 1)}`;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return output + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertValue(value, depth = 0): ?string {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'string':
|
||||||
|
return JSON.stringify(possiblyEllipsis(value).replace('\n', '\\n'));
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
return JSON.stringify(value);
|
||||||
|
case 'function':
|
||||||
|
return '[function]';
|
||||||
|
case 'object':
|
||||||
|
return convertObject(value, depth);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function possiblyEllipsis(value: string) {
|
||||||
|
if (value.length > MAX_STRING_LENGTH) {
|
||||||
|
return value.slice(0, MAX_STRING_LENGTH) + '...';
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function indent(size: number) {
|
||||||
|
return ' '.repeat(size * INDENTATION_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = dumpReactTree;
|
168
Libraries/BugReporting/getReactData.js
Normal file
168
Libraries/BugReporting/getReactData.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule getReactData
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a react internal instance to a sanitized data object.
|
||||||
|
*
|
||||||
|
* This is shamelessly stolen from react-devtools:
|
||||||
|
* https://github.com/facebook/react-devtools/blob/master/backend/getData.js
|
||||||
|
*/
|
||||||
|
function getData(element: Object): Object {
|
||||||
|
var children = null;
|
||||||
|
var props = null;
|
||||||
|
var state = null;
|
||||||
|
var context = null;
|
||||||
|
var updater = null;
|
||||||
|
var name = null;
|
||||||
|
var type = null;
|
||||||
|
var text = null;
|
||||||
|
var publicInstance = null;
|
||||||
|
var nodeType = 'Native';
|
||||||
|
// If the parent is a native node without rendered children, but with
|
||||||
|
// multiple string children, then the `element` that gets passed in here is
|
||||||
|
// a plain value -- a string or number.
|
||||||
|
if (typeof element !== 'object') {
|
||||||
|
nodeType = 'Text';
|
||||||
|
text = element + '';
|
||||||
|
} else if (element._currentElement === null || element._currentElement === false) {
|
||||||
|
nodeType = 'Empty';
|
||||||
|
} else if (element._renderedComponent) {
|
||||||
|
nodeType = 'NativeWrapper';
|
||||||
|
children = [element._renderedComponent];
|
||||||
|
props = element._instance.props;
|
||||||
|
state = element._instance.state;
|
||||||
|
context = element._instance.context;
|
||||||
|
if (context && Object.keys(context).length === 0) {
|
||||||
|
context = null;
|
||||||
|
}
|
||||||
|
} else if (element._renderedChildren) {
|
||||||
|
children = childrenList(element._renderedChildren);
|
||||||
|
} else if (element._currentElement && element._currentElement.props) {
|
||||||
|
// This is a native node without rendered children -- meaning the children
|
||||||
|
// prop is just a string or (in the case of the <option>) a list of
|
||||||
|
// strings & numbers.
|
||||||
|
children = element._currentElement.props.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props && element._currentElement && element._currentElement.props) {
|
||||||
|
props = element._currentElement.props;
|
||||||
|
}
|
||||||
|
|
||||||
|
// != used deliberately here to catch undefined and null
|
||||||
|
if (element._currentElement != null) {
|
||||||
|
type = element._currentElement.type;
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
name = type;
|
||||||
|
} else if (element.getName) {
|
||||||
|
nodeType = 'Composite';
|
||||||
|
name = element.getName();
|
||||||
|
// 0.14 top-level wrapper
|
||||||
|
// TODO(jared): The backend should just act as if these don't exist.
|
||||||
|
if (element._renderedComponent && element._currentElement.props === element._renderedComponent._currentElement) {
|
||||||
|
nodeType = 'Wrapper';
|
||||||
|
}
|
||||||
|
if (name === null) {
|
||||||
|
name = 'No display name';
|
||||||
|
}
|
||||||
|
} else if (element._stringText) {
|
||||||
|
nodeType = 'Text';
|
||||||
|
text = element._stringText;
|
||||||
|
} else {
|
||||||
|
name = type.displayName || type.name || 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element._instance) {
|
||||||
|
var inst = element._instance;
|
||||||
|
updater = {
|
||||||
|
setState: inst.setState && inst.setState.bind(inst),
|
||||||
|
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst),
|
||||||
|
setInProps: inst.forceUpdate && setInProps.bind(null, element),
|
||||||
|
setInState: inst.forceUpdate && setInState.bind(null, inst),
|
||||||
|
setInContext: inst.forceUpdate && setInContext.bind(null, inst),
|
||||||
|
};
|
||||||
|
publicInstance = inst;
|
||||||
|
|
||||||
|
// TODO: React ART currently falls in this bucket, but this doesn't
|
||||||
|
// actually make sense and we should clean this up after stabilizing our
|
||||||
|
// API for backends
|
||||||
|
if (inst._renderedChildren) {
|
||||||
|
children = childrenList(inst._renderedChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodeType,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
props,
|
||||||
|
state,
|
||||||
|
context,
|
||||||
|
children,
|
||||||
|
text,
|
||||||
|
updater,
|
||||||
|
publicInstance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInProps(internalInst, path: Array<string | number>, value: any) {
|
||||||
|
var element = internalInst._currentElement;
|
||||||
|
internalInst._currentElement = {
|
||||||
|
...element,
|
||||||
|
props: copyWithSet(element.props, path, value),
|
||||||
|
};
|
||||||
|
internalInst._instance.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInState(inst, path: Array<string | number>, value: any) {
|
||||||
|
setIn(inst.state, path, value);
|
||||||
|
inst.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInContext(inst, path: Array<string | number>, value: any) {
|
||||||
|
setIn(inst.context, path, value);
|
||||||
|
inst.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIn(obj: Object, path: Array<string | number>, value: any) {
|
||||||
|
var last = path.pop();
|
||||||
|
var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj);
|
||||||
|
if (parent) {
|
||||||
|
parent[last] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function childrenList(children) {
|
||||||
|
var res = [];
|
||||||
|
for (var name in children) {
|
||||||
|
res.push(children[name]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyWithSetImpl(obj, path, idx, value) {
|
||||||
|
if (idx >= path.length) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
var key = path[idx];
|
||||||
|
var updated = Array.isArray(obj) ? obj.slice() : {...obj};
|
||||||
|
// $FlowFixMe number or string is fine here
|
||||||
|
updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyWithSet(obj: Object | Array<any>, path: Array<string | number>, value: any): Object | Array<any> {
|
||||||
|
return copyWithSetImpl(obj, path, 0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getData;
|
Loading…
x
Reference in New Issue
Block a user