@@ -10,6 +10,11 @@ const TRACING_GETTER = ({
10
10
id : 'Tracing' ,
11
11
} as any ) as IntegrationClass < Integration > ;
12
12
13
+ // https://stackoverflow.com/questions/52702466/detect-react-reactdom-development-production-build
14
+ function isReactInDevMode ( ) : boolean {
15
+ return '_self' in React . createElement ( 'div' ) ;
16
+ }
17
+
13
18
/**
14
19
*
15
20
* Based on implementation from Preact:
@@ -39,25 +44,65 @@ function afterNextFrame(callback: Function): void {
39
44
timeout = window . setTimeout ( done , 100 ) ;
40
45
}
41
46
47
+ let profilerCount = 0 ;
48
+
49
+ const profiledComponents : {
50
+ [ key : string ] : number ;
51
+ } = { } ;
52
+
42
53
/**
43
54
* getInitActivity pushes activity based on React component mount
44
55
* @param name displayName of component that started activity
45
56
*/
46
57
const getInitActivity = ( name : string ) : number | null => {
47
58
const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
48
59
49
- if ( tracingIntegration ! == null ) {
50
- // tslint:disable-next-line:no-unsafe-any
51
- return ( tracingIntegration as any ) . constructor . pushActivity ( name , {
52
- description : `< ${ name } >` ,
53
- op : 'react' ,
54
- } ) ;
60
+ if ( tracingIntegration = == null ) {
61
+ logger . warn (
62
+ `Unable to profile component ${ name } due to invalid Tracing Integration. Please make sure to setup the Tracing integration.` ,
63
+ ) ;
64
+
65
+ return null ;
55
66
}
56
67
57
- logger . warn (
58
- `Unable to profile component ${ name } due to invalid Tracing Integration. Please make sure to setup the Tracing integration.` ,
59
- ) ;
60
- return null ;
68
+ // tslint:disable-next-line:no-unsafe-any
69
+ const activity = ( tracingIntegration as any ) . constructor . pushActivity ( name , {
70
+ description : `<${ name } >` ,
71
+ op : 'react' ,
72
+ } ) as number ;
73
+
74
+ /**
75
+ * If an activity was already generated, this the component is in React.StrictMode.
76
+ * React.StrictMode will call constructors and setState hooks twice, effectively
77
+ * creating redundant spans for every render (ex. two <App /> spans, two <Link /> spans)
78
+ *
79
+ * React.StrictMode only has this behaviour in Development Mode
80
+ * See: https://reactjs.org/docs/strict-mode.html
81
+ *
82
+ * To account for this, we track all profiled components, and cancel activities that
83
+ * we recognize to be coming from redundant push activity calls. It is important to note
84
+ * that it is the first call to push activity that is invalid, as that is the one caused
85
+ * by React.StrictMode.
86
+ *
87
+ */
88
+ if ( isReactInDevMode ( ) ) {
89
+ // We can make the guarantee here that if a redundant activity exists, it comes right
90
+ // before the current activity, hence having a profilerCount one less than the existing count.
91
+ const redundantActivity = profiledComponents [ String ( `${ name } ${ profilerCount - 1 } ` ) ] ;
92
+
93
+ if ( redundantActivity ) {
94
+ // tslint:disable-next-line:no-unsafe-any
95
+ ( tracingIntegration as any ) . constructor . cancelActivity ( redundantActivity ) ;
96
+ } else {
97
+ // If an redundant activity didn't exist, we can store the current activity to
98
+ // check later. We have to do this inside an else block because of the case of
99
+ // the edge case where two components may share a single components name.
100
+ profiledComponents [ String ( `${ name } ${ profilerCount } ` ) ] = activity ;
101
+ }
102
+ }
103
+
104
+ profilerCount += 1 ;
105
+ return activity ;
61
106
} ;
62
107
63
108
export type ProfilerProps = {
0 commit comments