├── .babelrc ├── media ├── logo.png ├── logo-cli.png └── screenshot.png ├── index.js ├── src ├── getDisplayName.js ├── shouldInclude.js ├── normalizeOptions.js ├── deepDiff.js └── index.js ├── webpack.config.js ├── .gitignore ├── package.json ├── test └── index.js ├── README.md └── main.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/reactopt/HEAD/media/logo.png -------------------------------------------------------------------------------- /media/logo-cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/reactopt/HEAD/media/logo-cli.png -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/reactopt/HEAD/media/screenshot.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | let reactopt = require('./src/index.js').whyDidYouUpdate; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | exports.reactopt = reactopt; 8 | -------------------------------------------------------------------------------- /src/getDisplayName.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var getDisplayName = function getDisplayName(o) { 7 | return o.displayName || o.constructor.displayName || o.constructor.name; 8 | }; 9 | exports.getDisplayName = getDisplayName; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: "./main.js", 5 | output: { 6 | path: __dirname, 7 | filename: "bundle.js" 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | exclude: /node_modules/, 14 | use: [ 15 | 'babel-loader', 16 | ], 17 | }, 18 | ], 19 | }, 20 | resolve: { 21 | modules: [ 22 | path.join(__dirname, 'node_modules'), 23 | ], 24 | }, 25 | }; -------------------------------------------------------------------------------- /src/shouldInclude.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _lodashSome = require('lodash/some'); 10 | 11 | var _lodashSome2 = _interopRequireDefault(_lodashSome); 12 | 13 | var shouldInclude = function shouldInclude(displayName, _ref) { 14 | var include = _ref.include; 15 | var exclude = _ref.exclude; 16 | 17 | return (0, _lodashSome2['default'])(include, function (r) { 18 | return r.test(displayName); 19 | }) && !(0, _lodashSome2['default'])(exclude, function (r) { 20 | return r.test(displayName); 21 | }); 22 | }; 23 | exports.shouldInclude = shouldInclude; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .DS_Store 60 | .vscode 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactopt", 3 | "version": "1.5.1", 4 | "description": "React performance optimization CLI", 5 | "homepage": "https://github.com/reactopt/reactopt#readme", 6 | "bugs": { 7 | "url": "https://github.com/reactopt/reactopt/issues", 8 | "email": "reactopt@gmail.com" 9 | }, 10 | "license": "MIT", 11 | "contributers": [ 12 | "Candace Rogers", 13 | "Pam Lam", 14 | "Vu Phung", 15 | "Selina Zawacki" 16 | ], 17 | "main": "index.js", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/reactopt/reactopt.git" 21 | }, 22 | "scripts": { 23 | "reactopt": "babel-node main.js", 24 | "test": "mocha -c test/index.js" 25 | }, 26 | "devDependencies": { 27 | "chai": "^4.1.2", 28 | "mocha": "^4.0.1", 29 | "sinon": "^4.1.2", 30 | "sinon-chai": "^2.14.0" 31 | }, 32 | "dependencies": { 33 | "babel-cli": "^6.26.0", 34 | "babel-core": "^6.26.0", 35 | "babel-loader": "^7.1.2", 36 | "babel-preset-es2015": "^6.24.1", 37 | "chalk": "2.3.0", 38 | "console-png": "^1.2.1", 39 | "lodash": "^4.13.1", 40 | "lodash-webpack-plugin": "^0.8.1", 41 | "puppeteer": "^0.12.0", 42 | "react": "^15.0 || 0.14.x", 43 | "react-dom": "^16.0.0", 44 | "webpack": "^3.8.1" 45 | }, 46 | "engines": { 47 | "node": ">=4.2.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/normalizeOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _lodashIsString = require('lodash/isString'); 10 | 11 | var _lodashIsString2 = _interopRequireDefault(_lodashIsString); 12 | 13 | var DEFAULT_INCLUDE = /./; 14 | exports.DEFAULT_INCLUDE = DEFAULT_INCLUDE; 15 | var DEFAULT_EXCLUDE = /[^a-zA-Z0-9]/; 16 | 17 | exports.DEFAULT_EXCLUDE = DEFAULT_EXCLUDE; 18 | var toRegExp = function toRegExp(s) { 19 | return (0, _lodashIsString2['default'])(s) ? new RegExp('^' + s + '$') : s; 20 | }; 21 | var toArray = function toArray(o) { 22 | return o ? [].concat(o) : []; 23 | }; 24 | 25 | var normalizeOptions = function normalizeOptions() { 26 | var opts = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 27 | var _opts$include = opts.include; 28 | var include = _opts$include === undefined ? [DEFAULT_INCLUDE] : _opts$include; 29 | var _opts$exclude = opts.exclude; 30 | var exclude = _opts$exclude === undefined ? [DEFAULT_EXCLUDE] : _opts$exclude; 31 | var _opts$groupByComponent = opts.groupByComponent; 32 | var groupByComponent = _opts$groupByComponent === undefined ? true : _opts$groupByComponent; 33 | var _opts$collapseComponentGroups = opts.collapseComponentGroups; 34 | var collapseComponentGroups = _opts$collapseComponentGroups === undefined ? true : _opts$collapseComponentGroups; 35 | var _opts$notifier = opts.notifier; 36 | 37 | return { 38 | include: toArray(include).map(toRegExp), 39 | exclude: toArray(exclude).map(toRegExp), 40 | groupByComponent: groupByComponent, 41 | collapseComponentGroups: collapseComponentGroups 42 | }; 43 | }; 44 | exports.normalizeOptions = normalizeOptions; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const expect = require('chai').expect; 3 | const main = require('./../main.js'); 4 | const sinon = require('sinon'); 5 | const sinonChai = require('sinon-chai'); 6 | chai.use(sinonChai); 7 | 8 | 9 | // object test 10 | describe('initial data object from main.js', () => { 11 | it('data property of "type" is an object', () => { 12 | expect(main.data).to.be.a('object'); 13 | }); 14 | it('data property of "type" is not an array', () => { 15 | expect(Array.isArray(main.data)).to.equal(false); 16 | }); 17 | it('data property of "time" value is "0ms"', () => { 18 | expect(main.data.time).to.equal('0ms'); 19 | }); 20 | it('data property of "rerenders" is an Array', () => { 21 | expect(Array.isArray(main.data.rerenders)).to.equal(true); 22 | }); 23 | it('rerenders property of "type" is a string value = "initialLoad"', () => { 24 | expect((main.data.rerenders[0]).type).to.equal('initialLoad'); 25 | }); 26 | it('rerenders property of "name" is a string value = "initialLoad"', () => { 27 | expect((main.data.rerenders[0]).name).to.equal('initialLoad'); 28 | }); 29 | it('rerenders property of "component" is an empty array length of 0', () => { 30 | expect((main.data.rerenders[0]).components.length).to.equal(0); 31 | }); 32 | }) 33 | // function test 34 | describe('printLine()', () => { 35 | const copyPrintLine = main.printLine; 36 | it('check execution of print line function', () => { 37 | let spy = sinon.spy(); 38 | spy(copyPrintLine); 39 | expect(spy.calledOnce).to.equal(true); 40 | spy.reset(); 41 | }); 42 | }); 43 | 44 | describe('loadTime()', () => { 45 | const copyLoadTime = main.loadTime; 46 | it('check execution of load time function', () => { 47 | let spy = sinon.spy(); 48 | spy(copyLoadTime); 49 | expect(spy.calledOnce).to.equal(true); 50 | spy.reset(); 51 | }); 52 | }); 53 | 54 | describe('componentRerenders()', () => { 55 | const copyComponentRerenders = main.componentRerenders; 56 | it('check execution of component rerenders function', () => { 57 | let spy = sinon.spy(); 58 | // copyPrintLine('heading', 'Page Load Time'); 59 | spy(copyComponentRerenders); 60 | expect(spy.calledOnce).to.equal(true); 61 | spy.reset(); 62 | }); 63 | }); 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | reactopt 3 |
4 |
5 |

