Skip to content

Commit f003213

Browse files
committed
refactor(core): Replace activeOutlines array with Map
1 parent 3c98e7b commit f003213

File tree

4 files changed

+113
-61
lines changed

4 files changed

+113
-61
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ import React from 'react';
121121
scan({
122122
enabled: true,
123123
log: true, // logs render info to console (default: false)
124-
clearLog: false, // clears the console per group of renders (default: false)
125124
});
126125
```
127126

@@ -416,7 +415,12 @@ We expect all contributors to abide by the terms of our [Code of Conduct](https:
416415
- [x] Don't show label if no reconciliation occurred ("client renders" in DevTools)
417416
- [x] "global" counter using `sessionStorage`, aggregate count stats instead of immediate replacement
418417
- [x] Give a general report of the app's performance
419-
418+
- [ ] checkbox filtering API, leaderboard
419+
- [ ] Offscreen canvas on worker thread
420+
- [ ] heatmap decay (stacked renders will be more intense)
421+
- [ ] Investigate components (UI allowlist)
422+
- [ ] UI for turning on/off options
423+
- [ ] “PageSpeed insights” for React
420424
- [ ] React Native support
421425
- [ ] Name / explain the actual problem, docs
422426
- [ ] Simple FPS counter

src/core/index.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ interface Internals {
8585
componentAllowList: WeakMap<React.ComponentType<any>, Options> | null;
8686
options: Options;
8787
scheduledOutlines: PendingOutline[];
88-
activeOutlines: ActiveOutline[];
88+
activeOutlinesMap: Map<string, ActiveOutline>;
8989
reportData: Record<
9090
string,
9191
{
@@ -136,7 +136,7 @@ export const ReactScanInternals: Internals = {
136136
}
137137
>(),
138138
scheduledOutlines: [],
139-
activeOutlines: [],
139+
activeOutlinesMap: new Map<string, ActiveOutline>(),
140140
};
141141

142142
export const getReport = () => ReactScanInternals.reportData;
@@ -204,30 +204,30 @@ export const start = () => {
204204
requestAnimationFrame(() => {
205205
flushOutlines(ctx, new Map(), toolbar, perfObserver);
206206

207-
const fiberData = ReactScanInternals.fiberMap.get(fiber);
208-
const now = Date.now();
209-
let count = render.count;
210-
let time = render.time;
211-
if (fiberData) {
212-
// clear aggregated fibers after 5 seconds
213-
if (
214-
now - fiberData.lastUpdated >
215-
(options.resetCountTimeout ?? 5000)
216-
) {
217-
ReactScanInternals.fiberMap.delete(fiber);
218-
} else {
219-
count += fiberData.count;
220-
time += fiberData.time;
221-
render.count = count;
222-
render.time = time;
223-
}
224-
}
207+
// const fiberData = ReactScanInternals.fiberMap.get(fiber);
208+
// const now = Date.now();
209+
// let count = render.count;
210+
// let time = render.time;
211+
// if (fiberData) {
212+
// // clear aggregated fibers after 5 seconds
213+
// if (
214+
// now - fiberData.lastUpdated >
215+
// (options.resetCountTimeout ?? 5000)
216+
// ) {
217+
// ReactScanInternals.fiberMap.delete(fiber);
218+
// } else {
219+
// count += fiberData.count;
220+
// time += fiberData.time;
221+
// render.count = count;
222+
// render.time = time;
223+
// }
224+
// }
225225

226-
ReactScanInternals.fiberMap.set(fiber, {
227-
count,
228-
time,
229-
lastUpdated: now,
230-
});
226+
// ReactScanInternals.fiberMap.set(fiber, {
227+
// count,
228+
// time,
229+
// lastUpdated: now,
230+
// });
231231
});
232232
},
233233
onCommitFinish() {

src/core/web/outline.ts

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const mergeOutlines = (outlines: PendingOutline[]) => {
113113
};
114114

115115
export const recalcOutlines = throttle(() => {
116-
const { scheduledOutlines, activeOutlines } = ReactScanInternals;
116+
const { scheduledOutlines, activeOutlinesMap } = ReactScanInternals;
117117
for (let i = scheduledOutlines.length - 1; i >= 0; i--) {
118118
const outline = scheduledOutlines[i];
119119
const rect = getRect(outline.domNode);
@@ -123,11 +123,16 @@ export const recalcOutlines = throttle(() => {
123123
}
124124
outline.rect = rect;
125125
}
126-
for (let i = activeOutlines.length - 1; i >= 0; i--) {
127-
const { outline } = activeOutlines[i];
126+
127+
const activeOutlines = Array.from(activeOutlinesMap.values());
128+
129+
for (let i = 0, len = activeOutlines.length; i < len; i++) {
130+
const activeOutline = activeOutlines[i];
131+
const { outline } = activeOutline;
128132
const rect = getRect(outline.domNode);
129133
if (!rect) {
130134
activeOutlines.splice(i, 1);
135+
activeOutlinesMap.delete(getOutlineKey(outline));
131136
continue;
132137
}
133138
outline.rect = rect;
@@ -217,17 +222,35 @@ export const paintOutline = (
217222
log(outline.renders);
218223
}
219224

220-
ReactScanInternals.activeOutlines.push({
221-
outline,
222-
alpha,
223-
frame,
224-
totalFrames,
225-
resolve: () => {
225+
const outlineKey = getOutlineKey(outline);
226+
const existingActiveOutline =
227+
ReactScanInternals.activeOutlinesMap.get(outlineKey);
228+
229+
if (existingActiveOutline) {
230+
existingActiveOutline.outline.renders.push(...outline.renders);
231+
existingActiveOutline.frame = frame;
232+
existingActiveOutline.totalFrames = totalFrames;
233+
existingActiveOutline.alpha = alpha;
234+
existingActiveOutline.text = getLabelText(
235+
existingActiveOutline.outline.renders,
236+
);
237+
existingActiveOutline.resolve = () => {
226238
resolve();
227-
options.onPaintFinish?.(outline);
228-
},
229-
text,
230-
});
239+
options.onPaintFinish?.(existingActiveOutline.outline);
240+
};
241+
} else {
242+
ReactScanInternals.activeOutlinesMap.set(outlineKey, {
243+
outline,
244+
alpha,
245+
frame,
246+
totalFrames,
247+
resolve: () => {
248+
resolve();
249+
options.onPaintFinish?.(outline);
250+
},
251+
text,
252+
});
253+
}
231254

232255
if (!animationFrameId) {
233256
fadeOutOutline(ctx);
@@ -236,7 +259,7 @@ export const paintOutline = (
236259
};
237260

238261
export const fadeOutOutline = (ctx: CanvasRenderingContext2D) => {
239-
const { activeOutlines } = ReactScanInternals;
262+
const { activeOutlinesMap } = ReactScanInternals;
240263

241264
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
242265

@@ -247,34 +270,36 @@ export const fadeOutOutline = (ctx: CanvasRenderingContext2D) => {
247270

248271
const pendingLabeledOutlines: PaintedOutline[] = [];
249272

250-
for (let i = ReactScanInternals.activeOutlines.length - 1; i >= 0; i--) {
251-
const animation = ReactScanInternals.activeOutlines[i];
252-
const { outline, frame, totalFrames } = animation;
273+
const activeOutlines = Array.from(activeOutlinesMap.values());
274+
275+
for (let i = 0, len = activeOutlines.length; i < len; i++) {
276+
const activeOutline = activeOutlines[i];
277+
const { outline, frame, totalFrames } = activeOutline;
253278
const { rect } = outline;
254279
const unstable = isOutlineUnstable(outline);
255280

256281
const alphaScalar = unstable ? 0.8 : 0.2;
257282

258-
animation.alpha = alphaScalar * (1 - frame / totalFrames);
283+
activeOutline.alpha = alphaScalar * (1 - frame / totalFrames);
259284

260-
maxStrokeAlpha = Math.max(maxStrokeAlpha, animation.alpha);
261-
maxFillAlpha = Math.max(maxFillAlpha, animation.alpha * 0.1);
285+
maxStrokeAlpha = Math.max(maxStrokeAlpha, activeOutline.alpha);
286+
maxFillAlpha = Math.max(maxFillAlpha, activeOutline.alpha * 0.1);
262287

263288
combinedPath.rect(rect.x, rect.y, rect.width, rect.height);
264289

265290
if (unstable) {
266291
pendingLabeledOutlines.push({
267-
alpha: animation.alpha,
292+
alpha: activeOutline.alpha,
268293
outline,
269-
text: animation.text,
294+
text: activeOutline.text,
270295
});
271296
}
272297

273-
animation.frame++;
298+
activeOutline.frame++;
274299

275-
if (animation.frame > animation.totalFrames) {
276-
activeOutlines.splice(i, 1);
277-
animation.resolve();
300+
if (activeOutline.frame > activeOutline.totalFrames) {
301+
activeOutlinesMap.delete(getOutlineKey(outline));
302+
activeOutline.resolve();
278303
}
279304
}
280305

src/core/web/perf-observer.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,43 @@ import { colorRef } from './outline';
55
export const createPerfObserver = () => {
66
const observer = new PerformanceObserver(NO_OP);
77

8-
observer.observe({ entryTypes: ['longtask'] });
8+
observer.observe({ entryTypes: ['measure'] });
99

1010
return observer;
1111
};
1212

1313
export const recalcOutlineColor = (entries: PerformanceEntryList) => {
1414
const { longTaskThreshold } = ReactScanInternals.options;
15+
const minDuration = longTaskThreshold ?? 1;
16+
const maxDuration = 1000;
17+
18+
let maxDurationFound = 0;
19+
1520
for (let i = 0, len = entries.length; i < len; i++) {
16-
const entry = entries[i];
17-
// 64ms = 4 frames
18-
// If longTaskThreshold is set, we show all "short" tasks, otherwise we hide them
19-
if (entry.duration < (longTaskThreshold ?? 50)) continue;
20-
colorRef.current = '185,49,115';
21-
return;
21+
const duration = entries[i].duration;
22+
if (duration > maxDurationFound) {
23+
maxDurationFound = duration;
24+
}
25+
}
26+
27+
if (maxDurationFound > minDuration) {
28+
const t = Math.min(
29+
Math.max(
30+
(maxDurationFound - minDuration) / (maxDuration - minDuration),
31+
0,
32+
),
33+
1,
34+
);
35+
36+
const startColor = { r: 115, g: 97, b: 230 }; // Base color
37+
const endColor = { r: 185, g: 49, b: 115 }; // Color for longest tasks
38+
39+
const r = Math.round(startColor.r + t * (endColor.r - startColor.r));
40+
const g = Math.round(startColor.g + t * (endColor.g - startColor.g));
41+
const b = Math.round(startColor.b + t * (endColor.b - startColor.b));
42+
43+
colorRef.current = `${r},${g},${b}`;
44+
} else {
45+
colorRef.current = '115,97,230'; // Default color when there are no significant entries
2246
}
23-
colorRef.current = '115,97,230';
2447
};

0 commit comments

Comments
 (0)