Skip to content

Commit 49168ce

Browse files
committed
feat(React Scan): Add performance analytics features
1 parent aaac532 commit 49168ce

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

README.md

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,12 @@ scan({
133133
});
134134
```
135135

136-
Or, if you prefer a more manual approach, use the `withScan` API:
136+
## Examples
137+
138+
If you're digging into performance issues and prefer a more manual approach, use the `withScan` API:
137139

138140
```js
139-
import { withScan } from 'react-scan';
141+
import { withScan, setOptions } from 'react-scan';
140142

141143
const ExpensiveComponent = withScan(
142144
(props) => {
@@ -146,6 +148,29 @@ const ExpensiveComponent = withScan(
146148
);
147149
```
148150

151+
You can also hook into internal lifecycle methods to for internal data or get a summary of component renders by calling `getReport()`:
152+
153+
```js
154+
import { setOptions, getReport } from 'react-scan';
155+
156+
setOptions({
157+
onCommitStart() {},
158+
onRender(fiber, render) {},
159+
onCommitFinish() {},
160+
onPaintStart(outline) {},
161+
onPaintFinish(outline) {},
162+
});
163+
164+
console.log(getReport());
165+
// ^
166+
// {
167+
// "ExpensiveComponent": {
168+
// count: 100,
169+
// time: 400
170+
// }
171+
// }
172+
```
173+
149174
And voilà! You're ready to go.
150175

151176
## Why React Scan?
@@ -208,21 +233,22 @@ We expect all contributors to abide by the terms of our [Code of Conduct](https:
208233

209234
- [x] Scan only for unnecessary renders ("unstable" props)
210235
- [x] Scan API (`withScan`, `scan`)
211-
- [ ] Don't show label if no reconciliation occurred ("client renders" in DevTools)
212-
- [ ] Investigate `__REACT_DEVTOOLS_TARGET_WINDOW__`
236+
- [x] Cleanup config options
213237
- [x] Chrome extension (h/t [@biw](https://github.com/biw))
238+
- [x] Mode to highlight long tasks
239+
- [x] Add context updates
240+
- [x] Expose primitives / internals for advanced use cases
241+
- [x] More explicit options override API (start log at certain area, stop log, etc.)
242+
- [x] Don't show label if no reconciliation occurred ("client renders" in DevTools)
243+
- [x] "global" counter using `sessionStorage`, aggregate count stats instead of immediate replacement
244+
- [x] Give a general report of the app's performance
245+
- [ ] UI for turning on/off options
214246
- [ ] "PageSpeed insights" for React
215-
- [x] Cleanup config options
216247
- [ ] Offscreen canvas on worker thread
217248
- [ ] React Native support
218-
- [ ] "global" counter using `sessionStorage`, aggregate count stats instead of immediate replacement
219249
- [ ] Name / explain the actual problem
220-
- [x] More explicit options override API (start log at certain area, stop log, etc.)
221-
- [x] Expose primitives / internals for advanced use cases
222-
- [x] Add context updates
223250
- [ ] Simple FPS counter
224251
- [ ] Drag and select areas of the screen to scan
225-
- [x] Mode to show on main thread blocking
226252
- [ ] Add a funny mascot, like the ["Stop I'm Changing" dude](https://www.youtube.com/shorts/FwOZdX7bDKI?app=desktop)
227253

228254
## Acknowledgments

src/core/index.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ interface Options {
6363
*/
6464
longTaskThreshold?: number;
6565

66+
/**
67+
* Clear aggregated fibers after this time in milliseconds
68+
*
69+
* @default 5000
70+
*/
71+
resetCountTimeout?: number;
72+
6673
onCommitStart?: () => void;
6774
onRender?: (fiber: Fiber, render: Render) => void;
6875
onCommitFinish?: () => void;
@@ -79,6 +86,21 @@ interface Internals {
7986
options: Options;
8087
scheduledOutlines: PendingOutline[];
8188
activeOutlines: ActiveOutline[];
89+
reportData: Record<
90+
string,
91+
{
92+
count: number;
93+
time: number;
94+
}
95+
>;
96+
fiberMap: WeakMap<
97+
Fiber,
98+
{
99+
count: number;
100+
time: number;
101+
lastUpdated: number;
102+
}
103+
>;
82104
}
83105

84106
export const ReactScanInternals: Internals = {
@@ -102,11 +124,23 @@ export const ReactScanInternals: Internals = {
102124
log: false,
103125
showToolbar: true,
104126
longTaskThreshold: 50,
127+
resetCountTimeout: 5000,
105128
},
129+
reportData: {},
130+
fiberMap: new WeakMap<
131+
Fiber,
132+
{
133+
count: number;
134+
time: number;
135+
lastUpdated: number;
136+
}
137+
>(),
106138
scheduledOutlines: [],
107139
activeOutlines: [],
108140
};
109141

142+
export const getReport = () => ReactScanInternals.reportData;
143+
110144
export const setOptions = (options: Options) => {
111145
ReactScanInternals.options = {
112146
...ReactScanInternals.options,
@@ -159,6 +193,36 @@ export const start = () => {
159193
playGeigerClickSound(audioContext, amplitude);
160194
}
161195

196+
const fiberData = ReactScanInternals.fiberMap.get(fiber);
197+
const now = Date.now();
198+
let count = render.count;
199+
let time = render.time;
200+
if (fiberData) {
201+
// clear aggregated fibers after 5 seconds
202+
if (now - fiberData.lastUpdated > (options.resetCountTimeout ?? 5000)) {
203+
ReactScanInternals.fiberMap.delete(fiber);
204+
} else {
205+
count += fiberData.count;
206+
time += fiberData.time;
207+
render.count = count;
208+
render.time = time;
209+
}
210+
}
211+
212+
if (render.name) {
213+
const prev = ReactScanInternals.reportData[render.name];
214+
ReactScanInternals.reportData[render.name] = {
215+
count: (prev?.count ?? 0) + render.count,
216+
time: (prev?.time ?? 0) + render.time,
217+
};
218+
}
219+
220+
ReactScanInternals.fiberMap.set(fiber, {
221+
count,
222+
time,
223+
lastUpdated: now,
224+
});
225+
162226
requestAnimationFrame(() => {
163227
flushOutlines(ctx, new Map(), toolbar, perfObserver);
164228
});

0 commit comments

Comments
 (0)