mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[perf] New ReactDefaultPerf
Simplified version of https://github.com/facebook/react/pull/962. More goodies coming soon since the inclusive time isn't very helpful right now.
This commit is contained in:
committed by
Paul O’Shannessy
parent
9ac27cb551
commit
439bca78ed
@@ -23,6 +23,7 @@
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
@@ -79,71 +80,76 @@ var ReactComponentBrowserEnvironment = {
|
||||
* @param {boolean} shouldReuseMarkup Should reuse the existing markup in the
|
||||
* container if possible.
|
||||
*/
|
||||
mountImageIntoNode: function(markup, container, shouldReuseMarkup) {
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE
|
||||
),
|
||||
'mountComponentIntoNode(...): Target container is not valid.'
|
||||
);
|
||||
mountImageIntoNode: ReactPerf.measure(
|
||||
'ReactComponentBrowserEnvironment',
|
||||
'mountImageIntoNode',
|
||||
function(markup, container, shouldReuseMarkup) {
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE
|
||||
),
|
||||
'mountComponentIntoNode(...): Target container is not valid.'
|
||||
);
|
||||
|
||||
if (shouldReuseMarkup) {
|
||||
if (ReactMarkupChecksum.canReuseMarkup(
|
||||
markup,
|
||||
getReactRootElementInContainer(container))) {
|
||||
return;
|
||||
} else {
|
||||
invariant(
|
||||
container.nodeType !== DOC_NODE_TYPE,
|
||||
'You\'re trying to render a component to the document using ' +
|
||||
'server rendering but the checksum was invalid. This usually ' +
|
||||
'means you rendered a different component type or props on ' +
|
||||
'the client from the one on the server, or your render() methods ' +
|
||||
'are impure. React cannot handle this case due to cross-browser ' +
|
||||
'quirks by rendering at the document root. You should look for ' +
|
||||
'environment dependent code in your components and ensure ' +
|
||||
'the props are the same client and server side.'
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'React attempted to use reuse markup in a container but the ' +
|
||||
'checksum was invalid. This generally means that you are using ' +
|
||||
'server rendering and the markup generated on the server was ' +
|
||||
'not what the client was expecting. React injected new markup ' +
|
||||
'to compensate which works but you have lost many of the ' +
|
||||
'benefits of server rendering. Instead, figure out why the ' +
|
||||
'markup being generated is different on the client or server.'
|
||||
if (shouldReuseMarkup) {
|
||||
if (ReactMarkupChecksum.canReuseMarkup(
|
||||
markup,
|
||||
getReactRootElementInContainer(container))) {
|
||||
return;
|
||||
} else {
|
||||
invariant(
|
||||
container.nodeType !== DOC_NODE_TYPE,
|
||||
'You\'re trying to render a component to the document using ' +
|
||||
'server rendering but the checksum was invalid. This usually ' +
|
||||
'means you rendered a different component type or props on ' +
|
||||
'the client from the one on the server, or your render() ' +
|
||||
'methods are impure. React cannot handle this case due to ' +
|
||||
'cross-browser quirks by rendering at the document root. You ' +
|
||||
'should look for environment dependent code in your components ' +
|
||||
'and ensure the props are the same client and server side.'
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'React attempted to use reuse markup in a container but the ' +
|
||||
'checksum was invalid. This generally means that you are ' +
|
||||
'using server rendering and the markup generated on the ' +
|
||||
'server was not what the client was expecting. React injected' +
|
||||
'new markup to compensate which works but you have lost many ' +
|
||||
'of the benefits of server rendering. Instead, figure out ' +
|
||||
'why the markup being generated is different on the client ' +
|
||||
'or server.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
container.nodeType !== DOC_NODE_TYPE,
|
||||
'You\'re trying to render a component to the document but ' +
|
||||
'you didn\'t use server rendering. We can\'t do this ' +
|
||||
'without using server rendering due to cross-browser quirks. ' +
|
||||
'See renderComponentToString() for server rendering.'
|
||||
);
|
||||
invariant(
|
||||
container.nodeType !== DOC_NODE_TYPE,
|
||||
'You\'re trying to render a component to the document but ' +
|
||||
'you didn\'t use server rendering. We can\'t do this ' +
|
||||
'without using server rendering due to cross-browser quirks. ' +
|
||||
'See renderComponentToString() for server rendering.'
|
||||
);
|
||||
|
||||
// Asynchronously inject markup by ensuring that the container is not in
|
||||
// the document when settings its `innerHTML`.
|
||||
var parent = container.parentNode;
|
||||
if (parent) {
|
||||
var next = container.nextSibling;
|
||||
parent.removeChild(container);
|
||||
container.innerHTML = markup;
|
||||
if (next) {
|
||||
parent.insertBefore(container, next);
|
||||
// Asynchronously inject markup by ensuring that the container is not in
|
||||
// the document when settings its `innerHTML`.
|
||||
var parent = container.parentNode;
|
||||
if (parent) {
|
||||
var next = container.nextSibling;
|
||||
parent.removeChild(container);
|
||||
container.innerHTML = markup;
|
||||
if (next) {
|
||||
parent.insertBefore(container, next);
|
||||
} else {
|
||||
parent.appendChild(container);
|
||||
}
|
||||
} else {
|
||||
parent.appendChild(container);
|
||||
container.innerHTML = markup;
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = markup;
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
module.exports = ReactComponentBrowserEnvironment;
|
||||
|
||||
+169
-382
@@ -17,406 +17,193 @@
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var merge = require('merge');
|
||||
var performanceNow = require('performanceNow');
|
||||
|
||||
var ReactDefaultPerf = {};
|
||||
// Don't try to save users less than 1.2ms (a number I made up)
|
||||
var DONT_CARE_THRESHOLD = 1.2;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDefaultPerf = {
|
||||
_injected: false,
|
||||
function getSummary(measurements, sortInclusive) {
|
||||
var candidates = {};
|
||||
var totalDOMTime = 0;
|
||||
var displayName;
|
||||
|
||||
start: function() {
|
||||
if (!ReactDefaultPerf._injected) {
|
||||
ReactPerf.injection.injectMeasure(ReactDefaultPerf.measure);
|
||||
}
|
||||
ReactPerf.enableMeasure = true;
|
||||
},
|
||||
for (var i = 0; i < measurements.length; i++) {
|
||||
var measurement = measurements[i];
|
||||
var id;
|
||||
|
||||
stop: function() {
|
||||
ReactPerf.enableMeasure = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the stored information for a given object's function.
|
||||
*
|
||||
* @param {string} objName
|
||||
* @param {string} fnName
|
||||
* @return {?object}
|
||||
*/
|
||||
getInfo: function(objName, fnName) {
|
||||
if (!this.info[objName] || !this.info[objName][fnName]) {
|
||||
return null;
|
||||
}
|
||||
return this.info[objName][fnName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the logs pertaining to a given object's function.
|
||||
*
|
||||
* @param {string} objName
|
||||
* @param {string} fnName
|
||||
* @return {?array<object>}
|
||||
*/
|
||||
getLogs: function(objName, fnName) {
|
||||
if (!this.getInfo(objName, fnName)) {
|
||||
return null;
|
||||
}
|
||||
return this.logs.filter(function(log) {
|
||||
return log.objName === objName && log.fnName === fnName;
|
||||
for (id in measurement.writes) {
|
||||
measurement.writes[id].forEach(function(write) {
|
||||
totalDOMTime += write.time;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs through the logs and builds an array of arrays, where each array
|
||||
* walks through the mounting/updating of each component underneath.
|
||||
*
|
||||
* @param {string} rootID The reactID of the root node, e.g. '.0'
|
||||
* @return {array<array>}
|
||||
*/
|
||||
getRawRenderHistory: function(rootID) {
|
||||
var history = [];
|
||||
/**
|
||||
* Since logs are added after the method returns, the logs are in a sense
|
||||
* upside-down: the inner-most elements from mounting/updating are logged
|
||||
* first, and the last addition to the log is the top renderComponent.
|
||||
* Therefore, we flip the logs upside down for ease of processing, and
|
||||
* reverse the history array at the end so the earliest event has index 0.
|
||||
*/
|
||||
var logs = this.logs.filter(function(log) {
|
||||
return log.reactID.indexOf(rootID) === 0;
|
||||
}).reverse();
|
||||
var allIDs = merge(measurement.exclusive, measurement.inclusive);
|
||||
|
||||
var subHistory = [];
|
||||
logs.forEach(function(log, i) {
|
||||
if (i && log.reactID === rootID && logs[i - 1].reactID !== rootID) {
|
||||
subHistory.length && history.push(subHistory);
|
||||
subHistory = [];
|
||||
}
|
||||
subHistory.push(log);
|
||||
});
|
||||
if (subHistory.length) {
|
||||
history.push(subHistory);
|
||||
}
|
||||
return history.reverse();
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs through the logs and builds an array of strings, where each string
|
||||
* is a multiline formatted way of walking through the mounting/updating
|
||||
* underneath.
|
||||
*
|
||||
* @param {string} rootID The reactID of the root node, e.g. '.0'
|
||||
* @return {array<string>}
|
||||
*/
|
||||
getRenderHistory: function(rootID) {
|
||||
var history = this.getRawRenderHistory(rootID);
|
||||
|
||||
return history.map(function(subHistory) {
|
||||
var headerString = (
|
||||
'log# Component (execution time) [bloat from logging]\n' +
|
||||
'================================================================\n'
|
||||
);
|
||||
return headerString + subHistory.map(function(log) {
|
||||
// Add two spaces for every layer in the reactID.
|
||||
var indents = '\t' + Array(log.reactID.split('.').length).join(' ');
|
||||
var delta = _microTime(log.timing.delta);
|
||||
var bloat = _microTime(log.timing.timeToLog);
|
||||
|
||||
return log.index + indents + log.name + ' (' + delta + 'ms)' +
|
||||
' [' + bloat + 'ms]';
|
||||
}).join('\n');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Print the render history from `getRenderHistory` using console.log.
|
||||
* This is currently the best way to display perf data from
|
||||
* any React component; working on that.
|
||||
*
|
||||
* @param {string} rootID The reactID of the root node, e.g. '.0'
|
||||
* @param {number} index
|
||||
*/
|
||||
printRenderHistory: function(rootID, index) {
|
||||
var history = this.getRenderHistory(rootID);
|
||||
if (!history[index]) {
|
||||
console.warn(
|
||||
'Index', index, 'isn\'t available! ' +
|
||||
'The render history is', history.length, 'long.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
'Loading render history #' + (index + 1) +
|
||||
' of ' + history.length + ':\n' + history[index]
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prints the heatmap legend to console, showing how the colors correspond
|
||||
* with render times. This relies on console.log styles.
|
||||
*/
|
||||
printHeatmapLegend: function() {
|
||||
if (!this.options.heatmap.enabled) {
|
||||
return;
|
||||
}
|
||||
var max = this.info.React
|
||||
&& this.info.React.renderComponent
|
||||
&& this.info.React.renderComponent.max;
|
||||
if (max) {
|
||||
var logStr = 'Heatmap: ';
|
||||
for (var ii = 0; ii <= 10 * max; ii += max) {
|
||||
logStr += '%c ' + (Math.round(ii) / 10) + 'ms ';
|
||||
}
|
||||
console.log(
|
||||
logStr,
|
||||
'background-color: hsla(100, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 90, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 80, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 70, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 60, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 50, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 40, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 30, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 20, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 10, 100%, 50%, 0.6);',
|
||||
'background-color: hsla( 0, 100%, 50%, 0.6);'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Measure a given function with logging information, and calls a callback
|
||||
* if there is one.
|
||||
*
|
||||
* @param {string} objName
|
||||
* @param {string} fnName
|
||||
* @param {function} func
|
||||
* @return {function}
|
||||
*/
|
||||
measure: function(objName, fnName, func) {
|
||||
var info = _getNewInfo(objName, fnName);
|
||||
|
||||
var fnArgs = _getFnArguments(func);
|
||||
|
||||
return function(...args) {
|
||||
var timeBeforeFn = performanceNow();
|
||||
var fnReturn = func.apply(this, args);
|
||||
var timeAfterFn = performanceNow();
|
||||
|
||||
/**
|
||||
* Hold onto arguments in a readable way: args[1] -> args.component.
|
||||
* args is also passed to the callback, so if you want to save an
|
||||
* argument in the log, do so in the callback.
|
||||
*/
|
||||
var argsObject = {};
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
argsObject[fnArgs[i]] = args[i];
|
||||
}
|
||||
|
||||
var log = {
|
||||
index: ReactDefaultPerf.logs.length,
|
||||
fnName: fnName,
|
||||
objName: objName,
|
||||
timing: {
|
||||
before: timeBeforeFn,
|
||||
after: timeAfterFn,
|
||||
delta: timeAfterFn - timeBeforeFn
|
||||
}
|
||||
};
|
||||
|
||||
ReactDefaultPerf.logs.push(log);
|
||||
|
||||
/**
|
||||
* The callback gets:
|
||||
* - this (the component)
|
||||
* - the original method's arguments
|
||||
* - what the method returned
|
||||
* - the log object, and
|
||||
* - the wrapped method's info object.
|
||||
*/
|
||||
var callback = _getCallback(objName, fnName);
|
||||
callback && callback(this, argsObject, fnReturn, log, info);
|
||||
|
||||
log.timing.timeToLog = performanceNow() - timeAfterFn;
|
||||
|
||||
return fnReturn;
|
||||
for (id in allIDs) {
|
||||
displayName = measurement.displayNames[id];
|
||||
candidates[displayName] = candidates[displayName] || {
|
||||
inclusive: 0,
|
||||
exclusive: 0
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Holds information on wrapped objects/methods.
|
||||
* For instance, ReactDefaultPerf.info.React.renderComponent
|
||||
*/
|
||||
info: {},
|
||||
|
||||
/**
|
||||
* Holds all of the logs. Filter this to pull desired information.
|
||||
*/
|
||||
logs: [],
|
||||
|
||||
/**
|
||||
* Toggle settings for ReactDefaultPerf
|
||||
*/
|
||||
options: {
|
||||
/**
|
||||
* The heatmap sets the background color of the React containers
|
||||
* according to how much total time has been spent rendering them.
|
||||
* The most temporally expensive component is set as pure red,
|
||||
* and the others are colored from green to red as a fraction
|
||||
* of that max component time.
|
||||
*/
|
||||
heatmap: {
|
||||
enabled: true
|
||||
if (measurement.exclusive[id]) {
|
||||
candidates[displayName].exclusive += measurement.exclusive[id];
|
||||
}
|
||||
if (measurement.inclusive[id]) {
|
||||
candidates[displayName].inclusive += measurement.inclusive[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a info area for a given object's function, adding a new one if
|
||||
* necessary.
|
||||
*
|
||||
* @param {string} objName
|
||||
* @param {string} fnName
|
||||
* @return {object}
|
||||
*/
|
||||
var _getNewInfo = function(objName, fnName) {
|
||||
var info = ReactDefaultPerf.getInfo(objName, fnName);
|
||||
if (info) {
|
||||
return info;
|
||||
// Now make a sorted array with the results.
|
||||
var arr = [];
|
||||
for (displayName in candidates) {
|
||||
if (candidates[displayName] < DONT_CARE_THRESHOLD) {
|
||||
continue;
|
||||
}
|
||||
ReactDefaultPerf.info[objName] = ReactDefaultPerf.info[objName] || {};
|
||||
arr.push({
|
||||
componentName: displayName,
|
||||
exclusiveTime: candidates[displayName].exclusive,
|
||||
inclusiveTime: candidates[displayName].inclusive
|
||||
});
|
||||
}
|
||||
|
||||
return ReactDefaultPerf.info[objName][fnName] = {
|
||||
getLogs: function() {
|
||||
return ReactDefaultPerf.getLogs(objName, fnName);
|
||||
if (sortInclusive) {
|
||||
arr.sort(function(a, b) {
|
||||
return b.inclusiveTime - a.inclusiveTime;
|
||||
});
|
||||
} else {
|
||||
arr.sort(function(a, b) {
|
||||
return b.exclusiveTime - a.exclusiveTime;
|
||||
});
|
||||
}
|
||||
|
||||
return {componentClasses: arr, totalDOMTime: totalDOMTime};
|
||||
}
|
||||
|
||||
var ReactDefaultPerf = {
|
||||
_allMeasurements: null, // last item in the list is the current one
|
||||
_injected: false,
|
||||
|
||||
start: function() {
|
||||
if (!ReactDefaultPerf._injected) {
|
||||
ReactPerf.injection.injectMeasure(ReactDefaultPerf.measure);
|
||||
}
|
||||
|
||||
ReactDefaultPerf._allMeasurements = [];
|
||||
ReactPerf.enableMeasure = true;
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
ReactPerf.enableMeasure = false;
|
||||
},
|
||||
|
||||
getLastMeasurements: function() {
|
||||
return ReactDefaultPerf._allMeasurements;
|
||||
},
|
||||
|
||||
printByExclusive: function(measurements) {
|
||||
ReactDefaultPerf.print(measurements, false);
|
||||
},
|
||||
|
||||
printByInclusive: function(measurements) {
|
||||
ReactDefaultPerf.print(measurements, true);
|
||||
},
|
||||
|
||||
print: function(measurements, sortInclusive) {
|
||||
measurements = measurements || ReactDefaultPerf._allMeasurements;
|
||||
var summary = getSummary(measurements, sortInclusive);
|
||||
console.table(summary.componentClasses.map(function(item) {
|
||||
return {
|
||||
'Component class name': item.componentName,
|
||||
'Inclusive time': item.inclusiveTime.toFixed(2) + ' ms',
|
||||
'Exclusive time': item.exclusiveTime.toFixed(2) + ' ms'
|
||||
};
|
||||
}));
|
||||
console.log('Total DOM time:', summary.totalDOMTime.toFixed(2) + ' ms');
|
||||
},
|
||||
|
||||
_recordWrite: function(id, fnName, totalTime) {
|
||||
var writes =
|
||||
ReactDefaultPerf
|
||||
._allMeasurements[ReactDefaultPerf._allMeasurements.length - 1]
|
||||
.writes;
|
||||
writes[id] = writes[id] || [];
|
||||
writes[id].push({
|
||||
type: fnName,
|
||||
time: totalTime
|
||||
});
|
||||
},
|
||||
|
||||
measure: function(moduleName, fnName, func) {
|
||||
return function(...args) {
|
||||
var totalTime;
|
||||
var rv;
|
||||
var start;
|
||||
|
||||
if (fnName === 'flushBatchedUpdates') {
|
||||
// A "measurement" is a set of metrics recorded for each flush. We want
|
||||
// to group the metrics for a given flush together so we can look at the
|
||||
// components that rendered and the DOM operations that actually
|
||||
// happened to determine the amount of "wasted work" performed.
|
||||
ReactDefaultPerf._allMeasurements.push({
|
||||
exclusive: {},
|
||||
inclusive: {},
|
||||
counts: {},
|
||||
writes: {},
|
||||
displayNames: {}
|
||||
});
|
||||
return func.apply(this, args);
|
||||
} else if (moduleName === 'ReactDOMIDOperations' ||
|
||||
moduleName === 'ReactComponentBrowserEnvironment') {
|
||||
start = performanceNow();
|
||||
rv = func.apply(this, args);
|
||||
totalTime = performanceNow() - start;
|
||||
|
||||
if (fnName === 'mountImageIntoNode') {
|
||||
var mountID = ReactMount.getID(args[1]);
|
||||
ReactDefaultPerf._recordWrite(mountID, fnName, totalTime);
|
||||
} else if (fnName === 'dangerouslyProcessChildrenUpdates') {
|
||||
// special format
|
||||
args[0].forEach(function(update) {
|
||||
ReactDefaultPerf._recordWrite(update.parentID, fnName, totalTime);
|
||||
});
|
||||
} else {
|
||||
// basic format
|
||||
ReactDefaultPerf._recordWrite(args[0], fnName, totalTime);
|
||||
}
|
||||
return rv;
|
||||
} else if (fnName === 'updateComponent' ||
|
||||
fnName === '_renderValidatedComponent') {
|
||||
var isInclusive = fnName === 'updateComponent';
|
||||
var entry = ReactDefaultPerf._allMeasurements[
|
||||
ReactDefaultPerf._allMeasurements.length - 1
|
||||
];
|
||||
if (isInclusive) {
|
||||
// Since both updateComponent() and _renderValidatedComponent() are
|
||||
// called for each render, only record the count for one of them.
|
||||
entry.counts[this._rootNodeID] = entry.counts[this._rootNodeID] || 0;
|
||||
entry.counts[this._rootNodeID] += 1;
|
||||
}
|
||||
start = performanceNow();
|
||||
rv = func.apply(this, args);
|
||||
totalTime = performanceNow() - start;
|
||||
|
||||
var typeOfLog = isInclusive ? entry.inclusive : entry.exclusive;
|
||||
typeOfLog[this._rootNodeID] = typeOfLog[this._rootNodeID] || 0;
|
||||
typeOfLog[this._rootNodeID] += totalTime;
|
||||
|
||||
entry.displayNames[this._rootNodeID] = this.constructor.displayName;
|
||||
|
||||
return rv;
|
||||
} else {
|
||||
return func.apply(this, args);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a list of the argument names from a function's definition.
|
||||
* This is useful for storing arguments by their names within wrapFn().
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {array<string>}
|
||||
*/
|
||||
var _getFnArguments = function(fn) {
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var fnStr = fn.toString().replace(STRIP_COMMENTS, '');
|
||||
fnStr = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'));
|
||||
return fnStr.match(/([^\s,]+)/g) || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Store common callbacks within ReactDefaultPerf.
|
||||
*
|
||||
* @param {string} objName
|
||||
* @param {string} fnName
|
||||
* @return {?function}
|
||||
*/
|
||||
var _getCallback = function(objName, fnName) {
|
||||
switch (objName + '.' + fnName) {
|
||||
case 'React.renderComponent':
|
||||
return _renderComponentCallback;
|
||||
case 'ReactDOMComponent.mountComponent':
|
||||
case 'ReactDOMComponent.updateComponent':
|
||||
return _nativeComponentCallback;
|
||||
case 'ReactCompositeComponent.mountComponent':
|
||||
case 'ReactCompositeComponent.updateComponent':
|
||||
return _compositeComponentCallback;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for React.renderComponent
|
||||
*
|
||||
* @param {object} component
|
||||
* @param {object} args
|
||||
* @param {?object} fnReturn
|
||||
* @param {object} log
|
||||
* @param {object} info
|
||||
*/
|
||||
var _renderComponentCallback =
|
||||
function(component, args, fnReturn, log, info) {
|
||||
log.name = args.nextComponent.constructor.displayName || '[unknown]';
|
||||
log.reactID = fnReturn._rootNodeID || null;
|
||||
|
||||
if (ReactDefaultPerf.options.heatmap.enabled) {
|
||||
var container = args.container;
|
||||
if (!container.loggedByReactDefaultPerf) {
|
||||
container.loggedByReactDefaultPerf = true;
|
||||
info.components = info.components || [];
|
||||
info.components.push(container);
|
||||
}
|
||||
|
||||
container.count = container.count || 0;
|
||||
container.count += log.timing.delta;
|
||||
info.max = info.max || 0;
|
||||
if (container.count > info.max) {
|
||||
info.max = container.count;
|
||||
info.components.forEach(function(component) {
|
||||
_setHue(component, 100 - 100 * component.count / info.max);
|
||||
});
|
||||
} else {
|
||||
_setHue(container, 100 - 100 * container.count / info.max);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for ReactDOMComponent
|
||||
*
|
||||
* @param {object} component
|
||||
* @param {object} args
|
||||
* @param {?object} fnReturn
|
||||
* @param {object} log
|
||||
* @param {object} info
|
||||
*/
|
||||
var _nativeComponentCallback =
|
||||
function(component, args, fnReturn, log, info) {
|
||||
log.name = component.tagName || '[unknown]';
|
||||
log.reactID = component._rootNodeID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for ReactCompositeComponent
|
||||
*
|
||||
* @param {object} component
|
||||
* @param {object} args
|
||||
* @param {?object} fnReturn
|
||||
* @param {object} log
|
||||
* @param {object} info
|
||||
*/
|
||||
var _compositeComponentCallback =
|
||||
function(component, args, fnReturn, log, info) {
|
||||
log.name = component.constructor.displayName || '[unknown]';
|
||||
log.reactID = component._rootNodeID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Using the hsl() background-color attribute, colors an element.
|
||||
*
|
||||
* @param {DOMElement} el
|
||||
* @param {number} hue [0 for red, 120 for green, 240 for blue]
|
||||
*/
|
||||
var _setHue = function(el, hue) {
|
||||
el.style.backgroundColor = 'hsla(' + hue + ', 100%, 50%, 0.6)';
|
||||
};
|
||||
|
||||
/**
|
||||
* Round to the thousandth place.
|
||||
* @param {number} time
|
||||
* @return {number}
|
||||
*/
|
||||
var _microTime = function(time) {
|
||||
return Math.round(time * 1000) / 1000;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactDefaultPerf;
|
||||
|
||||
Reference in New Issue
Block a user