@@ -22,6 +22,7 @@ const getTinyGlobby = memoize(() => require("tinyglobby"));
2222/** @typedef {import("webpack").Compilation } Compilation */
2323/** @typedef {import("webpack").Asset } Asset */
2424/** @typedef {import("webpack").AssetInfo } AssetInfo */
25+ /** @typedef {import("webpack").InputFileSystem } InputFileSystem */
2526/** @typedef {import("tinyglobby").GlobOptions } GlobbyOptions */
2627/** @typedef {ReturnType<Compilation["getLogger"]> } WebpackLogger */
2728/** @typedef {ReturnType<Compilation["getCache"]> } CacheFacade */
@@ -243,6 +244,63 @@ class CopyPlugin {
243244 return fullContentHash . toString ( ) . slice ( 0 , hashDigestLength ) ;
244245 }
245246
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+
246304 /**
247305 * @private
248306 * @param {typeof import("tinyglobby").glob } globby the globby function to use for globbing
@@ -277,12 +335,13 @@ class CopyPlugin {
277335
278336 logger . debug ( `getting stats for '${ absoluteFrom } '...` ) ;
279337
280- const { inputFileSystem } = compiler ;
338+ const { inputFileSystem } =
339+ /** @type {Compiler & { inputFileSystem: InputFileSystem } } */
340+ ( compiler ) ;
281341
282342 let stats ;
283343
284344 try {
285- // @ts -expect-error - webpack types are incomplete
286345 stats = await stat ( inputFileSystem , absoluteFrom ) ;
287346 } catch {
288347 // Nothing
@@ -291,22 +350,22 @@ class CopyPlugin {
291350 /**
292351 * @type {"file" | "dir" | "glob" }
293352 */
294- let fromType ;
353+ let typeOfFrom ;
295354
296355 if ( stats ) {
297356 if ( stats . isDirectory ( ) ) {
298- fromType = "dir" ;
357+ typeOfFrom = "dir" ;
299358 logger . debug ( `determined '${ absoluteFrom } ' is a directory` ) ;
300359 } else if ( stats . isFile ( ) ) {
301- fromType = "file" ;
360+ typeOfFrom = "file" ;
302361 logger . debug ( `determined '${ absoluteFrom } ' is a file` ) ;
303362 } else {
304363 // Fallback
305- fromType = "glob" ;
364+ typeOfFrom = "glob" ;
306365 logger . debug ( `determined '${ absoluteFrom } ' is unknown` ) ;
307366 }
308367 } else {
309- fromType = "glob" ;
368+ typeOfFrom = "glob" ;
310369 logger . debug ( `determined '${ absoluteFrom } ' is a glob` ) ;
311370 }
312371
@@ -325,12 +384,8 @@ class CopyPlugin {
325384
326385 let glob ;
327386
328- switch ( fromType ) {
387+ switch ( typeOfFrom ) {
329388 case "dir" :
330- compilation . contextDependencies . add ( absoluteFrom ) ;
331-
332- logger . debug ( `added '${ absoluteFrom } ' as a context dependency` ) ;
333-
334389 pattern . context = absoluteFrom ;
335390 glob = path . posix . join (
336391 getTinyGlobby ( ) . escapePath ( getNormalizePath ( ) ( absoluteFrom ) ) ,
@@ -342,10 +397,6 @@ class CopyPlugin {
342397 }
343398 break ;
344399 case "file" :
345- compilation . fileDependencies . add ( absoluteFrom ) ;
346-
347- logger . debug ( `added '${ absoluteFrom } ' as a file dependency` ) ;
348-
349400 pattern . context = path . dirname ( absoluteFrom ) ;
350401 glob = getTinyGlobby ( ) . escapePath ( getNormalizePath ( ) ( absoluteFrom ) ) ;
351402
@@ -355,14 +406,6 @@ class CopyPlugin {
355406 break ;
356407 case "glob" :
357408 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-
366409 glob = path . isAbsolute ( pattern . from )
367410 ? pattern . from
368411 : path . posix . join (
@@ -388,6 +431,14 @@ class CopyPlugin {
388431 }
389432
390433 if ( globEntries . length === 0 ) {
434+ await CopyPlugin . addCompilationDependency (
435+ compilation ,
436+ typeOfFrom ,
437+ absoluteFrom ,
438+ inputFileSystem ,
439+ logger ,
440+ ) ;
441+
391442 if ( pattern . noErrorOnMissing ) {
392443 logger . log (
393444 `finished to process a pattern from '${ pattern . from } ' using '${ pattern . context } ' context to '${ pattern . to } '` ,
@@ -401,6 +452,14 @@ class CopyPlugin {
401452 return ;
402453 }
403454
455+ await CopyPlugin . addCompilationDependency (
456+ compilation ,
457+ typeOfFrom ,
458+ absoluteFrom ,
459+ null ,
460+ logger ,
461+ ) ;
462+
404463 /**
405464 * @type {Array<CopiedResult | undefined> }
406465 */
@@ -475,7 +534,7 @@ class CopyPlugin {
475534 ) ;
476535
477536 // 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" ) {
479538 compilation . fileDependencies . add ( absoluteFilename ) ;
480539
481540 logger . debug ( `added '${ absoluteFilename } ' as a file dependency` ) ;
@@ -540,7 +599,6 @@ class CopyPlugin {
540599 let data ;
541600
542601 try {
543- // @ts -expect-error - webpack types are incomplete
544602 data = await readFile ( inputFileSystem , absoluteFilename ) ;
545603 } catch ( error ) {
546604 compilation . errors . push ( /** @type {Error } */ ( error ) ) ;
0 commit comments