Skip to content

Commit 9195c05

Browse files
author
Tim Robertson
committed
Allow users to specify which components to use with the visualizer
1 parent 3df5254 commit 9195c05

File tree

5 files changed

+315
-286
lines changed

5 files changed

+315
-286
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ a production build.
4646
## Usage
4747
Components will show up with a helpful box when monitored. This will attach itself to all components.
4848

49+
### Select Components to Visualize
50+
By default this transform will display the render visualizer on every component. If you'd like to specify the components you want to visualize perform the following steps
51+
52+
Use the following transform:
53+
```js
54+
"transform": "react-transform-render-visualizer/lib/specify"
55+
```
56+
57+
On any comnponent that you'd like the render visualizer to appear, set the static property rerenderViz to true;
58+
```js
59+
export class QueueMenu extends Component {
60+
static displayName = 'QueueMenu';
61+
static rerenderViz = true;
62+
```
63+
64+
4965
## License
5066
MIT
5167

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"version": "0.3.0",
44
"description": "Render visualizer for ReactJS",
55
"main": "lib/index.js",
6+
"files": [
7+
"lib/"
8+
],
69
"scripts": {
710
"clean": "rimraf lib",
811
"build": "babel src --stage 0 --out-dir lib",

src/index.js

Lines changed: 1 addition & 286 deletions
Original file line numberDiff line numberDiff line change
@@ -1,286 +1 @@
1-
import React, { Component, PropTypes } from 'react';
2-
import ReactDOM from 'react-dom';
3-
import shouldPureComponentUpdate from 'react-pure-render/function';
4-
5-
const RenderVisualizer = {
6-
UPDATE_RENDER_LOG_POSITION_INTERVAL_MS: 500,
7-
MAX_LOG_LENGTH: 20,
8-
9-
STATE_CHANGES: {
10-
MOUNT: 'mount',
11-
UPDATE: 'update'
12-
},
13-
14-
styling: {
15-
renderLog: {
16-
color: 'rgb(85, 85, 85)',
17-
fontFamily: '\'Helvetica Neue\', Arial, Helvetica, sans-serif',
18-
fontSize: '14px',
19-
lineHeight: '18px',
20-
background: 'linear-gradient(#fff, #ccc)',
21-
boxShadow: '0 2px 12px rgba(0,0,0,0.5)',
22-
textShadow: '0 1px 0 #fff',
23-
borderRadius: '3px',
24-
position: 'absolute',
25-
top: 0,
26-
left: 0,
27-
maxWidth: '70%',
28-
padding: '5px 10px',
29-
zIndex: '10000',
30-
transition: '.3s all'
31-
},
32-
renderLogDetailNotes: {
33-
color: 'red',
34-
textAlign: 'center'
35-
},
36-
elementHighlightMonitor: {
37-
outline: '1px solid rgba(47, 150, 180, 1)'
38-
},
39-
elementHighlightMount: {
40-
outline: '3px solid rgba(197, 16, 12, 1)'
41-
},
42-
elementHighlightUpdate: {
43-
outline: '3px solid rgba(197, 203, 1, 1)'
44-
}
45-
}
46-
};
47-
48-
let renders;
49-
if (window.__reactRenders) {
50-
renders = window.__reactRenders;
51-
} else {
52-
renders = new Map();
53-
Object.defineProperty(window, '__reactRenders', {
54-
configurable: true,
55-
enumerable: false,
56-
writable: false,
57-
value: renders
58-
});
59-
window.__reactRendersCount = 0;
60-
}
61-
62-
const containingElement = document.createElement('div');
63-
document.body.appendChild(containingElement);
64-
65-
const addToRenderLog = function (inst, message) {
66-
// don't add anything to the log if the element doesn't exist any more
67-
if (!renders.get(inst)) {
68-
return;
69-
}
70-
let { log, count, ...others } = renders.get(inst);
71-
72-
// add the log message to the start
73-
log = [`${ count } ) ${ message }`, ...log];
74-
75-
// keep everything trimmed to the max log length
76-
log.splice(RenderVisualizer.MAX_LOG_LENGTH, 1);
77-
78-
count++;
79-
80-
renders.set(inst, {
81-
...others,
82-
log,
83-
count
84-
});
85-
};
86-
87-
88-
/*
89-
* Get the changes made to props or state.
90-
*
91-
* @param object prevProps
92-
* @param object prevState
93-
* @param object nextProps
94-
* @param object nextState
95-
* @return boolean
96-
*/
97-
function getReasonForReRender (prevProps, prevState, nextProps, nextState) {
98-
for (let key in nextState) {
99-
if (nextState.hasOwnProperty(key) && nextState[key] !== prevState[key]) {
100-
if (typeof nextState[key] === 'object') {
101-
return `this.state[${ key }] changed`;
102-
} else {
103-
return `this.state[${ key }] changed: '${ prevState[key] }' => '${ nextState[key] }'`;
104-
}
105-
}
106-
}
107-
108-
for (let key in nextProps) {
109-
if (nextProps.hasOwnProperty(key) && nextProps[key] !== prevProps[key]) {
110-
if (typeof nextProps[key] === 'object') {
111-
return `this.props[${ key }] changed`;
112-
} else {
113-
return `this.props[${ key }] changed: '${ prevProps[key] }' => '${ nextProps[key] }'`;
114-
}
115-
}
116-
}
117-
118-
return 'unknown reason for update, possibly from forceUpdate()';
119-
}
120-
121-
class RenderLog extends Component {
122-
static displayName = 'RenderLog';
123-
shouldComponentUpdate = shouldPureComponentUpdate;
124-
125-
static defaultProps = {
126-
log: [],
127-
count: 1,
128-
node: null
129-
};
130-
static propTypes = {
131-
log: PropTypes.instanceOf(Array),
132-
count: PropTypes.number,
133-
node: PropTypes.object,
134-
posTop: PropTypes.number,
135-
posLeft: PropTypes.number
136-
};
137-
138-
state = {
139-
showDetails: false
140-
};
141-
142-
componentWillMount () {
143-
this.highlightChange(RenderVisualizer.STATE_CHANGES.MOUNT);
144-
}
145-
146-
componentDidUpdate (prevProps) {
147-
// only trigger a highlight if the change happened to the render count, as
148-
// this will also be triggered if the details are toggled
149-
if (prevProps.count !== this.props.count) {
150-
this.highlightChange(RenderVisualizer.STATE_CHANGES.UPDATE);
151-
}
152-
}
153-
154-
/*
155-
* Highlight any change by adding an animation style to the component DOM node
156-
* @param String change - The type of change being made to the node
157-
* @return void
158-
*/
159-
highlightChange (change) {
160-
const parentNode = ReactDOM.findDOMNode(this.props.node);
161-
const ANIMATION_DURATION = 500;
162-
163-
if (parentNode) {
164-
parentNode.style.boxSizing = 'border-box';
165-
166-
window.requestAnimationFrame(() => {
167-
parentNode.animate([
168-
change === RenderVisualizer.STATE_CHANGES.MOUNT ?
169-
RenderVisualizer.styling.elementHighlightMount :
170-
RenderVisualizer.styling.elementHighlightUpdate,
171-
RenderVisualizer.styling.elementHighlightMonitor
172-
], {
173-
duration: ANIMATION_DURATION
174-
});
175-
});
176-
}
177-
}
178-
render () {
179-
return (
180-
<div style={{
181-
...RenderVisualizer.styling.renderLog,
182-
183-
// go to the top of everything if we're showing details
184-
zIndex: this.state.showDetails ? 10001 : 10000,
185-
186-
// round coordinates down to prevent blurring
187-
transform: `translate3d(${this.props.posLeft | 0}px, ${this.props.posTop | 0}px, 0)`
188-
}} onClick={() => {
189-
this.setState({
190-
showDetails: !this.state.showDetails
191-
});
192-
}}>
193-
<div style={{ display: this.state.showDetails ? 'none' : 'block' }}>{ this.props.count }</div>
194-
<div style={{ display: this.state.showDetails ? 'block' : 'none' }}>
195-
<div>
196-
{
197-
this.props.log.map((message, i) => {
198-
return <div key={i}>{message}</div>;
199-
})
200-
}
201-
</div>
202-
<div style={RenderVisualizer.styling.renderLogDetailNode}></div>
203-
</div>
204-
</div>
205-
);
206-
}
207-
}
208-
209-
210-
class RenderLogs extends Component {
211-
static displayName = 'RenderLogs';
212-
213-
static propTypes = {
214-
renders: PropTypes.object
215-
};
216-
217-
render () {
218-
const renderLogs = [];
219-
this.props.renders.forEach((val, key) => {
220-
renderLogs.push(<RenderLog key={val.id} {...val} node={key} />);
221-
});
222-
return <div>{ renderLogs }</div>;
223-
}
224-
}
225-
226-
227-
window.setInterval(() => {
228-
renders.forEach((val, node) => {
229-
const parentNode = ReactDOM.findDOMNode(node);
230-
const parentNodeRect = parentNode && parentNode.getBoundingClientRect();
231-
232-
if (parentNodeRect) {
233-
renders.set(node, {
234-
...val,
235-
posTop: window.pageYOffset + parentNodeRect.top,
236-
posLeft: parentNodeRect.left
237-
});
238-
}
239-
});
240-
ReactDOM.render(React.createElement(RenderLogs, { renders: renders }), containingElement);
241-
}, RenderVisualizer.UPDATE_RENDER_LOG_POSITION_INTERVAL_MS);
242-
243-
ReactDOM.render(React.createElement(RenderLogs, { renders: renders }), containingElement);
244-
245-
export default function renderVisualizer () {
246-
return function wrapRenderVisualizer (ReactClass, componentId) {
247-
248-
const old = {
249-
componentDidMount: ReactClass.prototype.componentDidMount,
250-
componentDidUpdate: ReactClass.prototype.componentDidUpdate,
251-
componentWillUnmount: ReactClass.prototype.componentWillUnmount
252-
};
253-
254-
ReactClass.prototype.componentDidMount = function () {
255-
renders.set(this, {
256-
id: window.__reactRendersCount++,
257-
log: [],
258-
count: 0,
259-
260-
posTop: 0,
261-
posLeft: 0
262-
});
263-
addToRenderLog(this, 'Initial Render');
264-
265-
if (old.componentDidMount) {
266-
return old.componentDidMount.apply(this, [...arguments]);
267-
}
268-
};
269-
ReactClass.prototype.componentDidUpdate = function (prevProps, prevState) {
270-
addToRenderLog(this, getReasonForReRender(prevProps, prevState, this.props, this.state));
271-
272-
if (old.componentDidUpdate) {
273-
return old.componentDidUpdate.apply(this, [...arguments]);
274-
}
275-
};
276-
ReactClass.prototype.componentWillUnmount = function () {
277-
renders.delete(this);
278-
279-
if (old.componentWillUnmount) {
280-
return old.componentWillUnmount.apply(this, [...arguments]);
281-
}
282-
};
283-
284-
return ReactClass;
285-
};
286-
}
1+
module.exports = require('./renderWrapper')();

0 commit comments

Comments
 (0)