1
1
import type { TurbopackMessageAction } from '../../../../server/dev/hot-reloader-types'
2
2
import type { Update as TurbopackUpdate } from '../../../../build/swc/types'
3
3
4
+ declare global {
5
+ interface Window {
6
+ __NEXT_HMR_TURBOPACK_REPORT_NOISY_NOOP_EVENTS : boolean | undefined
7
+ }
8
+ }
9
+
10
+ // How long to wait before reporting the HMR start, used to suppress irrelevant
11
+ // `BUILDING` events. Does not impact reported latency.
12
+ const TURBOPACK_HMR_START_DELAY_MS = 100
13
+
4
14
interface Built {
15
+ hasUpdates : boolean
5
16
updatedModules : Set < string >
6
17
startMsSinceEpoch : number
7
18
endMsSinceEpoch : number
@@ -11,39 +22,89 @@ export class TurbopackHmr {
11
22
#updatedModules: Set < string >
12
23
#startMsSinceEpoch: number | undefined
13
24
#lastUpdateMsSinceEpoch: number | undefined
25
+ #deferredReportHmrStartId: ReturnType < typeof setTimeout > | undefined
14
26
15
27
constructor ( ) {
16
28
this . #updatedModules = new Set ( )
17
29
}
18
30
31
+ // HACK: Turbopack tends to generate a lot of irrelevant "BUILDING" actions,
32
+ // as it reports *any* compilation, including fully no-op/cached compilations
33
+ // and those unrelated to HMR. Fixing this would require significant
34
+ // architectural changes.
35
+ //
36
+ // Work around this by deferring any "rebuilding" message by 100ms. If we get
37
+ // a BUILT event within that threshold and nothing has changed, just suppress
38
+ // the message entirely.
39
+ #runDeferredReportHmrStart( ) {
40
+ if ( this . #deferredReportHmrStartId != null ) {
41
+ console . log ( '[Fast Refresh] rebuilding' )
42
+ this . #cancelDeferredReportHmrStart( )
43
+ }
44
+ }
45
+
46
+ #cancelDeferredReportHmrStart( ) {
47
+ clearTimeout ( this . #deferredReportHmrStartId)
48
+ this . #deferredReportHmrStartId = undefined
49
+ }
50
+
19
51
onBuilding ( ) {
20
52
this . #lastUpdateMsSinceEpoch = undefined
53
+ this . #cancelDeferredReportHmrStart( )
21
54
this . #startMsSinceEpoch = Date . now ( )
55
+
56
+ if ( self . __NEXT_HMR_TURBOPACK_REPORT_NOISY_NOOP_EVENTS ) {
57
+ // debugging feature: don't defer/suppress noisy no-op HMR update messages
58
+ this . #runDeferredReportHmrStart( )
59
+ } else {
60
+ // report the HMR start after a short delay
61
+ this . #deferredReportHmrStartId = setTimeout (
62
+ ( ) => this . #runDeferredReportHmrStart( ) ,
63
+ TURBOPACK_HMR_START_DELAY_MS
64
+ )
65
+ }
22
66
}
23
67
24
68
onTurbopackMessage ( msg : TurbopackMessageAction ) {
69
+ this . #runDeferredReportHmrStart( )
25
70
this . #lastUpdateMsSinceEpoch = Date . now ( )
26
71
const updatedModules = extractModulesFromTurbopackMessage ( msg . data )
27
72
for ( const module of updatedModules ) {
28
73
this . #updatedModules. add ( module )
29
74
}
30
75
}
31
76
32
- onBuilt ( ) : Built {
77
+ onBuilt ( ) : Built | null {
78
+ // check that we got *any* `TurbopackMessageAction`, even if
79
+ // `updatedModules` is empty (not everything gets recorded there).
80
+ const hasUpdates = this . #lastUpdateMsSinceEpoch != null
81
+ if ( ! hasUpdates && this . #deferredReportHmrStartId != null ) {
82
+ // suppress the update entirely
83
+ this . #cancelDeferredReportHmrStart( )
84
+ return null
85
+ }
86
+
87
+ this . #runDeferredReportHmrStart( )
88
+
89
+ // Turbopack has a debounce which causes every BUILT message to appear
90
+ // 30ms late. We don't want to include this latency in our reporting, so
91
+ // prefer to use the last TURBOPACK_MESSAGE time.
92
+ const endMsSinceEpoch = this . #lastUpdateMsSinceEpoch ?? Date . now ( )
93
+ const latencyMs = endMsSinceEpoch - this . #startMsSinceEpoch!
94
+ console . log ( `[Fast Refresh] done in ${ latencyMs } ms` )
95
+
33
96
const result = {
97
+ hasUpdates,
34
98
updatedModules : this . #updatedModules,
35
99
startMsSinceEpoch : this . #startMsSinceEpoch! ,
36
- // Turbopack has a debounce which causes every BUILT message to appear
37
- // 30ms late. We don't want to include this latency in our reporting, so
38
- // prefer to use the last TURBOPACK_MESSAGE time.
39
100
endMsSinceEpoch : this . #lastUpdateMsSinceEpoch ?? Date . now ( ) ,
40
101
}
41
102
this . #updatedModules = new Set ( )
42
103
return result
43
104
}
44
105
}
45
106
46
- export function extractModulesFromTurbopackMessage (
107
+ function extractModulesFromTurbopackMessage (
47
108
data : TurbopackUpdate | TurbopackUpdate [ ]
48
109
) : Set < string > {
49
110
const updatedModules : Set < string > = new Set ( )
0 commit comments