Benchmarking tool for React application performance
ReactAppPerf wraps core methods and logs info from them; there's no real UI at this point
Danny Ben-David committed
Aug 21, 2013 at 20:30 UTC
fce57abeca1d8388baaaac8511540aed486177dc
6 files changed
+595
-61
src/core/React.js
+6
-1
@@ -23,6 +23,7 @@ var ReactCompositeComponent = require('ReactCompositeComponent');
23
var ReactComponent = require('ReactComponent');
24
var ReactDOM = require('ReactDOM');
25
var ReactMount = require('ReactMount');
26
+var ReactPerf = require('ReactPerf');
27
var ReactPropTypes = require('ReactPropTypes');
28
var ReactServerRendering = require('ReactServerRendering');
29
@@ -42,7 +43,11 @@ var React = {
43
constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
44
forEachChildren: ReactChildren.forEach,
45
mapChildren: ReactChildren.map,
45
- renderComponent: ReactMount.renderComponent,
46
+ renderComponent: ReactPerf.measure(
47
+ 'React',
48
+ 'renderComponent',
49
+ ReactMount.renderComponent
50
+ ),
51
renderComponentToString: ReactServerRendering.renderComponentToString,
52
unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
53
isValidComponent: ReactComponent.isValidComponent
src/core/ReactCompositeComponent.js
+55
-46
@@ -21,6 +21,7 @@
21
var ReactComponent = require('ReactComponent');
22
var ReactCurrentOwner = require('ReactCurrentOwner');
23
var ReactOwner = require('ReactOwner');
24
+var ReactPerf = require('ReactPerf');
25
var ReactPropTransferer = require('ReactPropTransferer');
26
var ReactUpdates = require('ReactUpdates');
27
@@ -479,41 +480,45 @@ var ReactCompositeComponentMixin = {
480
* @final
481
* @internal
482
*/
482
- mountComponent: function(rootID, transaction) {
483
- ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
484
- this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
483
+ mountComponent: ReactPerf.measure(
484
+ 'ReactCompositeComponent',
485
+ 'mountComponent',
486
+ function(rootID, transaction) {
487
+ ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
488
+ this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
489
486
- this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
487
- this._processProps(this.props);
490
+ this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
491
+ this._processProps(this.props);
492
489
- if (this.__reactAutoBindMap) {
490
- this._bindAutoBindMethods();
491
- }
493
+ if (this.__reactAutoBindMap) {
494
+ this._bindAutoBindMethods();
495
+ }
496
493
- this.state = this.getInitialState ? this.getInitialState() : null;
494
- this._pendingState = null;
495
- this._pendingForceUpdate = false;
496
-
497
- if (this.componentWillMount) {
498
- this.componentWillMount();
499
- // When mounting, calls to `setState` by `componentWillMount` will set
500
- // `this._pendingState` without triggering a re-render.
501
- if (this._pendingState) {
502
- this.state = this._pendingState;
503
- this._pendingState = null;
497
+ this.state = this.getInitialState ? this.getInitialState() : null;
498
+ this._pendingState = null;
499
+ this._pendingForceUpdate = false;
500
+
501
+ if (this.componentWillMount) {
502
+ this.componentWillMount();
503
+ // When mounting, calls to `setState` by `componentWillMount` will set
504
+ // `this._pendingState` without triggering a re-render.
505
+ if (this._pendingState) {
506
+ this.state = this._pendingState;
507
+ this._pendingState = null;
508
+ }
509
}
505
- }
510
507
- this._renderedComponent = this._renderValidatedComponent();
511
+ this._renderedComponent = this._renderValidatedComponent();
512
509
- // Done with mounting, `setState` will now trigger UI changes.
510
- this._compositeLifeCycleState = null;
511
- var markup = this._renderedComponent.mountComponent(rootID, transaction);
512
- if (this.componentDidMount) {
513
- transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
513
+ // Done with mounting, `setState` will now trigger UI changes.
514
+ this._compositeLifeCycleState = null;
515
+ var markup = this._renderedComponent.mountComponent(rootID, transaction);
516
+ if (this.componentDidMount) {
517
+ transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
518
+ }
519
+ return markup;
520
}
515
- return markup;
516
- },
521
+ ),
522
523
/**
524
* Releases any resources allocated by `mountComponent`.
@@ -714,25 +719,29 @@ var ReactCompositeComponentMixin = {
719
* @internal
720
* @overridable
721
*/
717
- updateComponent: function(transaction, prevProps, prevState) {
718
- ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
719
- var currentComponent = this._renderedComponent;
720
- var nextComponent = this._renderValidatedComponent();
721
- if (currentComponent.constructor === nextComponent.constructor) {
722
- currentComponent.receiveProps(nextComponent.props, transaction);
723
- } else {
724
- // These two IDs are actually the same! But nothing should rely on that.
725
- var thisID = this._rootNodeID;
726
- var currentComponentID = currentComponent._rootNodeID;
727
- currentComponent.unmountComponent();
728
- var nextMarkup = nextComponent.mountComponent(thisID, transaction);
729
- ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
730
- currentComponentID,
731
- nextMarkup
732
- );
733
- this._renderedComponent = nextComponent;
722
+ updateComponent: ReactPerf.measure(
723
+ 'ReactCompositeComponent',
724
+ 'updateComponent',
725
+ function(transaction, prevProps, prevState) {
726
+ ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
727
+ var currentComponent = this._renderedComponent;
728
+ var nextComponent = this._renderValidatedComponent();
729
+ if (currentComponent.constructor === nextComponent.constructor) {
730
+ currentComponent.receiveProps(nextComponent.props, transaction);
731
+ } else {
732
+ // These two IDs are actually the same! But nothing should rely on that.
733
+ var thisID = this._rootNodeID;
734
+ var currentComponentID = currentComponent._rootNodeID;
735
+ currentComponent.unmountComponent();
736
+ var nextMarkup = nextComponent.mountComponent(thisID, transaction);
737
+ ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
738
+ currentComponentID,
739
+ nextMarkup
740
+ );
741
+ this._renderedComponent = nextComponent;
742
+ }
743
}
735
- },
744
+ ),
745
746
/**
747
* Forces an update. This should only be invoked when it is known with
src/core/ReactDefaultInjection.js
+5
@@ -26,6 +26,7 @@ var ReactDOMSelect = require('ReactDOMSelect');
26
var ReactDOMTextarea = require('ReactDOMTextarea');
27
var ReactEventEmitter = require('ReactEventEmitter');
28
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
29
+var ReactPerf = require('ReactPerf');
30
31
var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
32
var DOMProperty = require('DOMProperty');
@@ -66,6 +67,10 @@ function inject() {
67
});
68
69
DOMProperty.injection.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
70
+
71
+ if (__DEV__) {
72
+ ReactPerf.injection.injectMeasure(require('ReactDefaultPerf').measure);
73
+ }
74
}
75
76
module.exports = {
src/core/ReactNativeComponent.js
+23
-14
@@ -26,6 +26,7 @@ var ReactComponent = require('ReactComponent');
26
var ReactEventEmitter = require('ReactEventEmitter');
27
var ReactMultiChild = require('ReactMultiChild');
28
var ReactMount = require('ReactMount');
29
+var ReactPerf = require('ReactPerf');
30
31
var escapeTextForBrowser = require('escapeTextForBrowser');
32
var flattenChildren = require('flattenChildren');
@@ -85,15 +86,19 @@ ReactNativeComponent.Mixin = {
86
* @param {ReactReconcileTransaction} transaction
87
* @return {string} The computed markup.
88
*/
88
- mountComponent: function(rootID, transaction) {
89
- ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
90
- assertValidProps(this.props);
91
- return (
92
- this._createOpenTagMarkup() +
93
- this._createContentMarkup(transaction) +
94
- this._tagClose
95
- );
96
- },
89
+ mountComponent: ReactPerf.measure(
90
+ 'ReactNativeComponent',
91
+ 'mountComponent',
92
+ function(rootID, transaction) {
93
+ ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
94
+ assertValidProps(this.props);
95
+ return (
96
+ this._createOpenTagMarkup() +
97
+ this._createContentMarkup(transaction) +
98
+ this._tagClose
99
+ );
100
+ }
101
+ ),
102
103
/**
104
* Creates markup for the open tag and all attributes.
@@ -184,11 +189,15 @@ ReactNativeComponent.Mixin = {
189
* @internal
190
* @overridable
191
*/
187
- updateComponent: function(transaction, prevProps) {
188
- ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
189
- this._updateDOMProperties(prevProps);
190
- this._updateDOMChildren(prevProps, transaction);
191
- },
192
+ updateComponent: ReactPerf.measure(
193
+ 'ReactNativeComponent',
194
+ 'updateComponent',
195
+ function(transaction, prevProps) {
196
+ ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
197
+ this._updateDOMProperties(prevProps);
198
+ this._updateDOMChildren(prevProps, transaction);
199
+ }
200
+ ),
201
202
/**
203
* Reconciles the properties by detecting differences in property values and
src/test/ReactDefaultPerf.js
new
+416
@@ -0,0 +1,416 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactDefaultPerf
17
+ * @typechecks static-only
18
+ */
19
+
20
+"use strict";
21
+
22
+var ReactDefaultPerf = {};
23
+
24
+if (__DEV__) {
25
+ ReactDefaultPerf = {
26
+ /**
27
+ * Gets the stored information for a given object's function.
28
+ *
29
+ * @param {string} objName
30
+ * @param {string} fnName
31
+ * @return {?object}
32
+ */
33
+ getInfo: function(objName, fnName) {
34
+ if (!this.info[objName] || !this.info[objName][fnName]) {
35
+ return null;
36
+ }
37
+ return this.info[objName][fnName];
38
+ },
39
+
40
+ /**
41
+ * Gets the logs pertaining to a given object's function.
42
+ *
43
+ * @param {string} objName
44
+ * @param {string} fnName
45
+ * @return {?array<object>}
46
+ */
47
+ getLogs: function(objName, fnName) {
48
+ if (!this.getInfo(objName, fnName)) {
49
+ return null;
50
+ }
51
+ return this.logs.filter(function(log) {
52
+ return log.objName === objName && log.fnName === fnName;
53
+ });
54
+ },
55
+
56
+ /**
57
+ * Runs through the logs and builds an array of arrays, where each array
58
+ * walks through the mounting/updating of each component underneath.
59
+ *
60
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
61
+ * @return {array<array>}
62
+ */
63
+ getRawRenderHistory: function(rootID) {
64
+ var history = [];
65
+ /**
66
+ * Since logs are added after the method returns, the logs are in a sense
67
+ * upside-down: the inner-most elements from mounting/updating are logged
68
+ * first, and the last addition to the log is the top renderComponent.
69
+ * Therefore, we flip the logs upside down for ease of processing, and
70
+ * reverse the history array at the end so the earliest event has index 0.
71
+ */
72
+ var logs = this.logs.filter(function(log) {
73
+ return log.reactID.indexOf(rootID) === 0;
74
+ }).reverse();
75
+
76
+ var subHistory = [];
77
+ logs.forEach(function(log, i) {
78
+ if (i && log.reactID === rootID && logs[i - 1].reactID !== rootID) {
79
+ subHistory.length && history.push(subHistory);
80
+ subHistory = [];
81
+ }
82
+ subHistory.push(log);
83
+ });
84
+ if (subHistory.length) {
85
+ history.push(subHistory);
86
+ }
87
+ return history.reverse();
88
+ },
89
+
90
+ /**
91
+ * Runs through the logs and builds an array of strings, where each string
92
+ * is a multiline formatted way of walking through the mounting/updating
93
+ * underneath.
94
+ *
95
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
96
+ * @return {array<string>}
97
+ */
98
+ getRenderHistory: function(rootID) {
99
+ var history = this.getRawRenderHistory(rootID);
100
+
101
+ return history.map(function(subHistory) {
102
+ var headerString = (
103
+ 'log# Component (execution time) [bloat from logging]\n' +
104
+ '================================================================\n'
105
+ );
106
+ return headerString + subHistory.map(function(log) {
107
+ // Add two spaces for every layer in the reactID.
108
+ var indents = '\t' + Array(log.reactID.split('.[').length).join(' ');
109
+ var delta = _microTime(log.timing.delta);
110
+ var bloat = _microTime(log.timing.timeToLog);
111
+
112
+ return log.index + indents + log.name + ' (' + delta + 'ms)' +
113
+ ' [' + bloat + 'ms]';
114
+ }).join('\n');
115
+ });
116
+ },
117
+
118
+ /**
119
+ * Print the render history from `getRenderHistory` using console.log.
120
+ * This is currently the best way to display perf data from
121
+ * any React component; working on that.
122
+ *
123
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
124
+ * @param {number} index
125
+ */
126
+ printRenderHistory: function(rootID, index) {
127
+ var history = this.getRenderHistory(rootID);
128
+ if (!history[index]) {
129
+ console.warn(
130
+ 'Index', index, 'isn\'t available! ' +
131
+ 'The render history is', history.length, 'long.'
132
+ );
133
+ return;
134
+ }
135
+ console.log(
136
+ 'Loading render history #' + (index + 1) +
137
+ ' of ' + history.length + ':\n' + history[index]
138
+ );
139
+ },
140
+
141
+ /**
142
+ * Prints the heatmap legend to console, showing how the colors correspond
143
+ * with render times. This relies on console.log styles.
144
+ */
145
+ printHeatmapLegend: function() {
146
+ if (!this.options.heatmap.enabled) {
147
+ return;
148
+ }
149
+ var max = this.info.React
150
+ && this.info.React.renderComponent
151
+ && this.info.React.renderComponent.max;
152
+ if (max) {
153
+ var logStr = 'Heatmap: ';
154
+ for (var ii = 0; ii <= 10 * max; ii += max) {
155
+ logStr += '%c ' + (Math.round(ii) / 10) + 'ms ';
156
+ }
157
+ console.log(
158
+ logStr,
159
+ 'background-color: hsla(100, 100%, 50%, 0.6);',
160
+ 'background-color: hsla( 90, 100%, 50%, 0.6);',
161
+ 'background-color: hsla( 80, 100%, 50%, 0.6);',
162
+ 'background-color: hsla( 70, 100%, 50%, 0.6);',
163
+ 'background-color: hsla( 60, 100%, 50%, 0.6);',
164
+ 'background-color: hsla( 50, 100%, 50%, 0.6);',
165
+ 'background-color: hsla( 40, 100%, 50%, 0.6);',
166
+ 'background-color: hsla( 30, 100%, 50%, 0.6);',
167
+ 'background-color: hsla( 20, 100%, 50%, 0.6);',
168
+ 'background-color: hsla( 10, 100%, 50%, 0.6);',
169
+ 'background-color: hsla( 0, 100%, 50%, 0.6);'
170
+ );
171
+ }
172
+ },
173
+
174
+ /**
175
+ * Measure a given function with logging information, and calls a callback
176
+ * if there is one.
177
+ *
178
+ * @param {string} objName
179
+ * @param {string} fnName
180
+ * @param {function} func
181
+ * @return {function}
182
+ */
183
+ measure: function(objName, fnName, func) {
184
+ var info = _getNewInfo(objName, fnName);
185
+
186
+ var fnArgs = _getFnArguments(func);
187
+
188
+ return function() {
189
+ var timeBeforeFn = now();
190
+ var fnReturn = func.apply(this, arguments);
191
+ var timeAfterFn = now();
192
+
193
+ /**
194
+ * Hold onto arguments in a readable way: args[1] -> args.component.
195
+ * args is also passed to the callback, so if you want to save an
196
+ * argument in the log, do so in the callback.
197
+ */
198
+ var args = {};
199
+ for (var i = 0; i < arguments.length; i++) {
200
+ args[fnArgs[i]] = arguments[i];
201
+ }
202
+
203
+ var log = {
204
+ index: ReactDefaultPerf.logs.length,
205
+ fnName: fnName,
206
+ objName: objName,
207
+ timing: {
208
+ before: timeBeforeFn,
209
+ after: timeAfterFn,
210
+ delta: timeAfterFn - timeBeforeFn
211
+ }
212
+ };
213
+
214
+ ReactDefaultPerf.logs.push(log);
215
+
216
+ /**
217
+ * The callback gets:
218
+ * - this (the component)
219
+ * - the original method's arguments
220
+ * - what the method returned
221
+ * - the log object, and
222
+ * - the wrapped method's info object.
223
+ */
224
+ var callback = _getCallback(objName, fnName);
225
+ callback && callback(this, args, fnReturn, log, info);
226
+
227
+ log.timing.timeToLog = now() - timeAfterFn;
228
+
229
+ return fnReturn;
230
+ };
231
+ },
232
+
233
+ /**
234
+ * Holds information on wrapped objects/methods.
235
+ * For instance, ReactDefaultPerf.info.React.renderComponent
236
+ */
237
+ info: {},
238
+
239
+ /**
240
+ * Holds all of the logs. Filter this to pull desired information.
241
+ */
242
+ logs: [],
243
+
244
+ /**
245
+ * Toggle settings for ReactDefaultPerf
246
+ */
247
+ options: {
248
+ /**
249
+ * The heatmap sets the background color of the React containers
250
+ * according to how much total time has been spent rendering them.
251
+ * The most temporally expensive component is set as pure red,
252
+ * and the others are colored from green to red as a fraction
253
+ * of that max component time.
254
+ */
255
+ heatmap: {
256
+ enabled: true
257
+ }
258
+ }
259
+ };
260
+
261
+ /**
262
+ * Gets a info area for a given object's function, adding a new one if
263
+ * necessary.
264
+ *
265
+ * @param {string} objName
266
+ * @param {string} fnName
267
+ * @return {object}
268
+ */
269
+ var _getNewInfo = function(objName, fnName) {
270
+ var info = ReactDefaultPerf.getInfo(objName, fnName);
271
+ if (info) {
272
+ return info;
273
+ }
274
+ ReactDefaultPerf.info[objName] = ReactDefaultPerf.info[objName] || {};
275
+
276
+ return ReactDefaultPerf.info[objName][fnName] = {
277
+ getLogs: function() {
278
+ return ReactDefaultPerf.getLogs(objName, fnName);
279
+ }
280
+ };
281
+ };
282
+
283
+ /**
284
+ * Gets a list of the argument names from a function's definition.
285
+ * This is useful for storing arguments by their names within wrapFn().
286
+ *
287
+ * @param {function} fn
288
+ * @return {array<string>}
289
+ */
290
+ var _getFnArguments = function(fn) {
291
+ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
292
+ var fnStr = fn.toString().replace(STRIP_COMMENTS, '');
293
+ fnStr = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'));
294
+ return fnStr.match(/([^\s,]+)/g);
295
+ };
296
+
297
+ /**
298
+ * Store common callbacks within ReactDefaultPerf.
299
+ *
300
+ * @param {string} objName
301
+ * @param {string} fnName
302
+ * @return {?function}
303
+ */
304
+ var _getCallback = function(objName, fnName) {
305
+ switch (objName + '.' + fnName) {
306
+ case 'React.renderComponent':
307
+ return _renderComponentCallback;
308
+ case 'ReactNativeComponent.mountComponent':
309
+ case 'ReactNativeComponent.updateComponent':
310
+ return _nativeComponentCallback;
311
+ case 'ReactCompositeComponent.mountComponent':
312
+ case 'ReactCompositeComponent.updateComponent':
313
+ return _compositeComponentCallback;
314
+ default:
315
+ return null;
316
+ }
317
+ };
318
+
319
+ /**
320
+ * Callback function for React.renderComponent
321
+ *
322
+ * @param {object} component
323
+ * @param {object} args
324
+ * @param {?object} fnReturn
325
+ * @param {object} log
326
+ * @param {object} info
327
+ */
328
+ var _renderComponentCallback =
329
+ function(component, args, fnReturn, log, info) {
330
+ log.name = args.nextComponent.constructor.displayName || '[unknown]';
331
+ log.reactID = fnReturn._rootNodeID || null;
332
+
333
+ if (ReactDefaultPerf.options.heatmap.enabled) {
334
+ var container = args.container;
335
+ if (!container.loggedByReactDefaultPerf) {
336
+ container.loggedByReactDefaultPerf = true;
337
+ info.components = info.components || [];
338
+ info.components.push(container);
339
+ }
340
+
341
+ container.count = container.count || 0;
342
+ container.count += log.timing.delta;
343
+ info.max = info.max || 0;
344
+ if (container.count > info.max) {
345
+ info.max = container.count;
346
+ info.components.forEach(function(component) {
347
+ _setHue(component, 100 - 100 * component.count / info.max);
348
+ });
349
+ } else {
350
+ _setHue(container, 100 - 100 * container.count / info.max);
351
+ }
352
+ }
353
+ };
354
+
355
+ /**
356
+ * Callback function for ReactNativeComponent
357
+ *
358
+ * @param {object} component
359
+ * @param {object} args
360
+ * @param {?object} fnReturn
361
+ * @param {object} log
362
+ * @param {object} info
363
+ */
364
+ var _nativeComponentCallback =
365
+ function(component, args, fnReturn, log, info) {
366
+ log.name = component.tagName || '[unknown]';
367
+ log.reactID = component._rootNodeID;
368
+ };
369
+
370
+ /**
371
+ * Callback function for ReactCompositeComponent
372
+ *
373
+ * @param {object} component
374
+ * @param {object} args
375
+ * @param {?object} fnReturn
376
+ * @param {object} log
377
+ * @param {object} info
378
+ */
379
+ var _compositeComponentCallback =
380
+ function(component, args, fnReturn, log, info) {
381
+ log.name = component.constructor.displayName || '[unknown]';
382
+ log.reactID = component._rootNodeID;
383
+ };
384
+
385
+ /**
386
+ * Using the hsl() background-color attribute, colors an element.
387
+ *
388
+ * @param {DOMElement} el
389
+ * @param {number} hue [0 for red, 120 for green, 240 for blue]
390
+ */
391
+ var _setHue = function(el, hue) {
392
+ el.style.backgroundColor = 'hsla(' + hue + ', 100%, 50%, 0.6)';
393
+ };
394
+
395
+ /**
396
+ * Round to the thousandth place.
397
+ * @param {number} time
398
+ * @return {number}
399
+ */
400
+ var _microTime = function(time) {
401
+ return Math.round(time * 1000) / 1000;
402
+ };
403
+
404
+ /**
405
+ * Shim window.performance.now
406
+ * We can't assign window.performance.now and then call it, so need to bind.
407
+ * TODO: Support Firefox < 15 for now
408
+ */
409
+ var performance = window && (window.performance || window.webkitPeformance);
410
+ if (!performance || !performance.now) {
411
+ performance = Date;
412
+ }
413
+ var now = performance.now.bind(performance);
414
+}
415
+
416
+module.exports = ReactDefaultPerf;
src/test/ReactPerf.js
new
+90
@@ -0,0 +1,90 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactPerf
17
+ * @typechecks static-only
18
+ */
19
+
20
+"use strict";
21
+
22
+var ReactPerf = {
23
+ /**
24
+ * Boolean to enable/disable measurement. Set to false by default to prevent
25
+ * accidental logging and perf loss.
26
+ */
27
+ enableMeasure: false,
28
+
29
+ /**
30
+ * Holds onto the measure function in use. By default, don't measure
31
+ * anything, but we'll override this if we inject a measure function.
32
+ */
33
+ storedMeasure: _noMeasure,
34
+
35
+ /**
36
+ * Use this to wrap methods you want to measure.
37
+ *
38
+ * @param {string} objName
39
+ * @param {string} fnName
40
+ * @param {function} func
41
+ * @return {function}
42
+ */
43
+ measure: function(objName, fnName, func) {
44
+ if (__DEV__) {
45
+ if (this.enableMeasure) {
46
+ var measuredFunc = null;
47
+ return function() {
48
+ if (!measuredFunc) {
49
+ measuredFunc = ReactPerf.storedMeasure(objName, fnName, func);
50
+ }
51
+ return measuredFunc.apply(this, arguments);
52
+ };
53
+ }
54
+ }
55
+ return func;
56
+ },
57
+
58
+ injection: {
59
+ /**
60
+ * @param {function} measure
61
+ */
62
+ injectMeasure: function(measure) {
63
+ ReactPerf.storedMeasure = measure;
64
+ }
65
+ }
66
+};
67
+
68
+if (__DEV__) {
69
+ var ExecutionEnvironment = require('ExecutionEnvironment');
70
+ var URL = ExecutionEnvironment.global &&
71
+ ExecutionEnvironment.global.location &&
72
+ ExecutionEnvironment.global.location.href ||
73
+ '';
74
+ ReactPerf.enableMeasure = ReactPerf.enableMeasure ||
75
+ !!URL.match(/[?&]react_perf\b/);
76
+}
77
+
78
+/**
79
+ * Simply passes through the measured function, without measuring it.
80
+ *
81
+ * @param {string} objName
82
+ * @param {string} fnName
83
+ * @param {function} func
84
+ * @return {function}
85
+ */
86
+function _noMeasure(objName, fnName, func) {
87
+ return func;
88
+}
89
+
90
+module.exports = ReactPerf;