Skip to content

Commit a61b9d4

Browse files
committed
fix: improve render monitor accuracy and cleanup on unregister
- Use rest params for onCommitFiberRoot to forward all arguments - Only count fibers that actually rendered (check alternate props/state) - Remove dead patchedRoots WeakSet code - Add dispose() to stop rAF loop and unhook devtools on plugin unregister
1 parent 3389246 commit a61b9d4

File tree

1 file changed

+52
-24
lines changed

1 file changed

+52
-24
lines changed

packages/gym/components/toolbar-entries-provider.tsx

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,30 @@ const PLUGIN_NAME = "devtools-toolbar";
1212
const ICON_RENDER = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>`;
1313
const ICON_FPS = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>`;
1414

15+
interface FiberNode {
16+
child?: FiberNode | null;
17+
sibling?: FiberNode | null;
18+
type?: unknown;
19+
elementType?: unknown;
20+
alternate?: FiberNode | null;
21+
memoizedProps?: unknown;
22+
memoizedState?: unknown;
23+
flags?: number;
24+
stateNode?: unknown;
25+
}
26+
1527
interface RenderRecord {
1628
componentName: string;
1729
count: number;
1830
lastTimestamp: number;
1931
}
2032

21-
const createRenderMonitorEntry = (): ToolbarEntry => {
33+
const createRenderMonitorEntry = (): ToolbarEntry & { dispose: () => void } => {
2234
const renderCounts = new Map<string, RenderRecord>();
2335
let totalRenderCount = 0;
2436
let isMonitoring = false;
25-
let patchedRoots = new WeakSet<object>();
2637

27-
const getComponentName = (fiber: { type?: unknown; elementType?: unknown }): string | null => {
38+
const getComponentName = (fiber: FiberNode): string | null => {
2839
const type = fiber.type ?? fiber.elementType;
2940
if (!type) return null;
3041
if (typeof type === "string") return null;
@@ -40,10 +51,18 @@ const createRenderMonitorEntry = (): ToolbarEntry => {
4051
return null;
4152
};
4253

43-
const traverseFiber = (fiber: { child?: unknown; sibling?: unknown; type?: unknown; elementType?: unknown; alternate?: unknown; stateNode?: unknown } | null | undefined) => {
54+
const didFiberRender = (fiber: FiberNode): boolean => {
55+
if (!fiber.alternate) return true;
56+
return (
57+
fiber.memoizedProps !== fiber.alternate.memoizedProps ||
58+
fiber.memoizedState !== fiber.alternate.memoizedState
59+
);
60+
};
61+
62+
const traverseFiber = (fiber: FiberNode | null | undefined) => {
4463
if (!fiber) return;
45-
const componentName = getComponentName(fiber as { type?: unknown; elementType?: unknown });
46-
if (componentName) {
64+
const componentName = getComponentName(fiber);
65+
if (componentName && didFiberRender(fiber)) {
4766
const existing = renderCounts.get(componentName);
4867
if (existing) {
4968
existing.count++;
@@ -57,8 +76,8 @@ const createRenderMonitorEntry = (): ToolbarEntry => {
5776
}
5877
totalRenderCount++;
5978
}
60-
traverseFiber(fiber.child as typeof fiber);
61-
traverseFiber(fiber.sibling as typeof fiber);
79+
traverseFiber(fiber.child);
80+
traverseFiber(fiber.sibling);
6281
};
6382

6483
const startMonitoring = (handle: { setBadge: (badge: string | number | undefined) => void }) => {
@@ -68,50 +87,51 @@ const createRenderMonitorEntry = (): ToolbarEntry => {
6887
totalRenderCount = 0;
6988

7089
const hook = (window as unknown as Record<string, unknown>).__REACT_DEVTOOLS_GLOBAL_HOOK__ as {
71-
onCommitFiberRoot?: (
72-
rendererID: number,
73-
fiberRoot: { current?: unknown },
74-
) => void;
75-
_originalOnCommitFiberRoot?: typeof Function.prototype;
90+
onCommitFiberRoot?: (...args: unknown[]) => void;
91+
_originalOnCommitFiberRoot?: (...args: unknown[]) => void;
7692
} | undefined;
7793

7894
if (!hook) return;
7995

8096
if (!hook._originalOnCommitFiberRoot) {
81-
hook._originalOnCommitFiberRoot = hook.onCommitFiberRoot as typeof Function.prototype;
97+
hook._originalOnCommitFiberRoot = hook.onCommitFiberRoot;
8298
}
8399

84100
const originalOnCommit = hook._originalOnCommitFiberRoot;
85101

86-
hook.onCommitFiberRoot = (rendererID: number, fiberRoot: { current?: unknown }) => {
102+
hook.onCommitFiberRoot = (...args: unknown[]) => {
87103
if (typeof originalOnCommit === "function") {
88-
originalOnCommit.call(hook, rendererID, fiberRoot);
104+
originalOnCommit.call(hook, ...args);
89105
}
90106

91107
if (!isMonitoring) return;
92-
if (patchedRoots.has(fiberRoot)) return;
93108

94-
traverseFiber(fiberRoot.current as Parameters<typeof traverseFiber>[0]);
95-
handle.setBadge(totalRenderCount);
109+
const fiberRoot = args[1] as { current?: FiberNode } | undefined;
110+
if (fiberRoot?.current) {
111+
traverseFiber(fiberRoot.current);
112+
handle.setBadge(totalRenderCount);
113+
}
96114
};
97115
};
98116

99117
const stopMonitoring = () => {
100118
isMonitoring = false;
101-
patchedRoots = new WeakSet<object>();
102119
const hook = (window as unknown as Record<string, unknown>).__REACT_DEVTOOLS_GLOBAL_HOOK__ as {
103120
onCommitFiberRoot?: unknown;
104-
_originalOnCommitFiberRoot?: typeof Function.prototype;
121+
_originalOnCommitFiberRoot?: (...args: unknown[]) => void;
105122
} | undefined;
106123
if (hook?._originalOnCommitFiberRoot) {
107-
hook.onCommitFiberRoot = hook._originalOnCommitFiberRoot as typeof hook.onCommitFiberRoot;
124+
hook.onCommitFiberRoot = hook._originalOnCommitFiberRoot;
108125
}
109126
};
110127

111128
return {
112129
id: "render-monitor",
113130
icon: ICON_RENDER,
114131
tooltip: "Render Monitor",
132+
dispose: () => {
133+
if (isMonitoring) stopMonitoring();
134+
},
115135
onClick: (handle) => {
116136
if (isMonitoring) {
117137
stopMonitoring();
@@ -196,7 +216,7 @@ const createRenderMonitorEntry = (): ToolbarEntry => {
196216
};
197217
};
198218

199-
const createFpsMonitorEntry = (): ToolbarEntry => {
219+
const createFpsMonitorEntry = (): ToolbarEntry & { dispose: () => void } => {
200220
let isRunning = false;
201221
let animationFrameId: number | null = null;
202222
let lastFrameTimestamp = 0;
@@ -278,6 +298,9 @@ const createFpsMonitorEntry = (): ToolbarEntry => {
278298
id: "fps-monitor",
279299
icon: ICON_FPS,
280300
tooltip: "FPS Monitor",
301+
dispose: () => {
302+
if (isRunning) stopMeasuring();
303+
},
281304
onClick: (handle) => {
282305
if (isRunning) {
283306
stopMeasuring();
@@ -374,12 +397,17 @@ const createFpsMonitorEntry = (): ToolbarEntry => {
374397

375398
export function ToolbarEntriesProvider() {
376399
useEffect(() => {
400+
const renderEntry = createRenderMonitorEntry();
401+
const fpsEntry = createFpsMonitorEntry();
402+
377403
registerPlugin({
378404
name: PLUGIN_NAME,
379-
toolbarEntries: [createRenderMonitorEntry(), createFpsMonitorEntry()],
405+
toolbarEntries: [renderEntry, fpsEntry],
380406
});
381407

382408
return () => {
409+
renderEntry.dispose();
410+
fpsEntry.dispose();
383411
unregisterPlugin(PLUGIN_NAME);
384412
};
385413
}, []);

0 commit comments

Comments
 (0)