react-render-hook
Advanced tools
Comparing version 0.1.4 to 1.0.0
@@ -11,1 +11,8 @@ # Changelog | ||
Remove react peer dependencies (thanks Christian Hoffmeister @choffmeister) | ||
## v0.1.4 | ||
Updates and further information in package.json (thanks @valscion) | ||
## v1.0.0 | ||
Update to support fiber (React v16) | ||
Converted to use ES6 Maps with objects as keys (this must be supported by the runtime - Node v4 onwards is fine) |
@@ -1,58 +0,31 @@ | ||
'use strict'; | ||
"use strict"; | ||
var nodes = new Map(); | ||
var publicInstanceToData = new Map(); | ||
var privateInstanceToData = new Map(); | ||
exports.mount = function (component) { | ||
var rootNodeID = component.element._rootNodeID; | ||
var elementsInRoot = nodes.get(rootNodeID); | ||
if (elementsInRoot === undefined) { | ||
elementsInRoot = []; | ||
nodes.set(rootNodeID, elementsInRoot); | ||
} | ||
elementsInRoot.push(component); | ||
if (component.internalInstance.stateNode) { | ||
publicInstanceToData.set(component.internalInstance.stateNode, component); | ||
} | ||
privateInstanceToData.set(component.internalInstance, component); | ||
}; | ||
exports.update = function (component) { | ||
var existing = exports.findInternalComponent(component.element); | ||
if (existing) { | ||
existing.data = component.data; | ||
} | ||
var existing = exports.findInternalComponent(component.internalInstance); | ||
if (existing) { | ||
existing.data = component.data; | ||
} | ||
}; | ||
exports.findComponent = function (component) { | ||
if (component && component._reactInternalInstance) { | ||
var elementsInRoot = nodes.get(component._reactInternalInstance._rootNodeID); | ||
if (elementsInRoot) { | ||
for (var index = elementsInRoot.length - 1; index >= 0; --index) { | ||
if (elementsInRoot[index].data.publicInstance === component) { | ||
var renderedComponent = elementsInRoot[index]; | ||
if (renderedComponent.data.nodeType === 'NativeWrapper') { | ||
return exports.findInternalComponent(renderedComponent.data.children[0]); | ||
} | ||
return renderedComponent; | ||
} | ||
} | ||
} | ||
} | ||
return null; | ||
return publicInstanceToData.get(component) || null; | ||
}; | ||
exports.findInternalComponent = function (internalComponent) { | ||
if (internalComponent) { | ||
var elementsInRoot = nodes.get(internalComponent._rootNodeID); | ||
if (elementsInRoot) { | ||
for (var index = elementsInRoot.length - 1; index >= 0; --index) { | ||
if (elementsInRoot[index].element === internalComponent) { | ||
return elementsInRoot[index]; | ||
} | ||
} | ||
} | ||
} | ||
return privateInstanceToData.get(internalComponent) || null; | ||
}; | ||
exports.clearAll = function () { | ||
nodes.clear(); | ||
publicInstanceToData.clear(); | ||
}; | ||
//# sourceMappingURL=componentMap.js.map |
'use strict'; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
@@ -69,3 +69,2 @@ var Backend = require('./react-devtools/backend/backend'); | ||
exported.findComponent = function (component) { | ||
return ComponentMap.findComponent(component); | ||
@@ -75,3 +74,2 @@ }; | ||
exported.findInternalComponent = function (internalComponent) { | ||
return ComponentMap.findInternalComponent(internalComponent); | ||
@@ -88,3 +86,3 @@ }; | ||
var internalComponent = void 0; | ||
if (component && component.element && component.data) { | ||
if (component && component.data && component.data.publicInstance) { | ||
internalComponent = component; | ||
@@ -91,0 +89,0 @@ } else { |
@@ -15,2 +15,3 @@ /** | ||
var getData012 = require('./getData012'); | ||
var attachRendererFiber = require('./attachRendererFiber'); | ||
@@ -27,2 +28,7 @@ /** | ||
// React Fiber | ||
if (typeof renderer.findFiberByHostInstance === 'function') { | ||
return attachRendererFiber(hook, rid, renderer); | ||
} | ||
// React Native | ||
@@ -40,30 +46,30 @@ if (renderer.Mount.findNodeHandle && renderer.Mount.nativeTagToRootNodeID) { | ||
} else if (renderer.ComponentTree) { | ||
extras.getNativeFromReactElement = function (component) { | ||
return renderer.ComponentTree.getNodeFromInstance(component); | ||
}; | ||
extras.getNativeFromReactElement = function (component) { | ||
return renderer.ComponentTree.getNodeFromInstance(component); | ||
}; | ||
extras.getReactElementFromNative = function (node) { | ||
return renderer.ComponentTree.getClosestInstanceFromNode(node); | ||
}; | ||
// React DOM | ||
} else if (renderer.Mount.getID && renderer.Mount.getNode) { | ||
extras.getNativeFromReactElement = function (component) { | ||
try { | ||
return renderer.Mount.getNode(component._rootNodeID); | ||
} catch (e) { | ||
return undefined; | ||
} | ||
}; | ||
extras.getReactElementFromNative = function (node) { | ||
return renderer.ComponentTree.getClosestInstanceFromNode(node); | ||
}; | ||
// React DOM | ||
} else if (renderer.Mount.getID && renderer.Mount.getNode) { | ||
extras.getNativeFromReactElement = function (component) { | ||
try { | ||
return renderer.Mount.getNode(component._rootNodeID); | ||
} catch (e) { | ||
return undefined; | ||
} | ||
}; | ||
extras.getReactElementFromNative = function (node) { | ||
var id = renderer.Mount.getID(node); | ||
while (node && node.parentNode && !id) { | ||
node = node.parentNode; | ||
id = renderer.Mount.getID(node); | ||
} | ||
return rootNodeIDMap.get(id); | ||
}; | ||
} else { | ||
console.warn('Unknown react version (does not have getID), probably an unshimmed React Native'); | ||
extras.getReactElementFromNative = function (node) { | ||
var id = renderer.Mount.getID(node); | ||
while (node && node.parentNode && !id) { | ||
node = node.parentNode; | ||
id = renderer.Mount.getID(node); | ||
} | ||
return rootNodeIDMap.get(id); | ||
}; | ||
} else { | ||
console.warn('Unknown react version (does not have getID), probably an unshimmed React Native'); | ||
} | ||
@@ -76,11 +82,11 @@ var oldMethods; | ||
if (renderer.Mount._renderNewRootComponent) { | ||
oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', function (element) { | ||
hook.emit('root', { renderer: rid, element: element }); | ||
oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', function (internalInstance) { | ||
hook.emit('root', { renderer: rid, internalInstance: internalInstance }); | ||
}); | ||
// React Native | ||
} else if (renderer.Mount.renderComponent) { | ||
oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', function (element) { | ||
hook.emit('root', { renderer: rid, element: element._reactInternalInstance }); | ||
}); | ||
} | ||
oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', function (internalInstance) { | ||
hook.emit('root', { renderer: rid, internalInstance: internalInstance._reactInternalInstance }); | ||
}); | ||
} | ||
@@ -102,3 +108,3 @@ if (renderer.Component) { | ||
setTimeout(function () { | ||
hook.emit('mount', { element: _this, data: getData012(_this), renderer: rid }); | ||
hook.emit('mount', { internalInstance: _this, data: getData012(_this), renderer: rid }); | ||
}, 0); | ||
@@ -110,7 +116,7 @@ }, | ||
setTimeout(function () { | ||
hook.emit('update', { element: _this2, data: getData012(_this2), renderer: rid }); | ||
hook.emit('update', { internalInstance: _this2, data: getData012(_this2), renderer: rid }); | ||
}, 0); | ||
}, | ||
unmountComponent: function unmountComponent() { | ||
hook.emit('unmount', { element: this, renderer: rid }); | ||
hook.emit('unmount', { internalInstance: this, renderer: rid }); | ||
rootNodeIDMap.delete(this._rootNodeID, this); | ||
@@ -121,16 +127,16 @@ } | ||
oldMethods = decorateMany(renderer.Reconciler, { | ||
mountComponent: function mountComponent(element, rootID, transaction, context) { | ||
var data = getData(element); | ||
rootNodeIDMap.set(element._rootNodeID, element); | ||
hook.emit('mount', { element: element, data: data, renderer: rid }); | ||
mountComponent: function mountComponent(internalInstance, rootID, transaction, context) { | ||
var data = getData(internalInstance); | ||
rootNodeIDMap.set(internalInstance._rootNodeID, internalInstance); | ||
hook.emit('mount', { internalInstance: internalInstance, data: data, renderer: rid }); | ||
}, | ||
performUpdateIfNecessary: function performUpdateIfNecessary(element, nextChild, transaction, context) { | ||
hook.emit('update', { element: element, data: getData(element), renderer: rid }); | ||
performUpdateIfNecessary: function performUpdateIfNecessary(internalInstance, nextChild, transaction, context) { | ||
hook.emit('update', { internalInstance: internalInstance, data: getData(internalInstance), renderer: rid }); | ||
}, | ||
receiveComponent: function receiveComponent(element, nextChild, transaction, context) { | ||
hook.emit('update', { element: element, data: getData(element), renderer: rid }); | ||
receiveComponent: function receiveComponent(internalInstance, nextChild, transaction, context) { | ||
hook.emit('update', { internalInstance: internalInstance, data: getData(internalInstance), renderer: rid }); | ||
}, | ||
unmountComponent: function unmountComponent(element) { | ||
hook.emit('unmount', { element: element, renderer: rid }); | ||
rootNodeIDMap.delete(element._rootNodeID, element); | ||
unmountComponent: function unmountComponent(internalInstance) { | ||
hook.emit('unmount', { internalInstance: internalInstance, renderer: rid }); | ||
rootNodeIDMap.delete(internalInstance._rootNodeID, internalInstance); | ||
} | ||
@@ -177,4 +183,4 @@ }); | ||
function walkNode(element, onMount, isPre013) { | ||
var data = isPre013 ? getData012(element) : getData(element); | ||
function walkNode(internalInstance, onMount, isPre013) { | ||
var data = isPre013 ? getData012(internalInstance) : getData(internalInstance); | ||
if (data.children && Array.isArray(data.children)) { | ||
@@ -185,3 +191,3 @@ data.children.forEach(function (child) { | ||
} | ||
onMount(element, data); | ||
onMount(internalInstance, data); | ||
} | ||
@@ -188,0 +194,0 @@ |
@@ -17,3 +17,3 @@ /** | ||
* and whatever else is needed. | ||
* 4. The agend then calls `.emit('react-devtools', agent)` | ||
* 4. The agent then calls `.emit('react-devtools', agent)` | ||
* | ||
@@ -41,4 +41,4 @@ * Now things are hooked up. | ||
hook.on('renderer', function (_ref) { | ||
var id = _ref.id; | ||
var renderer = _ref.renderer; | ||
var id = _ref.id, | ||
renderer = _ref.renderer; | ||
@@ -45,0 +45,0 @@ hook.helpers[id] = attachRenderer(hook, id, renderer); |
@@ -15,5 +15,6 @@ /** | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
var copyWithSet = require('./copyWithSet'); | ||
var getDisplayName = require('./getDisplayName'); | ||
@@ -23,3 +24,3 @@ /** | ||
*/ | ||
function getData(element) { | ||
function getData(internalInstance) { | ||
var children = null; | ||
@@ -34,2 +35,3 @@ var props = null; | ||
var ref = null; | ||
var source = null; | ||
var text = null; | ||
@@ -41,44 +43,51 @@ var publicInstance = null; | ||
// a plain value -- a string or number. | ||
if ((typeof element === 'undefined' ? 'undefined' : _typeof(element)) !== 'object') { | ||
if ((typeof internalInstance === 'undefined' ? 'undefined' : _typeof(internalInstance)) !== 'object') { | ||
nodeType = 'Text'; | ||
text = element + ''; | ||
} else if (element._currentElement === null || element._currentElement === false) { | ||
text = internalInstance + ''; | ||
} else if (internalInstance._currentElement === null || internalInstance._currentElement === false) { | ||
nodeType = 'Empty'; | ||
} else if (element._renderedComponent) { | ||
} else if (internalInstance._renderedComponent) { | ||
nodeType = 'NativeWrapper'; | ||
children = [element._renderedComponent]; | ||
props = element._instance.props; | ||
state = element._instance.state; | ||
context = element._instance.context; | ||
children = [internalInstance._renderedComponent]; | ||
props = internalInstance._instance.props; | ||
state = internalInstance._instance.state; | ||
context = internalInstance._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) { | ||
} else if (internalInstance._renderedChildren) { | ||
children = childrenList(internalInstance._renderedChildren); | ||
} else if (internalInstance._currentElement && internalInstance._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; | ||
children = internalInstance._currentElement.props.children; | ||
} | ||
if (!props && element._currentElement && element._currentElement.props) { | ||
props = element._currentElement.props; | ||
if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { | ||
props = internalInstance._currentElement.props; | ||
} | ||
// != used deliberately here to catch undefined and null | ||
if (element._currentElement != null) { | ||
type = element._currentElement.type; | ||
if (element._currentElement.key) { | ||
key = String(element._currentElement.key); | ||
if (internalInstance._currentElement != null) { | ||
type = internalInstance._currentElement.type; | ||
if (internalInstance._currentElement.key) { | ||
key = String(internalInstance._currentElement.key); | ||
} | ||
ref = element._currentElement.ref; | ||
source = internalInstance._currentElement._source; | ||
ref = internalInstance._currentElement.ref; | ||
if (typeof type === 'string') { | ||
name = type; | ||
} else if (element.getName) { | ||
if (internalInstance._nativeNode != null) { | ||
publicInstance = internalInstance._nativeNode; | ||
} | ||
if (internalInstance._hostNode != null) { | ||
publicInstance = internalInstance._hostNode; | ||
} | ||
} else if (typeof type === 'function') { | ||
nodeType = 'Composite'; | ||
name = element.getName(); | ||
name = getDisplayName(type); | ||
// 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) { | ||
if (internalInstance._renderedComponent && (internalInstance._currentElement.props === internalInstance._renderedComponent._currentElement || internalInstance._currentElement.type.isReactTopLevelWrapper)) { | ||
nodeType = 'Wrapper'; | ||
@@ -89,20 +98,22 @@ } | ||
} | ||
} else if (typeof element._stringText === 'string') { | ||
} else if (typeof internalInstance._stringText === 'string') { | ||
nodeType = 'Text'; | ||
text = element._stringText; | ||
text = internalInstance._stringText; | ||
} else { | ||
name = type && (type.displayName || type.name) || 'Unknown'; | ||
name = getDisplayName(type); | ||
} | ||
} | ||
if (element._instance) { | ||
var inst = element._instance; | ||
if (internalInstance._instance) { | ||
var inst = internalInstance._instance; | ||
updater = { | ||
setState: inst.setState && inst.setState.bind(inst), | ||
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), | ||
setInProps: inst.forceUpdate && setInProps.bind(null, element), | ||
setInProps: inst.forceUpdate && setInProps.bind(null, internalInstance), | ||
setInState: inst.forceUpdate && setInState.bind(null, inst), | ||
setInContext: inst.forceUpdate && setInContext.bind(null, inst) | ||
}; | ||
publicInstance = inst; | ||
if (typeof type === 'function') { | ||
publicInstance = inst; | ||
} | ||
@@ -117,2 +128,11 @@ // TODO: React ART currently falls in this bucket, but this doesn't | ||
if (typeof internalInstance.setNativeProps === 'function') { | ||
// For editing styles in RN | ||
updater = { | ||
setNativeProps: function setNativeProps(nativeProps) { | ||
internalInstance.setNativeProps(nativeProps); | ||
} | ||
}; | ||
} | ||
return { | ||
@@ -123,2 +143,3 @@ nodeType: nodeType, | ||
ref: ref, | ||
source: source, | ||
name: name, | ||
@@ -125,0 +146,0 @@ props: props, |
@@ -15,7 +15,7 @@ /** | ||
function getData012(element) { | ||
function getData012(internalInstance) { | ||
var children = null; | ||
var props = element.props; | ||
var state = element.state; | ||
var context = element.context; | ||
var props = internalInstance.props; | ||
var state = internalInstance.state; | ||
var context = internalInstance.context; | ||
var updater = null; | ||
@@ -29,14 +29,14 @@ var name = null; | ||
var nodeType = 'Native'; | ||
if (element._renderedComponent) { | ||
if (internalInstance._renderedComponent) { | ||
nodeType = 'Wrapper'; | ||
children = [element._renderedComponent]; | ||
children = [internalInstance._renderedComponent]; | ||
if (context && Object.keys(context).length === 0) { | ||
context = null; | ||
} | ||
} else if (element._renderedChildren) { | ||
name = element.constructor.displayName; | ||
children = childrenList(element._renderedChildren); | ||
} else if (internalInstance._renderedChildren) { | ||
name = internalInstance.constructor.displayName; | ||
children = childrenList(internalInstance._renderedChildren); | ||
} else if (typeof props.children === 'string') { | ||
// string children | ||
name = element.constructor.displayName; | ||
name = internalInstance.constructor.displayName; | ||
children = props.children; | ||
@@ -46,12 +46,12 @@ nodeType = 'Native'; | ||
if (!props && element._currentElement && element._currentElement.props) { | ||
props = element._currentElement.props; | ||
if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { | ||
props = internalInstance._currentElement.props; | ||
} | ||
if (element._currentElement) { | ||
type = element._currentElement.type; | ||
if (element._currentElement.key) { | ||
key = String(element._currentElement.key); | ||
if (internalInstance._currentElement) { | ||
type = internalInstance._currentElement.type; | ||
if (internalInstance._currentElement.key) { | ||
key = String(internalInstance._currentElement.key); | ||
} | ||
ref = element._currentElement.ref; | ||
ref = internalInstance._currentElement.ref; | ||
if (typeof type === 'string') { | ||
@@ -69,3 +69,3 @@ name = type; | ||
if (!name) { | ||
name = element.constructor.displayName || 'No display name'; | ||
name = internalInstance.constructor.displayName || 'No display name'; | ||
nodeType = 'Composite'; | ||
@@ -81,11 +81,11 @@ } | ||
if (element.forceUpdate) { | ||
if (internalInstance.forceUpdate) { | ||
updater = { | ||
setState: element.setState.bind(element), | ||
forceUpdate: element.forceUpdate.bind(element), | ||
setInProps: element.forceUpdate && setInProps.bind(null, element), | ||
setInState: element.forceUpdate && setInState.bind(null, element), | ||
setInContext: element.forceUpdate && setInContext.bind(null, element) | ||
setState: internalInstance.setState.bind(internalInstance), | ||
forceUpdate: internalInstance.forceUpdate.bind(internalInstance), | ||
setInProps: internalInstance.forceUpdate && setInProps.bind(null, internalInstance), | ||
setInState: internalInstance.forceUpdate && setInState.bind(null, internalInstance), | ||
setInContext: internalInstance.forceUpdate && setInContext.bind(null, internalInstance) | ||
}; | ||
publicInstance = element; | ||
publicInstance = internalInstance; | ||
} | ||
@@ -98,2 +98,3 @@ | ||
ref: ref, | ||
source: null, | ||
name: name, | ||
@@ -100,0 +101,0 @@ props: props, |
@@ -21,46 +21,199 @@ /** | ||
} | ||
Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { | ||
value: { | ||
_renderers: {}, | ||
helpers: {}, | ||
inject: function inject(renderer) { | ||
var id = Math.random().toString(16).slice(2); | ||
this._renderers[id] = renderer; | ||
this.emit('renderer', { id: id, renderer: renderer }); | ||
}, | ||
_listeners: {}, | ||
sub: function sub(evt, fn) { | ||
var _this = this; | ||
function detectReactBuildType(renderer) { | ||
try { | ||
var toString = Function.prototype.toString; | ||
if (typeof renderer.version === 'string') { | ||
// React DOM Fiber (16+) | ||
if (renderer.bundleType > 0) { | ||
// This is not a production build. | ||
// We are currently only using 0 (PROD) and 1 (DEV) | ||
// but might add 2 (PROFILE) in the future. | ||
return 'development'; | ||
} | ||
// The above should cover envification, but we should still make sure | ||
// that the bundle code has been uglified. | ||
var findFiberCode = toString.call(renderer.findFiberByHostInstance); | ||
// Filter out bad results (if that is even possible): | ||
if (findFiberCode.indexOf('function') !== 0) { | ||
// Hope for the best if we're not sure. | ||
return 'production'; | ||
} | ||
this.on(evt, fn); | ||
return function () { | ||
return _this.off(evt, fn); | ||
}; | ||
}, | ||
on: function on(evt, fn) { | ||
if (!this._listeners[evt]) { | ||
this._listeners[evt] = []; | ||
// By now we know that it's envified--but what if it's not minified? | ||
// This can be bad too, as it means DEV code is still there. | ||
// FIXME: this is fragile! | ||
// We should replace this check with check on a specially passed | ||
// function. This also doesn't detect lack of dead code elimination | ||
// (although this is not a problem since flat bundles). | ||
if (findFiberCode.indexOf('getClosestInstanceFromNode') !== -1) { | ||
return 'unminified'; | ||
} | ||
this._listeners[evt].push(fn); | ||
}, | ||
off: function off(evt, fn) { | ||
if (!this._listeners[evt]) { | ||
return; | ||
// We're good. | ||
return 'production'; | ||
} | ||
if (renderer.Mount && renderer.Mount._renderNewRootComponent) { | ||
// React DOM Stack | ||
var renderRootCode = toString.call(renderer.Mount._renderNewRootComponent); | ||
// Filter out bad results (if that is even possible): | ||
if (renderRootCode.indexOf('function') !== 0) { | ||
// Hope for the best if we're not sure. | ||
return 'production'; | ||
} | ||
var ix = this._listeners[evt].indexOf(fn); | ||
if (ix !== -1) { | ||
this._listeners[evt].splice(ix, 1); | ||
// Check for React DOM Stack < 15.1.0 in development. | ||
// If it contains "storedMeasure" call, it's wrapped in ReactPerf (DEV only). | ||
// This would be true even if it's minified, as method name still matches. | ||
if (renderRootCode.indexOf('storedMeasure') !== -1) { | ||
return 'development'; | ||
} | ||
if (!this._listeners[evt].length) { | ||
this._listeners[evt] = null; | ||
// For other versions (and configurations) it's not so easy. | ||
// Let's quickly exclude proper production builds. | ||
// If it contains a warning message, it's either a DEV build, | ||
// or an PROD build without proper dead code elimination. | ||
if (renderRootCode.indexOf('should be a pure function') !== -1) { | ||
// Now how do we tell a DEV build from a bad PROD build? | ||
// If we see NODE_ENV, we're going to assume this is a dev build | ||
// because most likely it is referring to an empty shim. | ||
if (renderRootCode.indexOf('NODE_ENV') !== -1) { | ||
return 'development'; | ||
} | ||
// If we see "development", we're dealing with an envified DEV build | ||
// (such as the official React DEV UMD). | ||
if (renderRootCode.indexOf('development') !== -1) { | ||
return 'development'; | ||
} | ||
// I've seen process.env.NODE_ENV !== 'production' being smartly | ||
// replaced by `true` in DEV by Webpack. I don't know how that | ||
// works but we can safely guard against it because `true` was | ||
// never used in the function source since it was written. | ||
if (renderRootCode.indexOf('true') !== -1) { | ||
return 'development'; | ||
} | ||
// By now either it is a production build that has not been minified, | ||
// or (worse) this is a minified development build using non-standard | ||
// environment (e.g. "staging"). We're going to look at whether | ||
// the function argument name is mangled: | ||
if ( | ||
// 0.13 to 15 | ||
renderRootCode.indexOf('nextElement') !== -1 || | ||
// 0.12 | ||
renderRootCode.indexOf('nextComponent') !== -1) { | ||
// We can't be certain whether this is a development build or not, | ||
// but it is definitely unminified. | ||
return 'unminified'; | ||
} else { | ||
// This is likely a minified development build. | ||
return 'development'; | ||
} | ||
} | ||
}, | ||
emit: function emit(evt, data) { | ||
if (this._listeners[evt]) { | ||
this._listeners[evt].map(function (fn) { | ||
return fn(data); | ||
}); | ||
// By now we know that it's envified and dead code elimination worked, | ||
// but what if it's still not minified? (Is this even possible?) | ||
// Let's check matches for the first argument name. | ||
if ( | ||
// 0.13 to 15 | ||
renderRootCode.indexOf('nextElement') !== -1 || | ||
// 0.12 | ||
renderRootCode.indexOf('nextComponent') !== -1) { | ||
return 'unminified'; | ||
} | ||
// Seems like we're using the production version. | ||
// Now let's check if we're still on 0.14 or lower: | ||
if (renderRootCode.indexOf('._registerComponent') !== -1) { | ||
// TODO: we can remove the condition above once 16 | ||
// is older than a year. Since this branch only runs | ||
// for Stack, we can flip it completely when Stack | ||
// is old enough. The branch for Fiber is above, | ||
// and it can check renderer.version directly. | ||
return 'outdated'; | ||
} | ||
// We're all good. | ||
return 'production'; | ||
} | ||
} catch (err) { | ||
// Weird environments may exist. | ||
// This code needs a higher fault tolerance | ||
// because it runs even with closed DevTools. | ||
// TODO: should we catch errors in all injected code, and not just this part? | ||
} | ||
return 'production'; | ||
} | ||
var hook = { | ||
// Shared between Stack and Fiber: | ||
_renderers: {}, | ||
helpers: {}, | ||
inject: function inject(renderer) { | ||
var id = Math.random().toString(16).slice(2); | ||
hook._renderers[id] = renderer; | ||
var reactBuildType = detectReactBuildType(renderer); | ||
hook.emit('renderer', { id: id, renderer: renderer, reactBuildType: reactBuildType }); | ||
return id; | ||
}, | ||
_listeners: {}, | ||
sub: function sub(evt, fn) { | ||
hook.on(evt, fn); | ||
return function () { | ||
return hook.off(evt, fn); | ||
}; | ||
}, | ||
on: function on(evt, fn) { | ||
if (!hook._listeners[evt]) { | ||
hook._listeners[evt] = []; | ||
} | ||
hook._listeners[evt].push(fn); | ||
}, | ||
off: function off(evt, fn) { | ||
if (!hook._listeners[evt]) { | ||
return; | ||
} | ||
var ix = hook._listeners[evt].indexOf(fn); | ||
if (ix !== -1) { | ||
hook._listeners[evt].splice(ix, 1); | ||
} | ||
if (!hook._listeners[evt].length) { | ||
hook._listeners[evt] = null; | ||
} | ||
}, | ||
emit: function emit(evt, data) { | ||
if (hook._listeners[evt]) { | ||
hook._listeners[evt].map(function (fn) { | ||
return fn(data); | ||
}); | ||
} | ||
}, | ||
// Fiber-only: | ||
supportsFiber: true, | ||
_fiberRoots: {}, | ||
getFiberRoots: function getFiberRoots(rendererID) { | ||
var roots = hook._fiberRoots; | ||
if (!roots[rendererID]) { | ||
roots[rendererID] = new Set(); | ||
} | ||
return roots[rendererID]; | ||
}, | ||
onCommitFiberUnmount: function onCommitFiberUnmount(rendererID, fiber) { | ||
// TODO: can we use hook for roots too? | ||
if (hook.helpers[rendererID]) { | ||
hook.helpers[rendererID].handleCommitFiberUnmount(fiber); | ||
} | ||
}, | ||
onCommitFiberRoot: function onCommitFiberRoot(rendererID, root) { | ||
var mountedRoots = hook.getFiberRoots(rendererID); | ||
var current = root.current; | ||
var isKnownRoot = mountedRoots.has(root); | ||
var isUnmounting = current.memoizedState == null || current.memoizedState.element == null; | ||
// Keep track of mounted roots so we can hydrate when DevTools connect. | ||
if (!isKnownRoot && !isUnmounting) { | ||
mountedRoots.add(root); | ||
} else if (isKnownRoot && isUnmounting) { | ||
mountedRoots.delete(root); | ||
} | ||
if (hook.helpers[rendererID]) { | ||
hook.helpers[rendererID].handleCommitFiberRoot(root); | ||
} | ||
} | ||
}; | ||
Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { | ||
value: hook | ||
}); | ||
@@ -67,0 +220,0 @@ } |
{ | ||
"name": "react-render-hook", | ||
"version": "0.1.4", | ||
"version": "1.0.0", | ||
"description": "Hooks to React.js rendering, to enable access to the virtual DOM (as shown in React devtools)", | ||
@@ -24,3 +24,3 @@ "scripts": { | ||
"babel-cli": "^6.7.5", | ||
"babel-core": "^6.7.6", | ||
"babel-core": "^6.25.0", | ||
"babel-preset-es2015": "^6.6.0", | ||
@@ -31,3 +31,5 @@ "babel-preset-react": "^6.5.0", | ||
"babel-register": "^6.7.2", | ||
"jsdom": "^6.5.1", | ||
"create-react-class": "^15.6.0", | ||
"jsdom": "^11.1.0", | ||
"jsdom-global": "^3.0.2", | ||
"magicpen": "^5.4.0", | ||
@@ -34,0 +36,0 @@ "mocha": "^2.3.3", |
const nodes = new Map(); | ||
const publicInstanceToData = new Map(); | ||
const privateInstanceToData = new Map(); | ||
exports.mount = function (component) { | ||
const rootNodeID = component.element._rootNodeID; | ||
let elementsInRoot = nodes.get(rootNodeID); | ||
if (elementsInRoot === undefined) { | ||
elementsInRoot = []; | ||
nodes.set(rootNodeID, elementsInRoot); | ||
} | ||
elementsInRoot.push(component); | ||
if (component.internalInstance.stateNode) { | ||
publicInstanceToData.set(component.internalInstance.stateNode, component) | ||
} | ||
privateInstanceToData.set(component.internalInstance, component); | ||
}; | ||
exports.update = function (component) { | ||
const existing = exports.findInternalComponent(component.element); | ||
const existing = exports.findInternalComponent(component.internalInstance); | ||
if (existing) { | ||
@@ -23,36 +20,11 @@ existing.data = component.data; | ||
exports.findComponent = function (component) { | ||
if (component && component._reactInternalInstance) { | ||
const elementsInRoot = nodes.get(component._reactInternalInstance._rootNodeID); | ||
if (elementsInRoot) { | ||
for (let index = elementsInRoot.length - 1; index >= 0; --index) { | ||
if (elementsInRoot[index].data.publicInstance === component) { | ||
const renderedComponent = elementsInRoot[index]; | ||
if (renderedComponent.data.nodeType === 'NativeWrapper') { | ||
return exports.findInternalComponent(renderedComponent.data.children[0]); | ||
} | ||
return renderedComponent; | ||
} | ||
} | ||
} | ||
} | ||
return null; | ||
return publicInstanceToData.get(component) || null; | ||
}; | ||
exports.findInternalComponent = function (internalComponent) { | ||
if (internalComponent) { | ||
const elementsInRoot = nodes.get(internalComponent._rootNodeID); | ||
if (elementsInRoot) { | ||
for (let index = elementsInRoot.length - 1; index >= 0; --index) { | ||
if (elementsInRoot[index].element === internalComponent) { | ||
return elementsInRoot[index]; | ||
} | ||
} | ||
} | ||
} | ||
return privateInstanceToData.get(internalComponent) || null; | ||
}; | ||
exports.clearAll = function () { | ||
nodes.clear(); | ||
publicInstanceToData.clear(); | ||
}; |
@@ -68,3 +68,2 @@ const Backend = require('./react-devtools/backend/backend'); | ||
exported.findComponent = function (component) { | ||
return ComponentMap.findComponent(component); | ||
@@ -74,3 +73,2 @@ }; | ||
exported.findInternalComponent = function (internalComponent) { | ||
return ComponentMap.findInternalComponent(internalComponent); | ||
@@ -92,3 +90,3 @@ }; | ||
let internalComponent; | ||
if (component && component.element && component.data) { | ||
if (component && component.data && component.data.publicInstance) { | ||
internalComponent = component; | ||
@@ -95,0 +93,0 @@ } else { |
@@ -16,2 +16,3 @@ /** | ||
var getData012 = require('./getData012'); | ||
var attachRendererFiber = require('./attachRendererFiber'); | ||
@@ -30,2 +31,7 @@ type NodeLike = {}; | ||
// React Fiber | ||
if (typeof renderer.findFiberByHostInstance === 'function') { | ||
return attachRendererFiber(hook, rid, renderer); | ||
} | ||
// React Native | ||
@@ -78,9 +84,9 @@ if (renderer.Mount.findNodeHandle && renderer.Mount.nativeTagToRootNodeID) { | ||
if (renderer.Mount._renderNewRootComponent) { | ||
oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', (element) => { | ||
hook.emit('root', {renderer: rid, element}); | ||
oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', (internalInstance) => { | ||
hook.emit('root', {renderer: rid, internalInstance}); | ||
}); | ||
// React Native | ||
} else if (renderer.Mount.renderComponent) { | ||
oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', element => { | ||
hook.emit('root', {renderer: rid, element: element._reactInternalInstance}); | ||
oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', internalInstance => { | ||
hook.emit('root', {renderer: rid, internalInstance: internalInstance._reactInternalInstance}); | ||
}); | ||
@@ -102,3 +108,3 @@ } | ||
setTimeout(() => { | ||
hook.emit('mount', {element: this, data: getData012(this), renderer: rid}); | ||
hook.emit('mount', {internalInstance: this, data: getData012(this), renderer: rid}); | ||
}, 0); | ||
@@ -108,7 +114,7 @@ }, | ||
setTimeout(() => { | ||
hook.emit('update', {element: this, data: getData012(this), renderer: rid}); | ||
hook.emit('update', {internalInstance: this, data: getData012(this), renderer: rid}); | ||
}, 0); | ||
}, | ||
unmountComponent() { | ||
hook.emit('unmount', {element: this, renderer: rid}); | ||
hook.emit('unmount', {internalInstance: this, renderer: rid}); | ||
rootNodeIDMap.delete(this._rootNodeID, this); | ||
@@ -119,16 +125,16 @@ }, | ||
oldMethods = decorateMany(renderer.Reconciler, { | ||
mountComponent(element, rootID, transaction, context) { | ||
var data = getData(element); | ||
rootNodeIDMap.set(element._rootNodeID, element); | ||
hook.emit('mount', {element, data, renderer: rid}); | ||
mountComponent(internalInstance, rootID, transaction, context) { | ||
var data = getData(internalInstance); | ||
rootNodeIDMap.set(internalInstance._rootNodeID, internalInstance); | ||
hook.emit('mount', {internalInstance, data, renderer: rid}); | ||
}, | ||
performUpdateIfNecessary(element, nextChild, transaction, context) { | ||
hook.emit('update', {element, data: getData(element), renderer: rid}); | ||
performUpdateIfNecessary(internalInstance, nextChild, transaction, context) { | ||
hook.emit('update', {internalInstance, data: getData(internalInstance), renderer: rid}); | ||
}, | ||
receiveComponent(element, nextChild, transaction, context) { | ||
hook.emit('update', {element, data: getData(element), renderer: rid}); | ||
receiveComponent(internalInstance, nextChild, transaction, context) { | ||
hook.emit('update', {internalInstance, data: getData(internalInstance), renderer: rid}); | ||
}, | ||
unmountComponent(element) { | ||
hook.emit('unmount', {element, renderer: rid}); | ||
rootNodeIDMap.delete(element._rootNodeID, element); | ||
unmountComponent(internalInstance) { | ||
hook.emit('unmount', {internalInstance, renderer: rid}); | ||
rootNodeIDMap.delete(internalInstance._rootNodeID, internalInstance); | ||
}, | ||
@@ -138,3 +144,3 @@ }); | ||
extras.walkTree = function(visit: (component: OpaqueNodeHandle, data: DataType) => void, visitRoot: (element: OpaqueNodeHandle) => void) { | ||
extras.walkTree = function(visit: (component: OpaqueNodeHandle, data: DataType) => void, visitRoot: (internalInstance: OpaqueNodeHandle) => void) { | ||
var onMount = (component, data) => { | ||
@@ -176,8 +182,8 @@ rootNodeIDMap.set(component._rootNodeID, component); | ||
function walkNode(element, onMount, isPre013) { | ||
var data = isPre013 ? getData012(element) : getData(element); | ||
function walkNode(internalInstance, onMount, isPre013) { | ||
var data = isPre013 ? getData012(internalInstance) : getData(internalInstance); | ||
if (data.children && Array.isArray(data.children)) { | ||
data.children.forEach(child => walkNode(child, onMount, isPre013)); | ||
} | ||
onMount(element, data); | ||
onMount(internalInstance, data); | ||
} | ||
@@ -184,0 +190,0 @@ |
@@ -17,3 +17,3 @@ /** | ||
* and whatever else is needed. | ||
* 4. The agend then calls `.emit('react-devtools', agent)` | ||
* 4. The agent then calls `.emit('react-devtools', agent)` | ||
* | ||
@@ -20,0 +20,0 @@ * Now things are hooked up. |
@@ -15,2 +15,3 @@ /** | ||
var copyWithSet = require('./copyWithSet'); | ||
var getDisplayName = require('./getDisplayName'); | ||
@@ -20,3 +21,3 @@ /** | ||
*/ | ||
function getData(element: Object): DataType { | ||
function getData(internalInstance: Object): DataType { | ||
var children = null; | ||
@@ -31,2 +32,3 @@ var props = null; | ||
var ref = null; | ||
var source = null; | ||
var text = null; | ||
@@ -38,44 +40,54 @@ var publicInstance = null; | ||
// a plain value -- a string or number. | ||
if (typeof element !== 'object') { | ||
if (typeof internalInstance !== 'object') { | ||
nodeType = 'Text'; | ||
text = element + ''; | ||
} else if (element._currentElement === null || element._currentElement === false) { | ||
text = internalInstance + ''; | ||
} else if (internalInstance._currentElement === null || internalInstance._currentElement === false) { | ||
nodeType = 'Empty'; | ||
} else if (element._renderedComponent) { | ||
} else if (internalInstance._renderedComponent) { | ||
nodeType = 'NativeWrapper'; | ||
children = [element._renderedComponent]; | ||
props = element._instance.props; | ||
state = element._instance.state; | ||
context = element._instance.context; | ||
children = [internalInstance._renderedComponent]; | ||
props = internalInstance._instance.props; | ||
state = internalInstance._instance.state; | ||
context = internalInstance._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) { | ||
} else if (internalInstance._renderedChildren) { | ||
children = childrenList(internalInstance._renderedChildren); | ||
} else if (internalInstance._currentElement && internalInstance._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; | ||
children = internalInstance._currentElement.props.children; | ||
} | ||
if (!props && element._currentElement && element._currentElement.props) { | ||
props = element._currentElement.props; | ||
if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { | ||
props = internalInstance._currentElement.props; | ||
} | ||
// != used deliberately here to catch undefined and null | ||
if (element._currentElement != null) { | ||
type = element._currentElement.type; | ||
if (element._currentElement.key) { | ||
key = String(element._currentElement.key); | ||
if (internalInstance._currentElement != null) { | ||
type = internalInstance._currentElement.type; | ||
if (internalInstance._currentElement.key) { | ||
key = String(internalInstance._currentElement.key); | ||
} | ||
ref = element._currentElement.ref; | ||
source = internalInstance._currentElement._source; | ||
ref = internalInstance._currentElement.ref; | ||
if (typeof type === 'string') { | ||
name = type; | ||
} else if (element.getName) { | ||
if (internalInstance._nativeNode != null) { | ||
publicInstance = internalInstance._nativeNode; | ||
} | ||
if (internalInstance._hostNode != null) { | ||
publicInstance = internalInstance._hostNode; | ||
} | ||
} else if (typeof type === 'function') { | ||
nodeType = 'Composite'; | ||
name = element.getName(); | ||
name = getDisplayName(type); | ||
// 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) { | ||
if (internalInstance._renderedComponent && ( | ||
internalInstance._currentElement.props === internalInstance._renderedComponent._currentElement || | ||
internalInstance._currentElement.type.isReactTopLevelWrapper | ||
)) { | ||
nodeType = 'Wrapper'; | ||
@@ -86,20 +98,22 @@ } | ||
} | ||
} else if (typeof element._stringText === 'string') { | ||
} else if (typeof internalInstance._stringText === 'string') { | ||
nodeType = 'Text'; | ||
text = element._stringText; | ||
text = internalInstance._stringText; | ||
} else { | ||
name = type && (type.displayName || type.name) || 'Unknown'; | ||
name = getDisplayName(type); | ||
} | ||
} | ||
if (element._instance) { | ||
var inst = element._instance; | ||
if (internalInstance._instance) { | ||
var inst = internalInstance._instance; | ||
updater = { | ||
setState: inst.setState && inst.setState.bind(inst), | ||
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), | ||
setInProps: inst.forceUpdate && setInProps.bind(null, element), | ||
setInProps: inst.forceUpdate && setInProps.bind(null, internalInstance), | ||
setInState: inst.forceUpdate && setInState.bind(null, inst), | ||
setInContext: inst.forceUpdate && setInContext.bind(null, inst), | ||
}; | ||
publicInstance = inst; | ||
if (typeof type === 'function') { | ||
publicInstance = inst; | ||
} | ||
@@ -114,2 +128,11 @@ // TODO: React ART currently falls in this bucket, but this doesn't | ||
if (typeof internalInstance.setNativeProps === 'function') { | ||
// For editing styles in RN | ||
updater = { | ||
setNativeProps(nativeProps) { | ||
internalInstance.setNativeProps(nativeProps); | ||
}, | ||
}; | ||
} | ||
return { | ||
@@ -120,2 +143,3 @@ nodeType, | ||
ref, | ||
source, | ||
name, | ||
@@ -122,0 +146,0 @@ props, |
@@ -16,7 +16,7 @@ /** | ||
function getData012(element: Object): DataType { | ||
function getData012(internalInstance: Object): DataType { | ||
var children = null; | ||
var props = element.props; | ||
var state = element.state; | ||
var context = element.context; | ||
var props = internalInstance.props; | ||
var state = internalInstance.state; | ||
var context = internalInstance.context; | ||
var updater = null; | ||
@@ -30,14 +30,14 @@ var name = null; | ||
var nodeType = 'Native'; | ||
if (element._renderedComponent) { | ||
if (internalInstance._renderedComponent) { | ||
nodeType = 'Wrapper'; | ||
children = [element._renderedComponent]; | ||
children = [internalInstance._renderedComponent]; | ||
if (context && Object.keys(context).length === 0) { | ||
context = null; | ||
} | ||
} else if (element._renderedChildren) { | ||
name = element.constructor.displayName; | ||
children = childrenList(element._renderedChildren); | ||
} else if (internalInstance._renderedChildren) { | ||
name = internalInstance.constructor.displayName; | ||
children = childrenList(internalInstance._renderedChildren); | ||
} else if (typeof props.children === 'string') { | ||
// string children | ||
name = element.constructor.displayName; | ||
name = internalInstance.constructor.displayName; | ||
children = props.children; | ||
@@ -47,12 +47,12 @@ nodeType = 'Native'; | ||
if (!props && element._currentElement && element._currentElement.props) { | ||
props = element._currentElement.props; | ||
if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { | ||
props = internalInstance._currentElement.props; | ||
} | ||
if (element._currentElement) { | ||
type = element._currentElement.type; | ||
if (element._currentElement.key) { | ||
key = String(element._currentElement.key); | ||
if (internalInstance._currentElement) { | ||
type = internalInstance._currentElement.type; | ||
if (internalInstance._currentElement.key) { | ||
key = String(internalInstance._currentElement.key); | ||
} | ||
ref = element._currentElement.ref; | ||
ref = internalInstance._currentElement.ref; | ||
if (typeof type === 'string') { | ||
@@ -70,3 +70,3 @@ name = type; | ||
if (!name) { | ||
name = element.constructor.displayName || 'No display name'; | ||
name = internalInstance.constructor.displayName || 'No display name'; | ||
nodeType = 'Composite'; | ||
@@ -82,11 +82,11 @@ } | ||
if (element.forceUpdate) { | ||
if (internalInstance.forceUpdate) { | ||
updater = { | ||
setState: element.setState.bind(element), | ||
forceUpdate: element.forceUpdate.bind(element), | ||
setInProps: element.forceUpdate && setInProps.bind(null, element), | ||
setInState: element.forceUpdate && setInState.bind(null, element), | ||
setInContext: element.forceUpdate && setInContext.bind(null, element), | ||
setState: internalInstance.setState.bind(internalInstance), | ||
forceUpdate: internalInstance.forceUpdate.bind(internalInstance), | ||
setInProps: internalInstance.forceUpdate && setInProps.bind(null, internalInstance), | ||
setInState: internalInstance.forceUpdate && setInState.bind(null, internalInstance), | ||
setInContext: internalInstance.forceUpdate && setInContext.bind(null, internalInstance), | ||
}; | ||
publicInstance = element; | ||
publicInstance = internalInstance; | ||
} | ||
@@ -99,2 +99,3 @@ | ||
ref, | ||
source: null, | ||
name, | ||
@@ -101,0 +102,0 @@ props, |
@@ -23,43 +23,199 @@ /** | ||
} | ||
Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { | ||
value: ({ | ||
_renderers: {}, | ||
helpers: {}, | ||
inject: function(renderer) { | ||
var id = Math.random().toString(16).slice(2); | ||
this._renderers[id] = renderer; | ||
this.emit('renderer', {id, renderer}); | ||
}, | ||
_listeners: {}, | ||
sub: function(evt, fn) { | ||
this.on(evt, fn); | ||
return () => this.off(evt, fn); | ||
}, | ||
on: function(evt, fn) { | ||
if (!this._listeners[evt]) { | ||
this._listeners[evt] = []; | ||
function detectReactBuildType(renderer) { | ||
try { | ||
var toString = Function.prototype.toString; | ||
if (typeof renderer.version === 'string') { | ||
// React DOM Fiber (16+) | ||
if (renderer.bundleType > 0) { | ||
// This is not a production build. | ||
// We are currently only using 0 (PROD) and 1 (DEV) | ||
// but might add 2 (PROFILE) in the future. | ||
return 'development'; | ||
} | ||
this._listeners[evt].push(fn); | ||
}, | ||
off: function(evt, fn) { | ||
if (!this._listeners[evt]) { | ||
return; | ||
// The above should cover envification, but we should still make sure | ||
// that the bundle code has been uglified. | ||
var findFiberCode = toString.call(renderer.findFiberByHostInstance); | ||
// Filter out bad results (if that is even possible): | ||
if (findFiberCode.indexOf('function') !== 0) { | ||
// Hope for the best if we're not sure. | ||
return 'production'; | ||
} | ||
var ix = this._listeners[evt].indexOf(fn); | ||
if (ix !== -1) { | ||
this._listeners[evt].splice(ix, 1); | ||
// By now we know that it's envified--but what if it's not minified? | ||
// This can be bad too, as it means DEV code is still there. | ||
// FIXME: this is fragile! | ||
// We should replace this check with check on a specially passed | ||
// function. This also doesn't detect lack of dead code elimination | ||
// (although this is not a problem since flat bundles). | ||
if (findFiberCode.indexOf('getClosestInstanceFromNode') !== -1) { | ||
return 'unminified'; | ||
} | ||
if (!this._listeners[evt].length) { | ||
this._listeners[evt] = null; | ||
// We're good. | ||
return 'production'; | ||
} | ||
if (renderer.Mount && renderer.Mount._renderNewRootComponent) { | ||
// React DOM Stack | ||
var renderRootCode = toString.call(renderer.Mount._renderNewRootComponent); | ||
// Filter out bad results (if that is even possible): | ||
if (renderRootCode.indexOf('function') !== 0) { | ||
// Hope for the best if we're not sure. | ||
return 'production'; | ||
} | ||
}, | ||
emit: function(evt, data) { | ||
if (this._listeners[evt]) { | ||
this._listeners[evt].map(fn => fn(data)); | ||
// Check for React DOM Stack < 15.1.0 in development. | ||
// If it contains "storedMeasure" call, it's wrapped in ReactPerf (DEV only). | ||
// This would be true even if it's minified, as method name still matches. | ||
if (renderRootCode.indexOf('storedMeasure') !== -1) { | ||
return 'development'; | ||
} | ||
}, | ||
}: Hook), | ||
// For other versions (and configurations) it's not so easy. | ||
// Let's quickly exclude proper production builds. | ||
// If it contains a warning message, it's either a DEV build, | ||
// or an PROD build without proper dead code elimination. | ||
if (renderRootCode.indexOf('should be a pure function') !== -1) { | ||
// Now how do we tell a DEV build from a bad PROD build? | ||
// If we see NODE_ENV, we're going to assume this is a dev build | ||
// because most likely it is referring to an empty shim. | ||
if (renderRootCode.indexOf('NODE_ENV') !== -1) { | ||
return 'development'; | ||
} | ||
// If we see "development", we're dealing with an envified DEV build | ||
// (such as the official React DEV UMD). | ||
if (renderRootCode.indexOf('development') !== -1) { | ||
return 'development'; | ||
} | ||
// I've seen process.env.NODE_ENV !== 'production' being smartly | ||
// replaced by `true` in DEV by Webpack. I don't know how that | ||
// works but we can safely guard against it because `true` was | ||
// never used in the function source since it was written. | ||
if (renderRootCode.indexOf('true') !== -1) { | ||
return 'development'; | ||
} | ||
// By now either it is a production build that has not been minified, | ||
// or (worse) this is a minified development build using non-standard | ||
// environment (e.g. "staging"). We're going to look at whether | ||
// the function argument name is mangled: | ||
if ( | ||
// 0.13 to 15 | ||
renderRootCode.indexOf('nextElement') !== -1 || | ||
// 0.12 | ||
renderRootCode.indexOf('nextComponent') !== -1 | ||
) { | ||
// We can't be certain whether this is a development build or not, | ||
// but it is definitely unminified. | ||
return 'unminified'; | ||
} else { | ||
// This is likely a minified development build. | ||
return 'development'; | ||
} | ||
} | ||
// By now we know that it's envified and dead code elimination worked, | ||
// but what if it's still not minified? (Is this even possible?) | ||
// Let's check matches for the first argument name. | ||
if ( | ||
// 0.13 to 15 | ||
renderRootCode.indexOf('nextElement') !== -1 || | ||
// 0.12 | ||
renderRootCode.indexOf('nextComponent') !== -1 | ||
) { | ||
return 'unminified'; | ||
} | ||
// Seems like we're using the production version. | ||
// Now let's check if we're still on 0.14 or lower: | ||
if (renderRootCode.indexOf('._registerComponent') !== -1) { | ||
// TODO: we can remove the condition above once 16 | ||
// is older than a year. Since this branch only runs | ||
// for Stack, we can flip it completely when Stack | ||
// is old enough. The branch for Fiber is above, | ||
// and it can check renderer.version directly. | ||
return 'outdated'; | ||
} | ||
// We're all good. | ||
return 'production'; | ||
} | ||
} catch (err) { | ||
// Weird environments may exist. | ||
// This code needs a higher fault tolerance | ||
// because it runs even with closed DevTools. | ||
// TODO: should we catch errors in all injected code, and not just this part? | ||
} | ||
return 'production'; | ||
} | ||
const hook = ({ | ||
// Shared between Stack and Fiber: | ||
_renderers: {}, | ||
helpers: {}, | ||
inject: function(renderer) { | ||
var id = Math.random().toString(16).slice(2); | ||
hook._renderers[id] = renderer; | ||
var reactBuildType = detectReactBuildType(renderer); | ||
hook.emit('renderer', {id, renderer, reactBuildType}); | ||
return id; | ||
}, | ||
_listeners: {}, | ||
sub: function(evt, fn) { | ||
hook.on(evt, fn); | ||
return () => hook.off(evt, fn); | ||
}, | ||
on: function(evt, fn) { | ||
if (!hook._listeners[evt]) { | ||
hook._listeners[evt] = []; | ||
} | ||
hook._listeners[evt].push(fn); | ||
}, | ||
off: function(evt, fn) { | ||
if (!hook._listeners[evt]) { | ||
return; | ||
} | ||
var ix = hook._listeners[evt].indexOf(fn); | ||
if (ix !== -1) { | ||
hook._listeners[evt].splice(ix, 1); | ||
} | ||
if (!hook._listeners[evt].length) { | ||
hook._listeners[evt] = null; | ||
} | ||
}, | ||
emit: function(evt, data) { | ||
if (hook._listeners[evt]) { | ||
hook._listeners[evt].map(fn => fn(data)); | ||
} | ||
}, | ||
// Fiber-only: | ||
supportsFiber: true, | ||
_fiberRoots: {}, | ||
getFiberRoots(rendererID) { | ||
const roots = hook._fiberRoots; | ||
if (!roots[rendererID]) { | ||
roots[rendererID] = new Set(); | ||
} | ||
return roots[rendererID]; | ||
}, | ||
onCommitFiberUnmount: function(rendererID, fiber) { | ||
// TODO: can we use hook for roots too? | ||
if (hook.helpers[rendererID]) { | ||
hook.helpers[rendererID].handleCommitFiberUnmount(fiber); | ||
} | ||
}, | ||
onCommitFiberRoot: function(rendererID, root) { | ||
const mountedRoots = hook.getFiberRoots(rendererID); | ||
const current = root.current; | ||
const isKnownRoot = mountedRoots.has(root); | ||
const isUnmounting = current.memoizedState == null || current.memoizedState.element == null; | ||
// Keep track of mounted roots so we can hydrate when DevTools connect. | ||
if (!isKnownRoot && !isUnmounting) { | ||
mountedRoots.add(root); | ||
} else if (isKnownRoot && isUnmounting) { | ||
mountedRoots.delete(root); | ||
} | ||
if (hook.helpers[rendererID]) { | ||
hook.helpers[rendererID].handleCommitFiberRoot(root); | ||
} | ||
}, | ||
}); | ||
Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { | ||
value: (hook : Hook), | ||
}); | ||
} | ||
module.exports = installGlobalHook; |
@@ -13,7 +13,19 @@ /** | ||
type CompositeUpdater = { | ||
setInProps: ?(path: Array<string>, value: any) => void, | ||
setInState: ?(path: Array<string>, value: any) => void, | ||
setInContext: ?(path: Array<string>, value: any) => void, | ||
forceUpdate: ?() => void, | ||
}; | ||
type NativeUpdater = { | ||
setNativeProps: ?(nativeProps: {[key: string]: any}) => void, | ||
}; | ||
export type DataType = { | ||
nodeType: 'Native' | 'Wrapper' | 'NativeWrapper' | 'Composite' | 'Text' | 'Empty', | ||
nodeType: 'Native' | 'Wrapper' | 'NativeWrapper' | 'Composite' | 'Text' | 'Portal' | 'Empty', | ||
type: ?(string | AnyFn), | ||
key: ?string, | ||
ref: ?(string | AnyFn), | ||
source: ?Object, | ||
name: ?string, | ||
@@ -25,9 +37,3 @@ props: ?Object, | ||
text: ?string, | ||
updater: ?{ | ||
setInProps: ?(path: Array<string>, value: any) => void, | ||
setInState: ?(path: Array<string>, value: any) => void, | ||
setInContext: ?(path: Array<string>, value: any) => void, | ||
// setState: ?(newState: any) => void, | ||
forceUpdate: ?() => void, | ||
}, | ||
updater: ?(CompositeUpdater | NativeUpdater), | ||
publicInstance: ?Object, | ||
@@ -47,3 +53,16 @@ }; | ||
type BundleType = | ||
// PROD | ||
| 0 | ||
// DEV | ||
| 1; | ||
export type ReactRenderer = { | ||
// Fiber | ||
findHostInstanceByFiber: (fiber: Object) => ?NativeType, | ||
findFiberByHostInstance: (hostInstance: NativeType) => ?OpaqueNodeHandle, | ||
version: string, | ||
bundleType: BundleType, | ||
// Stack | ||
Reconciler: { | ||
@@ -90,3 +109,3 @@ mountComponent: AnyFn, | ||
helpers: {[key: string]: Helpers}, | ||
inject: (renderer: ReactRenderer) => void, | ||
inject: (renderer: ReactRenderer) => string | null, | ||
emit: (evt: string, data: any) => void, | ||
@@ -97,2 +116,3 @@ sub: (evt: string, handler: Handler) => () => void, | ||
reactDevtoolsAgent?: ?Object, | ||
getFiberRoots: (rendererID : string) => Set<Object>, | ||
}; |
{ | ||
"dependencies": { | ||
"adm-zip": "^0.4.7", | ||
"babel-core": "6.3.21", | ||
@@ -10,3 +11,6 @@ "babel-eslint": "6.0.4", | ||
"babel-preset-stage-0": "6.3.13", | ||
"child-process-promise": "^2.2.1", | ||
"classnames": "2.2.1", | ||
"cli-spinners": "^1.0.0", | ||
"clipboard-js": "^0.3.3", | ||
"es6-symbol": "3.0.2", | ||
@@ -18,11 +22,16 @@ "eslint": "2.10.2", | ||
"flow-bin": "0.23.0", | ||
"fs-extra": "^3.0.1", | ||
"gh-pages": "^1.0.0", | ||
"immutable": "3.7.6", | ||
"jest-cli": "0.9.0-fb2", | ||
"json-loader": "0.5.4", | ||
"log-update": "^2.0.0", | ||
"node-libs-browser": "0.5.3", | ||
"object-assign": "4.0.1", | ||
"raw-loader": "^0.5.1", | ||
"react": "0.14.3", | ||
"react-addons-create-fragment": "0.14.3", | ||
"react-dom": "0.14.3", | ||
"react": "15.4.2", | ||
"react-addons-create-fragment": "15.4.2", | ||
"react-color": "^2.11.7", | ||
"react-dom": "15.4.2", | ||
"react-portal": "^3.1.0", | ||
"webpack": "1.12.9" | ||
@@ -33,4 +42,13 @@ }, | ||
"scripts": { | ||
"lint": "./node_modules/.bin/eslint .", | ||
"build:extension": "npm run build:extension:chrome && npm run build:extension:firefox", | ||
"build:extension:chrome": "node ./shells/chrome/build", | ||
"build:extension:firefox": "node ./shells/firefox/build", | ||
"build:standalone": "cd packages/react-devtools-core && npm run build", | ||
"deploy": "cd ./shells/theme-preview && ./build.sh && gh-pages -d .", | ||
"lint": "eslint .", | ||
"postinstall": "lerna bootstrap", | ||
"test": "node --harmony ./node_modules/.bin/jest", | ||
"test:chrome": "node ./shells/chrome/test", | ||
"test:firefox": "node ./shells/firefox/test", | ||
"test:standalone": "cd packages/react-devtools && npm start", | ||
"typecheck": "flow check" | ||
@@ -49,4 +67,13 @@ }, | ||
"es6" | ||
], | ||
"modulePathIgnorePatterns": [ | ||
"<rootDir>/shells" | ||
] | ||
}, | ||
"devDependencies": { | ||
"chrome-launch": "^1.1.4", | ||
"firefox-profile": "^1.0.2", | ||
"lerna": "2.0.0-beta.36", | ||
"web-ext": "^1.10.1" | ||
} | ||
} |
# React Developer Tools [![Build Status](https://travis-ci.org/facebook/react-devtools.svg?branch=master)](https://travis-ci.org/facebook/react-devtools) | ||
React Developer Tools is a system that allows you to inspect a React Renderer, | ||
including the Component hierarchy, props, state, and more. | ||
React Developer Tools lets you inspect the React component hierarchy, including component props and state. | ||
There are shells for Chrome (adding it to the Chrome devtools), Firefox, | ||
Atom/Nuclide, and as a standalone Electron app. | ||
It exists both as a browser extension (for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) and [Firefox](https://addons.mozilla.org/firefox/addon/react-devtools/)), and as a [standalone app](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools) (works with other environments including Safari, IE, and React Native). | ||
@@ -14,2 +12,3 @@ ![](/images/devtools-full.gif) | ||
### Pre-packaged | ||
The official extensions represent the current stable release. | ||
@@ -19,12 +18,18 @@ | ||
- [Firefox extension](https://addons.mozilla.org/firefox/addon/react-devtools/) | ||
- Standalone app (coming soon) | ||
- [Standalone app (Safari, React Native, etc)](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) | ||
If you inspect an element or launch the developer tools on a React page, you | ||
should see an extra tab called **React** in the inspector. | ||
Opera users can [enable Chrome extensions](https://addons.opera.com/extensions/details/download-chrome-extension-9/) and then install the [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi). | ||
Check out [For Hacking](#for-hacking) if you want to develop the Developer | ||
Tools or use a pre-prelease version. | ||
## Usage | ||
The extension icon will light up on the websites using React: | ||
<img src="http://i.imgur.com/3tuhIgm.png" alt="Extension icon becomes active" width="500"> | ||
On such websites, you will see a tab called React in Chrome Developer Tools: | ||
<img src="http://i.imgur.com/jYieRqi.png" alt="React tab in DevTools" width="500"> | ||
A quick way to bring up the DevTools is to right-click on the page and press Inspect. | ||
### Tree View | ||
@@ -35,4 +40,3 @@ | ||
source, etc. | ||
- Use the search bar to find components by name | ||
- A red collapser means the component has state/context | ||
- Differently-colored collapser means the component has state/context | ||
@@ -48,10 +52,26 @@ ![](/images/devtools-tree-view.png) | ||
## For Hacking | ||
For changes that don't directly involve Chrome/Firefox/etc. APIs, there is a | ||
"plain" shell that just renders the devtools into an html page along with a | ||
TodoMVC test app. This is by far the quickest way to develop. Check out | ||
[the Readme.md](/shells/plain) in `/shells/plain` for info. | ||
### Search Bar | ||
For other shells (Chrome, Firefox, etc.), see the respective directories in `/shells`. | ||
- Use the search bar to find components by name | ||
![](/images/devtools-search-new.gif) | ||
### Handy Tips | ||
#### Finding Component by a DOM Node | ||
If you inspect a React element on the page using the regular **Elements** tab, then switch over to the **React** tab, that element will be automatically selected in the React tree. | ||
#### Finding DOM Node by a Component | ||
You can right-click any React element in the **React** tab, and choose "Find the DOM node". This will bring you to the corresponding DOM node in the **Elements** tab. | ||
#### Displaying Element Source | ||
You may include the [transform-react-jsx-source](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-source) Babel plugin to see the source file and line number of React elements. This information appears in the bottom of the right panel when available. Don't forget to disable it in production! (Tip: if you use [Create React App](https://github.com/facebookincubator/create-react-app) it is already enabled in development.) | ||
#### Usage with React Native and Safari | ||
There is a [standalone version](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) that works with other environments such as React Native and Safari. | ||
## FAQ | ||
@@ -61,17 +81,16 @@ | ||
The "React" tab won't show up if React can't communicate with the | ||
devtools. When the page loads, the devtools sets a global named | ||
`__REACT_DEVTOOLS_GLOBAL_HOOK__`, then React communicates with that | ||
hook during initialization. | ||
**If you are running your app from a local `file://` URL**, don't forget to check "Allow access to file URLs" on the Chrome Extensions settings page. You can find it by opening Settings > Extensions: | ||
(In React 0.11 and older, it was necessary to expose a global called `React` | ||
for the devtools to function.) | ||
![Allow access to file URLs](http://i.imgur.com/Yt1rmUp.png) | ||
You can test this on the [React website](http://facebook.github.io/react/) | ||
or by inspecting [Facebook](https://www.facebook.com/). | ||
**The React tab won't show up if the site doesn't use React**, or if React can't communicate with the devtools. When the page loads, the devtools sets a global named `__REACT_DEVTOOLS_GLOBAL_HOOK__`, then React communicates with that hook during initialization. You can test this on the [React website](http://facebook.github.io/react/) or by inspecting [Facebook](https://www.facebook.com/). | ||
Currently iframes and Chrome apps/extensions are not inspectable. | ||
**If your app is inside of CodePen**, make sure you are registered. Then press Fork (if it's not your pen), and then choose Change View > Debug. The Debug view is inspectable with DevTools because it doesn't use an iframe. | ||
### Does "Trace React Updates" trace renders? | ||
**If your app is inside an iframe, a Chrome extension, React Native, or in another unusual environment**, try [the standalone version instead](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools). Chrome apps are currently not inspectable. | ||
**If you still have issues** please [report them](https://github.com/facebook/react-devtools/issues/new). Don't forget to specify your OS, browser version, extension version, and the exact instructions to reproduce the issue with a screenshot. | ||
### Does "Highlight Updates" trace renders? | ||
Yes, but it's also tracing if a component *may* render. | ||
@@ -81,3 +100,3 @@ In order to fully understand what counts as an "update", you need to understand how [shouldComponentUpdate](https://facebook.github.io/react/docs/advanced-performance.html#shouldcomponentupdate-in-action) works. | ||
Here "Trace React Updates" will draw a border around every node but C4 and C5. | ||
Here "Highlight Updates" will draw a border around every node but C4 and C5. | ||
Why does it trace components that don't actually update? (via shouldComponentUpdate() -> false) | ||
@@ -87,8 +106,16 @@ This is a limitation of the system used to track updates, and will hopefully change in the future. It doesn't, however, trace the children of components that opt out, as there's no possibility of them updating. | ||
### ProTips | ||
## Contributing | ||
If you inspect a React element on the page using the regular **Elements** tab, | ||
then switch over to the **React** tab, that element will be automatically | ||
selected in the React tree. | ||
For changes that don't directly involve Chrome/Firefox/etc. APIs, there is a | ||
"plain" shell that just renders the devtools into an html page along with a | ||
TodoMVC test app. This is by far the quickest way to develop. Check out | ||
[the Readme.md](/shells/plain) in `/shells/plain` for info. | ||
For other shells (Chrome, Firefox, etc.), see the respective directories in `/shells`. | ||
For a list of good contribution opportunities, check the [good first bug](https://github.com/facebook/react-devtools/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+bug%22) label. We're happy to answer any questions on those issues! | ||
To read more about the community and guidelines for submitting pull requests, | ||
please read the [Contributing document](CONTRIBUTING.md). | ||
## Debugging (in Chrome) | ||
@@ -106,5 +133,1 @@ | ||
## Contributing | ||
To read more about the community and guidelines for submitting pull requests, | ||
please read the [Contributing document](CONTRIBUTING.md). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
117889
32
2881
0
18
1