@@ -24,6 +24,8 @@ import type {
24
24
} from './instrumentation' ;
25
25
import type { InternalInteraction } from './monitor/types' ;
26
26
import type { getSession } from './monitor/utils' ;
27
+ import { startTimingTracking } from './notifications/event-tracking' ;
28
+ import { createHighlightCanvas } from './notifications/outline-overlay' ;
27
29
28
30
let rootContainer : HTMLDivElement | null = null ;
29
31
let shadowRoot : ShadowRoot | null = null ;
@@ -160,6 +162,22 @@ export interface Options {
160
162
*/
161
163
trackUnnecessaryRenders ?: boolean ;
162
164
165
+ /**
166
+ * Should the FPS meter show in the toolbar
167
+ *
168
+ * @default true
169
+ */
170
+ showFPS ?: boolean ;
171
+
172
+ /**
173
+ * Should react scan log internal errors to the console.
174
+ *
175
+ * Useful if react scan is not behaving expected and you want to provide information to maintainers when submitting an issue https://github.com/aidenybai/react-scan/issues
176
+ *
177
+ * @default false
178
+ */
179
+ _debug ?: 'verbose' | false ;
180
+
163
181
onCommitStart ?: ( ) => void ;
164
182
onRender ?: ( fiber : Fiber , renders : Array < Render > ) => void ;
165
183
onCommitFinish ?: ( ) => void ;
@@ -197,6 +215,9 @@ export interface StoreType {
197
215
fiberRoots : WeakSet < Fiber > ;
198
216
reportData : Map < number , RenderData > ;
199
217
legacyReportData : Map < string , RenderData > ;
218
+ interactionListeningForRenders :
219
+ | ( ( fiber : Fiber , renders : Array < Render > ) => void )
220
+ | null ;
200
221
}
201
222
202
223
export type OutlineKey = `${string } -${string } `;
@@ -270,6 +291,7 @@ export const Store: StoreType = {
270
291
reportData : new Map < number , RenderData > ( ) ,
271
292
legacyReportData : new Map < string , RenderData > ( ) ,
272
293
lastReportTime : signal ( 0 ) ,
294
+ interactionListeningForRenders : null ,
273
295
} ;
274
296
275
297
export const ReactScanInternals : Internals = {
@@ -286,6 +308,7 @@ export const ReactScanInternals: Internals = {
286
308
// alwaysShowLabels: false,
287
309
animationSpeed : 'fast' ,
288
310
dangerouslyForceRunInProduction : false ,
311
+ showFPS : true ,
289
312
// smoothlyAnimateOutlines: true,
290
313
// trackUnnecessaryRenders: false,
291
314
} ) ,
@@ -465,50 +488,62 @@ export const getIsProduction = () => {
465
488
} ;
466
489
467
490
export const start = ( ) => {
468
- if ( typeof window === 'undefined' ) {
469
- return ;
470
- }
491
+ try {
492
+ if ( typeof window === 'undefined' ) {
493
+ return ;
494
+ }
471
495
472
- if (
473
- getIsProduction ( ) &&
474
- ! ReactScanInternals . options . value . dangerouslyForceRunInProduction
475
- ) {
476
- return ;
477
- }
496
+ if (
497
+ getIsProduction ( ) &&
498
+ ! ReactScanInternals . options . value . dangerouslyForceRunInProduction
499
+ ) {
500
+ return ;
501
+ }
478
502
479
- const localStorageOptions =
480
- readLocalStorage < LocalStorageOptions > ( 'react-scan-options' ) ;
503
+ const localStorageOptions =
504
+ readLocalStorage < LocalStorageOptions > ( 'react-scan-options' ) ;
481
505
482
- if ( localStorageOptions ) {
483
- const validLocalOptions = validateOptions ( localStorageOptions ) ;
506
+ if ( localStorageOptions ) {
507
+ const validLocalOptions = validateOptions ( localStorageOptions ) ;
484
508
485
- if ( Object . keys ( validLocalOptions ) . length > 0 ) {
486
- ReactScanInternals . options . value = {
487
- ...ReactScanInternals . options . value ,
488
- ...validLocalOptions ,
489
- } ;
509
+ if ( Object . keys ( validLocalOptions ) . length > 0 ) {
510
+ ReactScanInternals . options . value = {
511
+ ...ReactScanInternals . options . value ,
512
+ ...validLocalOptions ,
513
+ } ;
514
+ }
490
515
}
491
- }
492
516
493
- const options = getOptions ( ) ;
494
-
495
- initReactScanInstrumentation ( ( ) => {
496
- initToolbar ( ! ! options . value . showToolbar ) ;
497
- } ) ;
498
-
499
- const isUsedInBrowserExtension = typeof window !== 'undefined' ;
500
- if ( ! Store . monitor . value && ! isUsedInBrowserExtension ) {
501
- setTimeout ( ( ) => {
502
- if ( isInstrumentationActive ( ) ) return ;
503
- // biome-ignore lint/suspicious/noConsole: Intended debug output
517
+ const options = getOptions ( ) ;
518
+
519
+ initReactScanInstrumentation ( ( ) => {
520
+ initToolbar ( ! ! options . value . showToolbar ) ;
521
+ } ) ;
522
+
523
+ const isUsedInBrowserExtension = typeof window !== 'undefined' ;
524
+ if ( ! Store . monitor . value && ! isUsedInBrowserExtension ) {
525
+ setTimeout ( ( ) => {
526
+ if ( isInstrumentationActive ( ) ) return ;
527
+ // biome-ignore lint/suspicious/noConsole: Intended debug output
528
+ console . error (
529
+ '[React Scan] Failed to load. Must import React Scan before React runs.' ,
530
+ ) ;
531
+ } , 5000 ) ;
532
+ }
533
+ } catch ( e ) {
534
+ if ( ReactScanInternals . options . value . _debug === 'verbose' ) {
504
535
console . error (
505
- '[React Scan] Failed to load. Must import React Scan before React runs.' ,
536
+ '[React Scan Internal Error]' ,
537
+ 'Failed to create notifications outline canvas' ,
538
+ e ,
506
539
) ;
507
- } , 5000 ) ;
540
+ }
508
541
}
509
542
} ;
510
543
511
544
const initToolbar = ( showToolbar : boolean ) => {
545
+ startTimingTracking ( ) ;
546
+ createNotificationsOutlineCanvas ( ) ;
512
547
const windowToolbarContainer = window . __REACT_SCAN_TOOLBAR_CONTAINER__ ;
513
548
514
549
if ( ! showToolbar ) {
@@ -521,6 +556,21 @@ const initToolbar = (showToolbar: boolean) => {
521
556
createToolbar ( shadowRoot ) ;
522
557
} ;
523
558
559
+ const createNotificationsOutlineCanvas = ( ) => {
560
+ try {
561
+ const highlightRoot = document . documentElement ;
562
+ createHighlightCanvas ( highlightRoot ) ;
563
+ } catch ( e ) {
564
+ if ( ReactScanInternals . options . value . _debug === 'verbose' ) {
565
+ console . error (
566
+ '[React Scan Internal Error]' ,
567
+ 'Failed to create notifications outline canvas' ,
568
+ e ,
569
+ ) ;
570
+ }
571
+ }
572
+ } ;
573
+
524
574
export const scan = ( options : Options = { } ) => {
525
575
setOptions ( options ) ;
526
576
const isInIframe = Store . isInIframe . value ;
0 commit comments