6 | 7 | # reactopt 8 | [![npm version](https://badge.fury.io/js/reactopt.svg)](https://badge.fury.io/js/reactopt) 9 | 10 | A CLI React performance optimization tool that identifies potential unnecessary re-rendering. 11 | 12 | # About 13 | Reactopt identifies specific events that may be causing unnecessary re-rendering of components in your application, and which components may benefit from utilizing shouldComponentUpdate. 14 | 15 | Prior to React 16, the module react-addons-perf helped identify locations that developers may want to implement shouldComponentUpdate to limit over-rendering. However, since the module is no longer supported we created Reactopt to fill the gap, and also provide increased functionality for any version of React. 16 | 17 | Upon initiating Reactopt, your application will be launched in a browser for you to interact with. After you're finished and type 'done', you will see an audit on your application's component performance. 18 | 19 | 1.5.1 is the first working verison of this module. 20 | 21 |

reactopt-screenshot

22 | 23 | ## Install and Use 24 | npm install 25 | ```bash 26 | npm install --save-dev reactopt 27 | ``` 28 | 29 | Include this code at the top of your main React component file: 30 | ```js 31 | import { reactopt } from 'reactopt'; 32 | reactopt(React); 33 | ``` 34 | 35 | Include this script in your package.json: 36 | ```js 37 | "reactopt": "node node_modules/reactopt/main.js" 38 | ``` 39 | 40 | Run command 41 | ```bash 42 | npm run reactopt localhost:#### 43 | ``` 44 | 45 | ## Team 46 | This module was created by [Candace Rogers](https://github.com/candacerogue), [Pam Lam](https://github.com/itspamlam), [Vu Phung](https://github.com/Jin6Coding), [Selina Zawacki](https://github.com/szmoon) 47 | 48 | ## Contact 49 | Like our app, found a bug? 50 | 51 | Let us know! 52 | 53 | [reactopt@gmail.com](reactopt@gmail.com) 54 | 55 | Visit us at www.reactopt.com 56 | 57 | ## Credit 58 | Utilizes a modified version of ([why-did-you-update by maicki](https://github.com/maicki/why-did-you-update)) 59 | -------------------------------------------------------------------------------- /src/deepDiff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _lodashIsEqual = require('lodash/isEqual'); 10 | 11 | var _lodashIsEqual2 = _interopRequireDefault(_lodashIsEqual); 12 | 13 | var _lodashIsFunction = require('lodash/isFunction'); 14 | 15 | var _lodashIsFunction2 = _interopRequireDefault(_lodashIsFunction); 16 | 17 | var _lodashKeys = require('lodash/keys'); 18 | 19 | var _lodashKeys2 = _interopRequireDefault(_lodashKeys); 20 | 21 | var _lodashUnion = require('lodash/union'); 22 | 23 | var _lodashUnion2 = _interopRequireDefault(_lodashUnion); 24 | 25 | var _lodashFilter = require('lodash/filter'); 26 | 27 | var _lodashFilter2 = _interopRequireDefault(_lodashFilter); 28 | 29 | var _lodashEvery = require('lodash/every'); 30 | 31 | var _lodashEvery2 = _interopRequireDefault(_lodashEvery); 32 | 33 | var _lodashPick = require('lodash/pick'); 34 | 35 | var _lodashPick2 = _interopRequireDefault(_lodashPick); 36 | 37 | var DIFF_TYPES = { 38 | UNAVOIDABLE: 'unavoidable', 39 | SAME: 'same', 40 | EQUAL: 'equal', 41 | FUNCTIONS: 'functions' 42 | }; 43 | 44 | exports.DIFF_TYPES = DIFF_TYPES; 45 | // called when componentDidUpdate is called (which means comp rerendered) 46 | // comparing previous state to next state, if they're the same, return console.log with this info 47 | var classifyDiff = function classifyDiff(prev, next, name) { 48 | // UNKNOWN for now, but guess is one is for state and one for props 49 | if (prev === next) { 50 | return { 51 | type: DIFF_TYPES.SAME, 52 | name: name, 53 | prev: prev, 54 | next: next 55 | }; 56 | } 57 | 58 | // 59 | if ((0, _lodashIsEqual2['default'])(prev, next)) { 60 | return { 61 | type: DIFF_TYPES.EQUAL, 62 | name: name, 63 | prev: prev, 64 | next: next 65 | }; 66 | } 67 | 68 | if (!prev || !next) { 69 | return { 70 | type: DIFF_TYPES.UNAVOIDABLE, 71 | name: name, 72 | prev: prev, 73 | next: next 74 | }; 75 | } 76 | 77 | var isChanged = function isChanged(key) { 78 | return prev[key] !== next[key] && !(0, _lodashIsEqual2['default'])(prev[key], next[key]); 79 | }; 80 | var isSameFunction = function isSameFunction(key) { 81 | var prevFn = prev[key]; 82 | var nextFn = next[key]; 83 | return (0, _lodashIsFunction2['default'])(prevFn) && (0, _lodashIsFunction2['default'])(nextFn) && prevFn.name === nextFn.name; 84 | }; 85 | 86 | var keys = (0, _lodashUnion2['default'])((0, _lodashKeys2['default'])(prev), (0, _lodashKeys2['default'])(next)); 87 | var changedKeys = (0, _lodashFilter2['default'])(keys, isChanged); 88 | 89 | if (changedKeys.length && (0, _lodashEvery2['default'])(changedKeys, isSameFunction)) { 90 | return { 91 | type: DIFF_TYPES.FUNCTIONS, 92 | name: name, 93 | prev: (0, _lodashPick2['default'])(prev, changedKeys), 94 | next: (0, _lodashPick2['default'])(next, changedKeys) 95 | }; 96 | } 97 | return { 98 | type: DIFF_TYPES.UNAVOIDABLE, 99 | name: name, 100 | prev: prev, 101 | next: next 102 | }; 103 | }; 104 | exports.classifyDiff = classifyDiff; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //load image 4 | require('console-png').attachTo(console); 5 | let image = require('fs').readFileSync(__dirname + '/media/logo-cli.png'); 6 | 7 | // chalk requirements 8 | const chalk = require('chalk'); 9 | const log = console.log; 10 | 11 | const puppeteer = require('puppeteer'); 12 | // middleware for reading cli text input 13 | const readline = require('readline'); 14 | const rl = readline.createInterface({ 15 | input: process.stdin, 16 | output: process.stdout 17 | }); 18 | 19 | // placeholder for data object window events 20 | let data; // comment out for testing 21 | 22 | // // data for testing suite 23 | // let data = { 24 | // time: '0ms', 25 | // rerenders : [{ 26 | // type: 'initialLoad', 27 | // name: 'initialLoad', 28 | // components: [] 29 | // }] 30 | // }; 31 | 32 | let uri = process.argv[2]; // gets url from CLI "npm start [url]" 33 | 34 | //start puppeteer to allow user to interact with React app 35 | puppeteer.launch({headless: false}).then(async browser => { 36 | const page = await browser.newPage(); 37 | await page.setViewport({width: 1000, height: 1000}); // chromium default is 800 x 600 px 38 | await page.goto(uri); 39 | 40 | //close browser on 'done' but also grab data before closing browser 41 | await rl.on('line', (line) => { 42 | if (line === 'done') { 43 | page.evaluate(() => { 44 | return window.data 45 | }).then((returnedData) => { 46 | data = returnedData; 47 | browser.close(); 48 | return data; 49 | }).then(logAudits) 50 | .catch((err) => console.log('Error, no data collected. Try interacting more with your page.')); 51 | } 52 | }); 53 | }); 54 | 55 | //runs on start of reactopt 56 | (function startReactopt() { 57 | console.png(image); 58 | setTimeout(reactoptRun,1000); 59 | function reactoptRun() { 60 | log(''); 61 | log(''); 62 | log(chalk.bgGreen.bold(" Reactopt is running - Interact with your app, don't close the browser, then type 'done' to perform audit. ")); 63 | log(''); 64 | } 65 | })(); // iife 66 | 67 | // when user ends interaction with 'done', execute these audits 68 | function logAudits() { 69 | var funcArray = [ 70 | loadTime, 71 | componentRerenders 72 | ]; 73 | 74 | // run functions in funcArray, with printLine prior to each 75 | funcArray.forEach((eventsMethod) => { 76 | printLine(); 77 | eventsMethod(data); 78 | }); 79 | } 80 | 81 | // styling for different console logs 82 | function printLine(type, string) { 83 | switch (type) { 84 | case 'heading': 85 | log(chalk.black.bgWhite.dim(string)); 86 | log(''); 87 | break; 88 | case 'pass': 89 | log(chalk.green.bold(string)); 90 | break; 91 | case 'fail': 92 | log(chalk.red.bold(string)); 93 | break; 94 | case 'suggestion': 95 | log(chalk.gray(string)); 96 | break; 97 | case 'line': 98 | log(''); 99 | log(chalk.gray('-----------------------------------------------------------------------------------')); 100 | log(''); 101 | break; 102 | default: 103 | break; 104 | } 105 | } 106 | 107 | let indentD = ' $ '; 108 | let indent = ' '; 109 | 110 | // test functions 111 | function loadTime(data) { 112 | printLine('heading', 'Page Load Time'); 113 | log(indent + 'Your page took ' + data.time + ' to load'); 114 | log(''); 115 | } 116 | 117 | function componentRerenders(data) { 118 | printLine('heading', 'Component Re-rendering'); 119 | 120 | if (data.rerenders.length > 1) { 121 | printLine('fail', 'There are components that are potentially re-rendering unnecessarily. Below are identified events that triggered them:'); 122 | log(''); 123 | // print eventTypes, eventNames, and components rerendered for each unnecessary rerendering 124 | for (let i = 1; i < data.rerenders.length; i += 1) { 125 | if ((data.rerenders[i].components).length > 0) { 126 | log(indentD + chalk.underline(data.rerenders[i].type + ' - ' + data.rerenders[i].name) + ' => ' + data.rerenders[i].components); 127 | } 128 | } 129 | log(''); 130 | // print suggestions for possible improvements 131 | log(chalk.italic('Possible improvements to re-rendering')); 132 | log(''); 133 | printLine('suggestion', indent + "* " + "Consider utilizing shouldComponentDidUpdate of components that shouldn't be constantly re-rendering"); 134 | printLine('suggestion', indent + "* " + "Note: this may affect functionality of child components"); 135 | } else { 136 | printLine('pass', indent + 'Way to go, Idaho! No unnecessary re-rendering of components were detected.'); 137 | log(''); 138 | } 139 | } 140 | 141 | Object.defineProperty(exports, '__esModule', { 142 | value: true 143 | }); 144 | 145 | // exports.data = data; 146 | module.exports = { data, loadTime, printLine, componentRerenders }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // declare exports 4 | Object.defineProperty(exports, '__esModule', { 5 | value: true 6 | }); 7 | 8 | var _deepDiff = require('./deepDiff'); 9 | var _getDisplayName = require('./getDisplayName'); 10 | var _normalizeOptions = require('./normalizeOptions'); 11 | var _shouldInclude = require('./shouldInclude'); 12 | 13 | window.data = { 14 | time: '', 15 | rerenders : [{ 16 | type: 'initialLoad', 17 | name: 'initialLoad', 18 | components: [] 19 | }] 20 | }; 21 | 22 | var keyboardEvents = ['keypress', 'keydown', 'input']; 23 | var mouseEvents = ['click', 'dbclick', 'drag']; 24 | 25 | // monkeypatch 26 | // ****** called on render -> look down to opts.notifier 27 | function createComponentDidUpdate(opts) { 28 | return function componentDidUpdate(prevProps, prevState) { 29 | //displayname is component name 30 | var displayName = (0, _getDisplayName.getDisplayName)(this); 31 | 32 | //should include returns display/comp name, if return value doesn't exist exit compDidUpdate w/o doing anything 33 | if (!(0, _shouldInclude.shouldInclude)(displayName, opts)) { 34 | return; 35 | } 36 | 37 | var propsDiff = (0, _deepDiff.classifyDiff)(prevProps, this.props, displayName + '.props'); 38 | if (propsDiff.type === _deepDiff.DIFF_TYPES.UNAVOIDABLE) { 39 | return; 40 | } 41 | 42 | var stateDiff = (0, _deepDiff.classifyDiff)(prevState, this.state, displayName + '.state'); 43 | if (stateDiff.type === _deepDiff.DIFF_TYPES.UNAVOIDABLE) { 44 | return; 45 | } 46 | //if makes it past above non-conflicts (meaning there are components re-rendering unnecessarily) 47 | // temp timeout because event listener sets global event names after components re-render 48 | setTimeout(timeTest,100); 49 | function timeTest() { 50 | console.log('data!!!',window.data); 51 | 52 | // storing event data in window's 'data' object 53 | let len = (window.data.rerenders).length - 1; 54 | window.data.rerenders[len].components.push(displayName); 55 | } 56 | }; 57 | } 58 | 59 | // takes in react component, triggers all other logic, is exported out 60 | var whyDidYouUpdate = function whyDidYouUpdate(React) { 61 | 62 | // event listener for load page 63 | window.addEventListener('load', () => { 64 | // calculation for total time taken to render the webpage 65 | const startLoadTime = window.performance.timing.loadEventStart 66 | const endLoadTime = window.performance.timing.domLoading 67 | const deltaTime = startLoadTime - endLoadTime; 68 | 69 | window.data.time = deltaTime + 'ms'; 70 | }); 71 | 72 | /** 73 | * function handler for click, dbclick, and drag 74 | */ 75 | function handleMouseEvents(e) { 76 | let localName = e.target.localName; 77 | let innerText = ''; 78 | 79 | // description string for click elements 80 | function setInnerText(type, targetInfo) { 81 | innerText = type +': ' + targetInfo; 82 | } 83 | // if clicked element is '