1
1
import { getCurrentHub } from '@sentry/browser' ;
2
2
import { Integration , IntegrationClass } from '@sentry/types' ;
3
3
import { logger } from '@sentry/utils' ;
4
+ import * as hoistNonReactStatic from 'hoist-non-react-statics' ;
4
5
import * as React from 'react' ;
5
6
6
- export const DEFAULT_DURATION = 30000 ;
7
7
export const UNKNOWN_COMPONENT = 'unknown' ;
8
8
9
9
const TRACING_GETTER = ( {
10
10
id : 'Tracing' ,
11
11
} as any ) as IntegrationClass < Integration > ;
12
12
13
- const getInitActivity = ( componentDisplayName : string , timeout : number ) : number | null => {
13
+ /**
14
+ *
15
+ * Based on implementation from Preact:
16
+ * https:github.com/preactjs/preact/blob/9a422017fec6dab287c77c3aef63c7b2fef0c7e1/hooks/src/index.js#L301-L313
17
+ *
18
+ * Schedule a callback to be invoked after the browser has a chance to paint a new frame.
19
+ * Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after
20
+ * the next browser frame.
21
+ *
22
+ * Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked
23
+ * even if RAF doesn't fire (for example if the browser tab is not visible)
24
+ *
25
+ * This is what we use to tell if a component activity has finished
26
+ *
27
+ */
28
+ function afterNextFrame ( callback : Function ) : void {
29
+ let timeout : number | undefined ;
30
+ let raf : number ;
31
+
32
+ const done = ( ) => {
33
+ window . clearTimeout ( timeout ) ;
34
+ window . cancelAnimationFrame ( raf ) ;
35
+ window . setTimeout ( callback ) ;
36
+ } ;
37
+
38
+ raf = window . requestAnimationFrame ( done ) ;
39
+ timeout = window . setTimeout ( done , 100 ) ;
40
+ }
41
+
42
+ const getInitActivity = ( componentDisplayName : string ) : number | null => {
14
43
const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
15
44
16
45
if ( tracingIntegration !== null ) {
17
46
// tslint:disable-next-line:no-unsafe-any
18
- return ( tracingIntegration as any ) . constructor . pushActivity (
19
- componentDisplayName ,
20
- {
21
- data : { } ,
22
- description : `<${ componentDisplayName } >` ,
23
- op : 'react' ,
24
- } ,
25
- {
26
- autoPopAfter : timeout ,
27
- } ,
28
- ) ;
47
+ const activity = ( tracingIntegration as any ) . constructor . pushActivity ( componentDisplayName , {
48
+ data : { } ,
49
+ description : `<${ componentDisplayName } >` ,
50
+ op : 'react' ,
51
+ } ) ;
52
+
53
+ logger . log ( 'INIT ' , componentDisplayName , activity ) ;
54
+
55
+ // tslint:disable-next-line: no-unsafe-any
56
+ return activity ;
29
57
}
30
58
31
59
logger . warn ( `Unable to profile component ${ componentDisplayName } due to invalid Tracing Integration` ) ;
@@ -34,7 +62,6 @@ const getInitActivity = (componentDisplayName: string, timeout: number): number
34
62
35
63
interface ProfilerProps {
36
64
componentDisplayName ?: string ;
37
- timeout ?: number ;
38
65
}
39
66
40
67
interface ProfilerState {
@@ -45,15 +72,23 @@ class Profiler extends React.Component<ProfilerProps, ProfilerState> {
45
72
public constructor ( props : ProfilerProps ) {
46
73
super ( props ) ;
47
74
48
- const { componentDisplayName = UNKNOWN_COMPONENT , timeout = DEFAULT_DURATION } = this . props ;
75
+ const { componentDisplayName = UNKNOWN_COMPONENT } = this . props ;
49
76
50
77
this . state = {
51
- activity : getInitActivity ( componentDisplayName , timeout ) ,
78
+ activity : getInitActivity ( componentDisplayName ) ,
52
79
} ;
53
80
}
54
81
82
+ public componentDidMount ( ) : void {
83
+ if ( this . state . activity ) {
84
+ afterNextFrame ( this . finishProfile ) ;
85
+ }
86
+ }
87
+
55
88
public componentWillUnmount ( ) : void {
56
- this . finishProfile ( ) ;
89
+ if ( this . state . activity ) {
90
+ afterNextFrame ( this . finishProfile ) ;
91
+ }
57
92
}
58
93
59
94
public finishProfile = ( ) => {
@@ -88,6 +123,9 @@ function withProfiler<P extends object>(
88
123
89
124
Wrapped . displayName = `profiler(${ componentDisplayName } )` ;
90
125
126
+ // Copy over static methods from Wrapped component to Profiler HOC
127
+ // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
128
+ hoistNonReactStatic ( Wrapped , WrappedComponent ) ;
91
129
return Wrapped ;
92
130
}
93
131
0 commit comments