mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
added comments to the module aliasing code
This commit is contained in:
@@ -1,424 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDebugTool
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
|
||||
var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
|
||||
var ReactComponentTreeHook = require('ReactComponentTreeHook');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
var performanceNow = require('performanceNow');
|
||||
var warning = require('warning');
|
||||
|
||||
import type { ReactElement } from 'ReactElementType';
|
||||
import type { DebugID } from 'ReactInstanceType';
|
||||
import type { Operation } from 'ReactHostOperationHistoryHook';
|
||||
|
||||
type Hook = any;
|
||||
|
||||
type TimerType =
|
||||
'ctor' |
|
||||
'render' |
|
||||
'componentWillMount' |
|
||||
'componentWillUnmount' |
|
||||
'componentWillReceiveProps' |
|
||||
'shouldComponentUpdate' |
|
||||
'componentWillUpdate' |
|
||||
'componentDidUpdate' |
|
||||
'componentDidMount';
|
||||
|
||||
type Measurement = {
|
||||
timerType: TimerType,
|
||||
instanceID: DebugID,
|
||||
duration: number,
|
||||
};
|
||||
|
||||
type TreeSnapshot = {
|
||||
[key: DebugID]: {
|
||||
displayName: string,
|
||||
text: string,
|
||||
updateCount: number,
|
||||
childIDs: Array<DebugID>,
|
||||
ownerID: DebugID,
|
||||
parentID: DebugID,
|
||||
}
|
||||
};
|
||||
|
||||
type HistoryItem = {
|
||||
duration: number,
|
||||
measurements: Array<Measurement>,
|
||||
operations: Array<Operation>,
|
||||
treeSnapshot: TreeSnapshot,
|
||||
};
|
||||
|
||||
export type FlushHistory = Array<HistoryItem>;
|
||||
|
||||
// Trust the developer to only use this with a __DEV__ check
|
||||
var ReactDebugTool = ((null: any): typeof ReactDebugTool);
|
||||
|
||||
if (__DEV__) {
|
||||
var hooks = [];
|
||||
var didHookThrowForEvent = {};
|
||||
|
||||
const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) {
|
||||
try {
|
||||
fn.call(context, arg1, arg2, arg3, arg4, arg5);
|
||||
} catch (e) {
|
||||
warning(
|
||||
didHookThrowForEvent[event],
|
||||
'Exception thrown by hook while handling %s: %s',
|
||||
event,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
didHookThrowForEvent[event] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const emitEvent = function(event, arg1, arg2, arg3, arg4, arg5) {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
var hook = hooks[i];
|
||||
var fn = hook[event];
|
||||
if (fn) {
|
||||
callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var isProfiling = false;
|
||||
var flushHistory = [];
|
||||
var lifeCycleTimerStack = [];
|
||||
var currentFlushNesting = 0;
|
||||
var currentFlushMeasurements = [];
|
||||
var currentFlushStartTime = 0;
|
||||
var currentTimerDebugID = null;
|
||||
var currentTimerStartTime = 0;
|
||||
var currentTimerNestedFlushDuration = 0;
|
||||
var currentTimerType = null;
|
||||
|
||||
var lifeCycleTimerHasWarned = false;
|
||||
|
||||
const clearHistory = function() {
|
||||
ReactComponentTreeHook.purgeUnmountedComponents();
|
||||
ReactHostOperationHistoryHook.clearHistory();
|
||||
};
|
||||
|
||||
const getTreeSnapshot = function(registeredIDs) {
|
||||
return registeredIDs.reduce((tree, id) => {
|
||||
var ownerID = ReactComponentTreeHook.getOwnerID(id);
|
||||
var parentID = ReactComponentTreeHook.getParentID(id);
|
||||
tree[id] = {
|
||||
displayName: ReactComponentTreeHook.getDisplayName(id),
|
||||
text: ReactComponentTreeHook.getText(id),
|
||||
updateCount: ReactComponentTreeHook.getUpdateCount(id),
|
||||
childIDs: ReactComponentTreeHook.getChildIDs(id),
|
||||
// Text nodes don't have owners but this is close enough.
|
||||
ownerID: ownerID ||
|
||||
parentID && ReactComponentTreeHook.getOwnerID(parentID) ||
|
||||
0,
|
||||
parentID,
|
||||
};
|
||||
return tree;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const resetMeasurements = function() {
|
||||
var previousStartTime = currentFlushStartTime;
|
||||
var previousMeasurements = currentFlushMeasurements;
|
||||
var previousOperations = ReactHostOperationHistoryHook.getHistory();
|
||||
|
||||
if (currentFlushNesting === 0) {
|
||||
currentFlushStartTime = 0;
|
||||
currentFlushMeasurements = [];
|
||||
clearHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousMeasurements.length || previousOperations.length) {
|
||||
var registeredIDs = ReactComponentTreeHook.getRegisteredIDs();
|
||||
flushHistory.push({
|
||||
duration: performanceNow() - previousStartTime,
|
||||
measurements: previousMeasurements || [],
|
||||
operations: previousOperations || [],
|
||||
treeSnapshot: getTreeSnapshot(registeredIDs),
|
||||
});
|
||||
}
|
||||
|
||||
clearHistory();
|
||||
currentFlushStartTime = performanceNow();
|
||||
currentFlushMeasurements = [];
|
||||
};
|
||||
|
||||
const checkDebugID = function(debugID, allowRoot = false) {
|
||||
if (allowRoot && debugID === 0) {
|
||||
return;
|
||||
}
|
||||
if (!debugID) {
|
||||
warning(false, 'ReactDebugTool: debugID may not be empty.');
|
||||
}
|
||||
};
|
||||
|
||||
const beginLifeCycleTimer = function(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code.' +
|
||||
'\n\nDid not expect %s timer to start while %s timer is still in ' +
|
||||
'progress for %s instance.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
(debugID === currentTimerDebugID) ? 'the same' : 'another'
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
currentTimerStartTime = performanceNow();
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
};
|
||||
|
||||
const endLifeCycleTimer = function(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code. ' +
|
||||
'We did not expect %s timer to stop while %s timer is still in ' +
|
||||
'progress for %s instance. Please report this as a bug in React.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
(debugID === currentTimerDebugID) ? 'the same' : 'another'
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
if (isProfiling) {
|
||||
currentFlushMeasurements.push({
|
||||
timerType,
|
||||
instanceID: debugID,
|
||||
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
|
||||
});
|
||||
}
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
};
|
||||
|
||||
const pauseCurrentLifeCycleTimer = function() {
|
||||
var currentTimer = {
|
||||
startTime: currentTimerStartTime,
|
||||
nestedFlushStartTime: performanceNow(),
|
||||
debugID: currentTimerDebugID,
|
||||
timerType: currentTimerType,
|
||||
};
|
||||
lifeCycleTimerStack.push(currentTimer);
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
};
|
||||
|
||||
const resumeCurrentLifeCycleTimer = function() {
|
||||
var {startTime, nestedFlushStartTime, debugID, timerType} = lifeCycleTimerStack.pop();
|
||||
var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
|
||||
currentTimerStartTime = startTime;
|
||||
currentTimerNestedFlushDuration += nestedFlushDuration;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
};
|
||||
|
||||
var lastMarkTimeStamp = 0;
|
||||
var canUsePerformanceMeasure: boolean =
|
||||
typeof performance !== 'undefined' &&
|
||||
typeof performance.mark === 'function' &&
|
||||
typeof performance.clearMarks === 'function' &&
|
||||
typeof performance.measure === 'function' &&
|
||||
typeof performance.clearMeasures === 'function';
|
||||
|
||||
const shouldMark = function(debugID) {
|
||||
if (!isProfiling || !canUsePerformanceMeasure) {
|
||||
return false;
|
||||
}
|
||||
var element = ReactComponentTreeHook.getElement(debugID);
|
||||
if (element == null || typeof element !== 'object') {
|
||||
return false;
|
||||
}
|
||||
var isHostElement = typeof element.type === 'string';
|
||||
if (isHostElement) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const markBegin = function(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
lastMarkTimeStamp = performanceNow();
|
||||
performance.mark(markName);
|
||||
};
|
||||
|
||||
const markEnd = function(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown';
|
||||
|
||||
// Chrome has an issue of dropping markers recorded too fast:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=640652
|
||||
// To work around this, we will not report very small measurements.
|
||||
// I determined the magic number by tweaking it back and forth.
|
||||
// 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe.
|
||||
// When the bug is fixed, we can `measure()` unconditionally if we want to.
|
||||
var timeStamp = performanceNow();
|
||||
if (timeStamp - lastMarkTimeStamp > 0.1) {
|
||||
var measurementName = `${displayName} [${markType}]`;
|
||||
performance.measure(measurementName, markName);
|
||||
}
|
||||
|
||||
performance.clearMarks(markName);
|
||||
performance.clearMeasures(measurementName);
|
||||
};
|
||||
|
||||
ReactDebugTool = {
|
||||
addHook(hook: Hook): void {
|
||||
hooks.push(hook);
|
||||
},
|
||||
removeHook(hook: Hook): void {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
if (hooks[i] === hook) {
|
||||
hooks.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
},
|
||||
isProfiling(): boolean {
|
||||
return isProfiling;
|
||||
},
|
||||
beginProfiling(): void {
|
||||
if (isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = true;
|
||||
flushHistory.length = 0;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.addHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
endProfiling(): void {
|
||||
if (!isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = false;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
getFlushHistory(): FlushHistory {
|
||||
return flushHistory;
|
||||
},
|
||||
onBeginFlush(): void {
|
||||
currentFlushNesting++;
|
||||
resetMeasurements();
|
||||
pauseCurrentLifeCycleTimer();
|
||||
emitEvent('onBeginFlush');
|
||||
},
|
||||
onEndFlush(): void {
|
||||
resetMeasurements();
|
||||
currentFlushNesting--;
|
||||
resumeCurrentLifeCycleTimer();
|
||||
emitEvent('onEndFlush');
|
||||
},
|
||||
onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
|
||||
markBegin(debugID, timerType);
|
||||
beginLifeCycleTimer(debugID, timerType);
|
||||
},
|
||||
onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
endLifeCycleTimer(debugID, timerType);
|
||||
markEnd(debugID, timerType);
|
||||
emitEvent('onEndLifeCycleTimer', debugID, timerType);
|
||||
},
|
||||
onBeginProcessingChildContext(): void {
|
||||
emitEvent('onBeginProcessingChildContext');
|
||||
},
|
||||
onEndProcessingChildContext(): void {
|
||||
emitEvent('onEndProcessingChildContext');
|
||||
},
|
||||
onHostOperation(operation: Operation) {
|
||||
checkDebugID(operation.instanceID);
|
||||
emitEvent('onHostOperation', operation);
|
||||
},
|
||||
onSetState(): void {
|
||||
emitEvent('onSetState');
|
||||
},
|
||||
onSetChildren(debugID: DebugID, childDebugIDs: Array<DebugID>) {
|
||||
checkDebugID(debugID);
|
||||
childDebugIDs.forEach(checkDebugID);
|
||||
emitEvent('onSetChildren', debugID, childDebugIDs);
|
||||
},
|
||||
onBeforeMountComponent(debugID: DebugID, element: ReactElement, parentDebugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
checkDebugID(parentDebugID, true);
|
||||
emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
|
||||
markBegin(debugID, 'mount');
|
||||
},
|
||||
onMountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'mount');
|
||||
emitEvent('onMountComponent', debugID);
|
||||
},
|
||||
onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUpdateComponent', debugID, element);
|
||||
markBegin(debugID, 'update');
|
||||
},
|
||||
onUpdateComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'update');
|
||||
emitEvent('onUpdateComponent', debugID);
|
||||
},
|
||||
onBeforeUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUnmountComponent', debugID);
|
||||
markBegin(debugID, 'unmount');
|
||||
},
|
||||
onUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'unmount');
|
||||
emitEvent('onUnmountComponent', debugID);
|
||||
},
|
||||
onTestEvent(): void {
|
||||
emitEvent('onTestEvent');
|
||||
},
|
||||
};
|
||||
|
||||
ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
|
||||
ReactDebugTool.addHook(ReactComponentTreeHook);
|
||||
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
|
||||
if ((/[?&]react_perf\b/).test(url)) {
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactDebugTool;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
@@ -12,448 +12,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var warning = require('warning');
|
||||
var alreadyWarned = false;
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactDOM');
|
||||
|
||||
import type { FlushHistory } from 'ReactDebugTool';
|
||||
|
||||
function roundFloat(val, base = 2) {
|
||||
var n = Math.pow(10, base);
|
||||
return Math.floor(val * n) / n;
|
||||
}
|
||||
|
||||
// Flow type definition of console.table is too strict right now, see
|
||||
// https://github.com/facebook/flow/pull/2353 for updates
|
||||
function consoleTable(table: Array<{[key: string]: any}>): void {
|
||||
console.table((table: any));
|
||||
}
|
||||
|
||||
function warnInProduction() {
|
||||
if (alreadyWarned) {
|
||||
return;
|
||||
}
|
||||
alreadyWarned = true;
|
||||
if (typeof console !== 'undefined') {
|
||||
console.error(
|
||||
'ReactPerf is not supported in the production builds of React. ' +
|
||||
'To collect measurements, please use the development build of React instead.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getLastMeasurements() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
return ReactDebugTool.getFlushHistory();
|
||||
}
|
||||
|
||||
function getExclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) {
|
||||
var {displayName} = treeSnapshot[instanceID];
|
||||
var key = displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
counts: {},
|
||||
durations: {},
|
||||
totalDuration: 0,
|
||||
};
|
||||
}
|
||||
if (!stats.durations[timerType]) {
|
||||
stats.durations[timerType] = 0;
|
||||
}
|
||||
if (!stats.counts[timerType]) {
|
||||
stats.counts[timerType] = 0;
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => {
|
||||
stats.totalDuration += duration;
|
||||
stats.durations[timerType] += duration;
|
||||
stats.counts[timerType]++;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.totalDuration - a.totalDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getInclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
var isCompositeByID = {};
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
isCompositeByID[instanceID] = true;
|
||||
});
|
||||
});
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// As we traverse parents, only count inclusive time towards composites.
|
||||
// We know something is a composite if its render() was called.
|
||||
if (isCompositeByID[nextParentID]) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.inclusiveRenderDuration - a.inclusiveRenderDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getWasted(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot, operations} = flush;
|
||||
var isDefinitelyNotWastedByID = {};
|
||||
|
||||
// Find host components associated with an operation in this batch.
|
||||
// Mark all components in their parent tree as definitely not wasted.
|
||||
operations.forEach(operation => {
|
||||
var {instanceID} = operation;
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
isDefinitelyNotWastedByID[nextParentID] = true;
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
|
||||
// Find composite components that rendered in this batch.
|
||||
// These are potential candidates for being wasted renders.
|
||||
var renderedCompositeIDs = {};
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
renderedCompositeIDs[instanceID] = true;
|
||||
});
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was a DOM update below this component, or it has just been
|
||||
// mounted, its render() is not considered wasted.
|
||||
var { updateCount } = treeSnapshot[instanceID];
|
||||
if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We consider this render() wasted.
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// Any parents rendered during this batch are considered wasted
|
||||
// unless we previously marked them as dirty.
|
||||
var isWasted =
|
||||
renderedCompositeIDs[nextParentID] &&
|
||||
!isDefinitelyNotWastedByID[nextParentID];
|
||||
if (isWasted) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.inclusiveRenderDuration - a.inclusiveRenderDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getOperations(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var stats = [];
|
||||
flushHistory.forEach((flush, flushIndex) => {
|
||||
var {operations, treeSnapshot} = flush;
|
||||
operations.forEach(operation => {
|
||||
var {instanceID, type, payload} = operation;
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
|
||||
stats.push({
|
||||
flushIndex,
|
||||
instanceID,
|
||||
key,
|
||||
type,
|
||||
ownerID,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
|
||||
function printExclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getExclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, totalDuration} = item;
|
||||
var renderCount = item.counts.render || 0;
|
||||
var renderDuration = item.durations.render || 0;
|
||||
return {
|
||||
'Component': key,
|
||||
'Total time (ms)': roundFloat(totalDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Total render time (ms)': roundFloat(renderDuration),
|
||||
'Average render time (ms)': renderCount ?
|
||||
roundFloat(renderDuration / renderCount) :
|
||||
undefined,
|
||||
'Render count': renderCount,
|
||||
'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration),
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printInclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getInclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printWasted(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getWasted(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printOperations(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getOperations(flushHistory);
|
||||
var table = stats.map(stat => ({
|
||||
'Owner > Node': stat.key,
|
||||
'Operation': stat.type,
|
||||
'Payload': typeof stat.payload === 'object' ?
|
||||
JSON.stringify(stat.payload) :
|
||||
stat.payload,
|
||||
'Flush index': stat.flushIndex,
|
||||
'Owner Component ID': stat.ownerID,
|
||||
'DOM Component ID': stat.instanceID,
|
||||
}));
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
var warnedAboutPrintDOM = false;
|
||||
function printDOM(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutPrintDOM,
|
||||
'`ReactPerf.printDOM(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.printOperations(...)` instead.'
|
||||
);
|
||||
warnedAboutPrintDOM = true;
|
||||
return printOperations(measurements);
|
||||
}
|
||||
|
||||
var warnedAboutGetMeasurementsSummaryMap = false;
|
||||
function getMeasurementsSummaryMap(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutGetMeasurementsSummaryMap,
|
||||
'`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.getWasted(...)` instead.'
|
||||
);
|
||||
warnedAboutGetMeasurementsSummaryMap = true;
|
||||
return getWasted(measurements);
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.endProfiling();
|
||||
}
|
||||
|
||||
function isRunning() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReactDebugTool.isProfiling();
|
||||
}
|
||||
|
||||
var ReactPerfAnalysis = {
|
||||
getLastMeasurements,
|
||||
getExclusive,
|
||||
getInclusive,
|
||||
getWasted,
|
||||
getOperations,
|
||||
printExclusive,
|
||||
printInclusive,
|
||||
printWasted,
|
||||
printOperations,
|
||||
start,
|
||||
stop,
|
||||
isRunning,
|
||||
// Deprecated:
|
||||
printDOM,
|
||||
getMeasurementsSummaryMap,
|
||||
};
|
||||
|
||||
module.exports = ReactPerfAnalysis;
|
||||
module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var { isValidElement } = require('React');
|
||||
var { Children } = require('React');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
@@ -30,7 +30,7 @@ var invariant = require('invariant');
|
||||
*/
|
||||
function onlyChild(children) {
|
||||
invariant(
|
||||
isValidElement(children),
|
||||
Children.isValidElement(children),
|
||||
'React.Children.only expected to receive a single React element child.'
|
||||
);
|
||||
return children;
|
||||
|
||||
@@ -42,6 +42,8 @@ function getExternalModules(bundleType) {
|
||||
|
||||
function getInternalModules() {
|
||||
return {
|
||||
// we tell Rollup where these files are located internally, otherwise
|
||||
// it doesn't pick them up and assumes they're external
|
||||
reactProdInvariant: resolve('./src/shared/utils/reactProdInvariant.js'),
|
||||
'ReactCurrentOwner': resolve('./src/isomorphic/classic/element/ReactCurrentOwner.js'),
|
||||
'ReactComponentTreeHook': resolve('./src/isomorphic/hooks/ReactComponentTreeHook.js'),
|
||||
@@ -65,7 +67,12 @@ function replaceInternalModules(bundleType) {
|
||||
'react-dom': resolve('./src/renderers/dom/ReactDOM.js'),
|
||||
};
|
||||
case bundleTypes.FB:
|
||||
return {};
|
||||
// for FB, we should probably bundle ReactPerf and ReactTestUtils till, but provide
|
||||
// a forwarding module for them
|
||||
return {
|
||||
'react-dom/lib/ReactInstanceMap': resolve('./src/renderers/shared/shared/ReactInstanceMap.js'),
|
||||
'react-dom': resolve('./src/renderers/dom/ReactDOM.js'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +81,8 @@ function getFbjsModuleAliases(bundleType) {
|
||||
case bundleTypes.DEV:
|
||||
case bundleTypes.PROD:
|
||||
return {
|
||||
// we want to bundle these modules, so we re-alias them to the actual
|
||||
// file so Rollup can bundle them up
|
||||
'fbjs/lib/warning': resolve('./node_modules/fbjs/lib/warning.js'),
|
||||
'fbjs/lib/invariant': resolve('./node_modules/fbjs/lib/invariant.js'),
|
||||
'fbjs/lib/emptyFunction': resolve('./node_modules/fbjs/lib/emptyFunction.js'),
|
||||
@@ -93,6 +102,8 @@ function getFbjsModuleAliases(bundleType) {
|
||||
};
|
||||
case bundleTypes.NODE:
|
||||
case bundleTypes.FB:
|
||||
// for FB we don't want to bundle the above modules, instead keep them
|
||||
// as external require() calls in the bundle
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -104,6 +115,8 @@ function replaceFbjsModuleAliases(bundleType) {
|
||||
case bundleTypes.NODE:
|
||||
return {};
|
||||
case bundleTypes.FB:
|
||||
// the diff for Haste to support fbjs/lib/* hasn't landed, so this
|
||||
// re-aliases them back to the non fbjs/lib/* versions
|
||||
return {
|
||||
'fbjs/lib/warning': 'warning',
|
||||
'fbjs/lib/invariant': 'invariant',
|
||||
|
||||
Reference in New Issue
Block a user