@@ -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,69 @@ 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 redudant 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 old activity comes right before the current
90
+ // activity, hence having a profilerCount one less than the existing count.
91
+ const oldActivity = profiledComponents [ String ( `${ name } ${ profilerCount - 1 } ` ) ] ;
92
+
93
+ if ( oldActivity ) {
94
+ // if we cancel an old activity, we can be sure that the existing activity will
95
+ // never be a redundant call caused by React.StrictMode as the old activity was
96
+ // that redudant call.
97
+
98
+ // tslint:disable-next-line:no-unsafe-any
99
+ ( tracingIntegration as any ) . constructor . cancelActivity ( oldActivity ) ;
100
+ } else {
101
+ // If an oldActivity didn't exist, we can store this activity to check later.
102
+ // We have to do this inside an else block because of the case of the edge case
103
+ // where two components may share a single components name.
104
+ profiledComponents [ String ( `${ name } ${ profilerCount } ` ) ] = activity ;
105
+ }
106
+ }
107
+
108
+ profilerCount += 1 ;
109
+ return activity ;
61
110
} ;
62
111
63
112
export type ProfilerProps = {
0 commit comments