@@ -8,10 +8,12 @@ const getWatcherManager = require("./getWatcherManager");
8
8
const LinkResolver = require ( "./LinkResolver" ) ;
9
9
const EventEmitter = require ( "events" ) . EventEmitter ;
10
10
const globToRegExp = require ( "glob-to-regexp" ) ;
11
+ const watchEventSource = require ( "./watchEventSource" ) ;
11
12
12
13
let EXISTANCE_ONLY_TIME_ENTRY ; // lazy required
13
14
14
15
const EMPTY_ARRAY = [ ] ;
16
+ const EMPTY_OPTIONS = { } ;
15
17
16
18
function addWatchersToSet ( watchers , set ) {
17
19
for ( const w of watchers ) {
@@ -63,18 +65,21 @@ const cachedNormalizeOptions = options => {
63
65
class Watchpack extends EventEmitter {
64
66
constructor ( options ) {
65
67
super ( ) ;
66
- if ( ! options ) options = { } ;
67
- if ( typeof options . aggregateTimeout !== "number" ) {
68
- options . aggregateTimeout = 200 ;
69
- }
68
+ if ( ! options ) options = EMPTY_OPTIONS ;
70
69
this . options = options ;
70
+ this . aggregateTimeout =
71
+ typeof options . aggregateTimeout === "number"
72
+ ? options . aggregateTimeout
73
+ : 200 ;
71
74
this . watcherOptions = cachedNormalizeOptions ( options ) ;
72
75
this . watcherManager = getWatcherManager ( this . watcherOptions ) ;
73
- this . watchers = [ ] ;
76
+ this . fileWatchers = new Map ( ) ;
77
+ this . directoryWatchers = new Map ( ) ;
78
+ this . startTime = undefined ;
74
79
this . paused = false ;
75
80
this . aggregatedChanges = new Set ( ) ;
76
81
this . aggregatedRemovals = new Set ( ) ;
77
- this . aggregateTimeout = 0 ;
82
+ this . aggregateTimer = undefined ;
78
83
this . _onTimeout = this . _onTimeout . bind ( this ) ;
79
84
}
80
85
@@ -94,23 +99,30 @@ class Watchpack extends EventEmitter {
94
99
startTime = arg3 ;
95
100
}
96
101
this . paused = false ;
97
- const oldWatchers = this . watchers ;
102
+ const oldFileWatchers = this . fileWatchers ;
103
+ const oldDirectoryWatchers = this . directoryWatchers ;
98
104
const ignored = this . watcherOptions . ignored ;
99
105
const filter = ignored
100
106
? path => ! ignored . test ( path . replace ( / \\ / g, "/" ) )
101
107
: ( ) => true ;
102
- this . watchers = [ ] ;
108
+ const addToMap = ( map , key , item ) => {
109
+ const list = map . get ( key ) ;
110
+ if ( list === undefined ) {
111
+ map . set ( key , [ item ] ) ;
112
+ } else {
113
+ list . push ( item ) ;
114
+ }
115
+ } ;
116
+ const fileWatchersNeeded = new Map ( ) ;
117
+ const directoryWatchersNeeded = new Map ( ) ;
118
+ const missingFiles = new Set ( ) ;
103
119
if ( this . watcherOptions . followSymlinks ) {
104
120
const resolver = new LinkResolver ( ) ;
105
121
for ( const file of files ) {
106
122
if ( filter ( file ) ) {
107
123
for ( const innerFile of resolver . resolve ( file ) ) {
108
124
if ( file === innerFile || filter ( innerFile ) ) {
109
- const watcher = this . _fileWatcher (
110
- file ,
111
- this . watcherManager . watchFile ( innerFile , startTime )
112
- ) ;
113
- if ( watcher ) this . watchers . push ( watcher ) ;
125
+ addToMap ( fileWatchersNeeded , innerFile , file ) ;
114
126
}
115
127
}
116
128
}
@@ -119,11 +131,8 @@ class Watchpack extends EventEmitter {
119
131
if ( filter ( file ) ) {
120
132
for ( const innerFile of resolver . resolve ( file ) ) {
121
133
if ( file === innerFile || filter ( innerFile ) ) {
122
- const watcher = this . _missingWatcher (
123
- file ,
124
- this . watcherManager . watchFile ( innerFile , startTime )
125
- ) ;
126
- if ( watcher ) this . watchers . push ( watcher ) ;
134
+ missingFiles . add ( file ) ;
135
+ addToMap ( fileWatchersNeeded , innerFile , file ) ;
127
136
}
128
137
}
129
138
}
@@ -133,13 +142,11 @@ class Watchpack extends EventEmitter {
133
142
let first = true ;
134
143
for ( const innerItem of resolver . resolve ( dir ) ) {
135
144
if ( filter ( innerItem ) ) {
136
- const watcher = this . _dirWatcher (
137
- dir ,
138
- first
139
- ? this . watcherManager . watchDirectory ( innerItem , startTime )
140
- : this . watcherManager . watchFile ( innerItem , startTime )
145
+ addToMap (
146
+ first ? directoryWatchersNeeded : fileWatchersNeeded ,
147
+ innerItem ,
148
+ dir
141
149
) ;
142
- if ( watcher ) this . watchers . push ( watcher ) ;
143
150
}
144
151
first = false ;
145
152
}
@@ -148,50 +155,118 @@ class Watchpack extends EventEmitter {
148
155
} else {
149
156
for ( const file of files ) {
150
157
if ( filter ( file ) ) {
151
- const watcher = this . _fileWatcher (
152
- file ,
153
- this . watcherManager . watchFile ( file , startTime )
154
- ) ;
155
- if ( watcher ) this . watchers . push ( watcher ) ;
158
+ addToMap ( fileWatchersNeeded , file , file ) ;
156
159
}
157
160
}
158
161
for ( const file of missing ) {
159
162
if ( filter ( file ) ) {
160
- const watcher = this . _missingWatcher (
161
- file ,
162
- this . watcherManager . watchFile ( file , startTime )
163
- ) ;
164
- if ( watcher ) this . watchers . push ( watcher ) ;
163
+ missingFiles . add ( file ) ;
164
+ addToMap ( fileWatchersNeeded , file , file ) ;
165
165
}
166
166
}
167
167
for ( const dir of directories ) {
168
168
if ( filter ( dir ) ) {
169
- const watcher = this . _dirWatcher (
170
- dir ,
171
- this . watcherManager . watchDirectory ( dir , startTime )
172
- ) ;
173
- if ( watcher ) this . watchers . push ( watcher ) ;
169
+ addToMap ( directoryWatchersNeeded , dir , dir ) ;
170
+ }
171
+ }
172
+ }
173
+ const newFileWatchers = new Map ( ) ;
174
+ const newDirectoryWatchers = new Map ( ) ;
175
+ const setupFileWatcher = ( watcher , key , files ) => {
176
+ watcher . on ( "initial-missing" , type => {
177
+ for ( const file of files ) {
178
+ if ( ! missingFiles . has ( file ) ) this . _onRemove ( file , file , type ) ;
179
+ }
180
+ } ) ;
181
+ watcher . on ( "change" , ( mtime , type ) => {
182
+ for ( const file of files ) {
183
+ this . _onChange ( file , mtime , file , type ) ;
184
+ }
185
+ } ) ;
186
+ watcher . on ( "remove" , type => {
187
+ for ( const file of files ) {
188
+ this . _onRemove ( file , file , type ) ;
189
+ }
190
+ } ) ;
191
+ newFileWatchers . set ( key , watcher ) ;
192
+ } ;
193
+ const setupDirectoryWatcher = ( watcher , key , directories ) => {
194
+ watcher . on ( "initial-missing" , type => {
195
+ for ( const item of directories ) {
196
+ this . _onRemove ( item , item , type ) ;
197
+ }
198
+ } ) ;
199
+ watcher . on ( "change" , ( file , mtime , type ) => {
200
+ for ( const item of directories ) {
201
+ this . _onChange ( item , mtime , file , type ) ;
174
202
}
203
+ } ) ;
204
+ watcher . on ( "remove" , type => {
205
+ for ( const item of directories ) {
206
+ this . _onRemove ( item , item , type ) ;
207
+ }
208
+ } ) ;
209
+ newDirectoryWatchers . set ( key , watcher ) ;
210
+ } ;
211
+ // Close unneeded old watchers
212
+ const fileWatchersToClose = [ ] ;
213
+ const directoryWatchersToClose = [ ] ;
214
+ for ( const [ key , w ] of oldFileWatchers ) {
215
+ if ( ! fileWatchersNeeded . has ( key ) ) {
216
+ w . close ( ) ;
217
+ } else {
218
+ fileWatchersToClose . push ( w ) ;
175
219
}
176
220
}
177
- for ( const w of oldWatchers ) w . close ( ) ;
221
+ for ( const [ key , w ] of oldDirectoryWatchers ) {
222
+ if ( ! directoryWatchersNeeded . has ( key ) ) {
223
+ w . close ( ) ;
224
+ } else {
225
+ directoryWatchersToClose . push ( w ) ;
226
+ }
227
+ }
228
+ // Create new watchers and install handlers on these watchers
229
+ watchEventSource . batch ( ( ) => {
230
+ for ( const [ key , files ] of fileWatchersNeeded ) {
231
+ const watcher = this . watcherManager . watchFile ( key , startTime ) ;
232
+ if ( watcher ) {
233
+ setupFileWatcher ( watcher , key , files ) ;
234
+ }
235
+ }
236
+ for ( const [ key , directories ] of directoryWatchersNeeded ) {
237
+ const watcher = this . watcherManager . watchDirectory ( key , startTime ) ;
238
+ if ( watcher ) {
239
+ setupDirectoryWatcher ( watcher , key , directories ) ;
240
+ }
241
+ }
242
+ } ) ;
243
+ // Close old watchers
244
+ for ( const w of fileWatchersToClose ) w . close ( ) ;
245
+ for ( const w of directoryWatchersToClose ) w . close ( ) ;
246
+ // Store watchers
247
+ this . fileWatchers = newFileWatchers ;
248
+ this . directoryWatchers = newDirectoryWatchers ;
249
+ this . startTime = startTime ;
178
250
}
179
251
180
252
close ( ) {
181
253
this . paused = true ;
182
- if ( this . aggregateTimeout ) clearTimeout ( this . aggregateTimeout ) ;
183
- for ( const w of this . watchers ) w . close ( ) ;
184
- this . watchers . length = 0 ;
254
+ if ( this . aggregateTimer ) clearTimeout ( this . aggregateTimer ) ;
255
+ for ( const w of this . fileWatchers . values ( ) ) w . close ( ) ;
256
+ for ( const w of this . directoryWatchers . values ( ) ) w . close ( ) ;
257
+ this . fileWatchers . clear ( ) ;
258
+ this . directoryWatchers . clear ( ) ;
185
259
}
186
260
187
261
pause ( ) {
188
262
this . paused = true ;
189
- if ( this . aggregateTimeout ) clearTimeout ( this . aggregateTimeout ) ;
263
+ if ( this . aggregateTimer ) clearTimeout ( this . aggregateTimer ) ;
190
264
}
191
265
192
266
getTimes ( ) {
193
267
const directoryWatchers = new Set ( ) ;
194
- addWatchersToSet ( this . watchers , directoryWatchers ) ;
268
+ addWatchersToSet ( this . fileWatchers . values ( ) , directoryWatchers ) ;
269
+ addWatchersToSet ( this . directoryWatchers . values ( ) , directoryWatchers ) ;
195
270
const obj = Object . create ( null ) ;
196
271
for ( const w of directoryWatchers ) {
197
272
const times = w . getTimes ( ) ;
@@ -206,7 +281,8 @@ class Watchpack extends EventEmitter {
206
281
. EXISTANCE_ONLY_TIME_ENTRY ;
207
282
}
208
283
const directoryWatchers = new Set ( ) ;
209
- addWatchersToSet ( this . watchers , directoryWatchers ) ;
284
+ addWatchersToSet ( this . fileWatchers . values ( ) , directoryWatchers ) ;
285
+ addWatchersToSet ( this . directoryWatchers . values ( ) , directoryWatchers ) ;
210
286
const map = new Map ( ) ;
211
287
for ( const w of directoryWatchers ) {
212
288
const times = w . getTimeInfoEntries ( ) ;
@@ -270,30 +346,24 @@ class Watchpack extends EventEmitter {
270
346
file = file || item ;
271
347
if ( this . paused ) return ;
272
348
this . emit ( "change" , file , mtime , type ) ;
273
- if ( this . aggregateTimeout ) clearTimeout ( this . aggregateTimeout ) ;
349
+ if ( this . aggregateTimer ) clearTimeout ( this . aggregateTimer ) ;
274
350
this . aggregatedRemovals . delete ( item ) ;
275
351
this . aggregatedChanges . add ( item ) ;
276
- this . aggregateTimeout = setTimeout (
277
- this . _onTimeout ,
278
- this . options . aggregateTimeout
279
- ) ;
352
+ this . aggregateTimer = setTimeout ( this . _onTimeout , this . aggregateTimeout ) ;
280
353
}
281
354
282
355
_onRemove ( item , file , type ) {
283
356
file = file || item ;
284
357
if ( this . paused ) return ;
285
358
this . emit ( "remove" , file , type ) ;
286
- if ( this . aggregateTimeout ) clearTimeout ( this . aggregateTimeout ) ;
359
+ if ( this . aggregateTimer ) clearTimeout ( this . aggregateTimer ) ;
287
360
this . aggregatedChanges . delete ( item ) ;
288
361
this . aggregatedRemovals . add ( item ) ;
289
- this . aggregateTimeout = setTimeout (
290
- this . _onTimeout ,
291
- this . options . aggregateTimeout
292
- ) ;
362
+ this . aggregateTimer = setTimeout ( this . _onTimeout , this . aggregateTimeout ) ;
293
363
}
294
364
295
365
_onTimeout ( ) {
296
- this . aggregateTimeout = 0 ;
366
+ this . aggregateTimer = undefined ;
297
367
const changes = this . aggregatedChanges ;
298
368
const removals = this . aggregatedRemovals ;
299
369
this . aggregatedChanges = new Set ( ) ;
0 commit comments