@@ -12,19 +12,30 @@ const PLUGIN_NAME = "devtools-toolbar";
1212const 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>` ;
1313const 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+
1527interface 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
375398export 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