@@ -22,6 +22,7 @@ const getTinyGlobby = memoize(() => require("tinyglobby"));
22
22
/** @typedef {import("webpack").Compilation } Compilation */
23
23
/** @typedef {import("webpack").Asset } Asset */
24
24
/** @typedef {import("webpack").AssetInfo } AssetInfo */
25
+ /** @typedef {import("webpack").InputFileSystem } InputFileSystem */
25
26
/** @typedef {import("tinyglobby").GlobOptions } GlobbyOptions */
26
27
/** @typedef {ReturnType<Compilation["getLogger"]> } WebpackLogger */
27
28
/** @typedef {ReturnType<Compilation["getCache"]> } CacheFacade */
@@ -243,6 +244,63 @@ class CopyPlugin {
243
244
return fullContentHash . toString ( ) . slice ( 0 , hashDigestLength ) ;
244
245
}
245
246
247
+ /**
248
+ * @private
249
+ * @param {Compilation } compilation the compilation
250
+ * @param {"file" | "dir" | "glob" } typeOfFrom the type of from
251
+ * @param {string } absoluteFrom the source content to hash
252
+ * @param {InputFileSystem | null } inputFileSystem input file system
253
+ * @param {WebpackLogger } logger the logger to use for logging
254
+ * @returns {Promise<void> }
255
+ */
256
+ static async addCompilationDependency (
257
+ compilation ,
258
+ typeOfFrom ,
259
+ absoluteFrom ,
260
+ inputFileSystem ,
261
+ logger ,
262
+ ) {
263
+ switch ( typeOfFrom ) {
264
+ case "dir" :
265
+ compilation . contextDependencies . add ( absoluteFrom ) ;
266
+ logger . debug ( `added '${ absoluteFrom } ' as a context dependency` ) ;
267
+ break ;
268
+ case "file" :
269
+ compilation . fileDependencies . add ( absoluteFrom ) ;
270
+ logger . debug ( `added '${ absoluteFrom } ' as a file dependency` ) ;
271
+ break ;
272
+ case "glob" :
273
+ default : {
274
+ const contextDependency = getTinyGlobby ( ) . isDynamicPattern ( absoluteFrom )
275
+ ? path . normalize ( getGlobParent ( ) ( absoluteFrom ) )
276
+ : path . normalize ( absoluteFrom ) ;
277
+
278
+ let stats ;
279
+
280
+ // If we have `inputFileSystem` we should check the glob is existing or not
281
+ if ( inputFileSystem ) {
282
+ try {
283
+ stats = await stat ( inputFileSystem , contextDependency ) ;
284
+ } catch {
285
+ // Nothing
286
+ }
287
+ }
288
+
289
+ // To prevent double compilation during aggregation (initial run) - https://github.com/webpack-contrib/copy-webpack-plugin/issues/806.
290
+ // On first run we don't know if the glob exists or not, adding the dependency to the context dependencies triggers the `removed` event during aggregation.
291
+ // To prevent this behavior we should add the glob to the missing dependencies if the glob doesn't exist,
292
+ // otherwise we should add the dependency to the context dependencies.
293
+ if ( inputFileSystem && ! stats ) {
294
+ compilation . missingDependencies . add ( contextDependency ) ;
295
+ logger . debug ( `added '${ contextDependency } ' as a missing dependency` ) ;
296
+ } else {
297
+ compilation . contextDependencies . add ( contextDependency ) ;
298
+ logger . debug ( `added '${ contextDependency } ' as a context dependency` ) ;
299
+ }
300
+ }
301
+ }
302
+ }
303
+
246
304
/**
247
305
* @private
248
306
* @param {typeof import("tinyglobby").glob } globby the globby function to use for globbing
@@ -277,12 +335,13 @@ class CopyPlugin {
277
335
278
336
logger . debug ( `getting stats for '${ absoluteFrom } '...` ) ;
279
337
280
- const { inputFileSystem } = compiler ;
338
+ const { inputFileSystem } =
339
+ /** @type {Compiler & { inputFileSystem: InputFileSystem } } */
340
+ ( compiler ) ;
281
341
282
342
let stats ;
283
343
284
344
try {
285
- // @ts -expect-error - webpack types are incomplete
286
345
stats = await stat ( inputFileSystem , absoluteFrom ) ;
287
346
} catch {
288
347
// Nothing
@@ -291,22 +350,22 @@ class CopyPlugin {
291
350
/**
292
351
* @type {"file" | "dir" | "glob" }
293
352
*/
294
- let fromType ;
353
+ let typeOfFrom ;
295
354
296
355
if ( stats ) {
297
356
if ( stats . isDirectory ( ) ) {
298
- fromType = "dir" ;
357
+ typeOfFrom = "dir" ;
299
358
logger . debug ( `determined '${ absoluteFrom } ' is a directory` ) ;
300
359
} else if ( stats . isFile ( ) ) {
301
- fromType = "file" ;
360
+ typeOfFrom = "file" ;
302
361
logger . debug ( `determined '${ absoluteFrom } ' is a file` ) ;
303
362
} else {
304
363
// Fallback
305
- fromType = "glob" ;
364
+ typeOfFrom = "glob" ;
306
365
logger . debug ( `determined '${ absoluteFrom } ' is unknown` ) ;
307
366
}
308
367
} else {
309
- fromType = "glob" ;
368
+ typeOfFrom = "glob" ;
310
369
logger . debug ( `determined '${ absoluteFrom } ' is a glob` ) ;
311
370
}
312
371
@@ -325,12 +384,8 @@ class CopyPlugin {
325
384
326
385
let glob ;
327
386
328
- switch ( fromType ) {
387
+ switch ( typeOfFrom ) {
329
388
case "dir" :
330
- compilation . contextDependencies . add ( absoluteFrom ) ;
331
-
332
- logger . debug ( `added '${ absoluteFrom } ' as a context dependency` ) ;
333
-
334
389
pattern . context = absoluteFrom ;
335
390
glob = path . posix . join (
336
391
getTinyGlobby ( ) . escapePath ( getNormalizePath ( ) ( absoluteFrom ) ) ,
@@ -342,10 +397,6 @@ class CopyPlugin {
342
397
}
343
398
break ;
344
399
case "file" :
345
- compilation . fileDependencies . add ( absoluteFrom ) ;
346
-
347
- logger . debug ( `added '${ absoluteFrom } ' as a file dependency` ) ;
348
-
349
400
pattern . context = path . dirname ( absoluteFrom ) ;
350
401
glob = getTinyGlobby ( ) . escapePath ( getNormalizePath ( ) ( absoluteFrom ) ) ;
351
402
@@ -355,14 +406,6 @@ class CopyPlugin {
355
406
break ;
356
407
case "glob" :
357
408
default : {
358
- const contextDependencies = path . normalize (
359
- getGlobParent ( ) ( absoluteFrom ) ,
360
- ) ;
361
-
362
- compilation . contextDependencies . add ( contextDependencies ) ;
363
-
364
- logger . debug ( `added '${ contextDependencies } ' as a context dependency` ) ;
365
-
366
409
glob = path . isAbsolute ( pattern . from )
367
410
? pattern . from
368
411
: path . posix . join (
@@ -388,6 +431,14 @@ class CopyPlugin {
388
431
}
389
432
390
433
if ( globEntries . length === 0 ) {
434
+ await CopyPlugin . addCompilationDependency (
435
+ compilation ,
436
+ typeOfFrom ,
437
+ absoluteFrom ,
438
+ inputFileSystem ,
439
+ logger ,
440
+ ) ;
441
+
391
442
if ( pattern . noErrorOnMissing ) {
392
443
logger . log (
393
444
`finished to process a pattern from '${ pattern . from } ' using '${ pattern . context } ' context to '${ pattern . to } '` ,
@@ -401,6 +452,14 @@ class CopyPlugin {
401
452
return ;
402
453
}
403
454
455
+ await CopyPlugin . addCompilationDependency (
456
+ compilation ,
457
+ typeOfFrom ,
458
+ absoluteFrom ,
459
+ null ,
460
+ logger ,
461
+ ) ;
462
+
404
463
/**
405
464
* @type {Array<CopiedResult | undefined> }
406
465
*/
@@ -475,7 +534,7 @@ class CopyPlugin {
475
534
) ;
476
535
477
536
// If this came from a glob or dir, add it to the file dependencies
478
- if ( fromType === "dir" || fromType === "glob" ) {
537
+ if ( typeOfFrom === "dir" || typeOfFrom === "glob" ) {
479
538
compilation . fileDependencies . add ( absoluteFilename ) ;
480
539
481
540
logger . debug ( `added '${ absoluteFilename } ' as a file dependency` ) ;
@@ -540,7 +599,6 @@ class CopyPlugin {
540
599
let data ;
541
600
542
601
try {
543
- // @ts -expect-error - webpack types are incomplete
544
602
data = await readFile ( inputFileSystem , absoluteFilename ) ;
545
603
} catch ( error ) {
546
604
compilation . errors . push ( /** @type {Error } */ ( error ) ) ;
0 commit comments