UNPKG

28.9 kBJavaScriptView Raw
1"use strict";
2
3const path = require("path");
4
5const {
6 validate
7} = require("schema-utils");
8
9const serialize = require("serialize-javascript");
10
11const normalizePath = require("normalize-path");
12
13const globParent = require("glob-parent");
14
15const fastGlob = require("fast-glob"); // @ts-ignore
16
17
18const {
19 version
20} = require("../package.json");
21
22const schema = require("./options.json");
23
24const {
25 readFile,
26 stat,
27 throttleAll
28} = require("./utils");
29
30const template = /\[\\*([\w:]+)\\*\]/i;
31/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
32
33/** @typedef {import("webpack").Compiler} Compiler */
34
35/** @typedef {import("webpack").Compilation} Compilation */
36
37/** @typedef {import("webpack").WebpackError} WebpackError */
38
39/** @typedef {import("webpack").Asset} Asset */
40
41/** @typedef {import("globby").Options} GlobbyOptions */
42
43/** @typedef {import("globby").GlobEntry} GlobEntry */
44
45/** @typedef {ReturnType<Compilation["getLogger"]>} WebpackLogger */
46
47/** @typedef {ReturnType<Compilation["getCache"]>} CacheFacade */
48
49/** @typedef {ReturnType<ReturnType<Compilation["getCache"]>["getLazyHashedEtag"]>} Etag */
50
51/** @typedef {ReturnType<Compilation["fileSystemInfo"]["mergeSnapshots"]>} Snapshot */
52
53/**
54 * @typedef {boolean} Force
55 */
56
57/**
58 * @typedef {Object} CopiedResult
59 * @property {string} sourceFilename
60 * @property {string} absoluteFilename
61 * @property {string} filename
62 * @property {Asset["source"]} source
63 * @property {Force | undefined} force
64 * @property {Record<string, any>} info
65 */
66
67/**
68 * @typedef {string} StringPattern
69 */
70
71/**
72 * @typedef {boolean} NoErrorOnMissing
73 */
74
75/**
76 * @typedef {string} Context
77 */
78
79/**
80 * @typedef {string} From
81 */
82
83/**
84 * @callback ToFunction
85 * @param {{ context: string, absoluteFilename?: string }} pathData
86 * @return {string | Promise<string>}
87 */
88
89/**
90 * @typedef {string | ToFunction} To
91 */
92
93/**
94 * @typedef {"dir" | "file" | "template"} ToType
95 */
96
97/**
98 * @callback TransformerFunction
99 * @param {Buffer} input
100 * @param {string} absoluteFilename
101 * @returns {string | Buffer | Promise<string> | Promise<Buffer>}
102 */
103
104/**
105 * @typedef {{ keys: { [key: string]: any } } | { keys: ((defaultCacheKeys: { [key: string]: any }, absoluteFilename: string) => Promise<{ [key: string]: any }>) }} TransformerCacheObject
106 */
107
108/**
109 * @typedef {Object} TransformerObject
110 * @property {TransformerFunction} transformer
111 * @property {boolean | TransformerCacheObject} [cache]
112 */
113
114/**
115 * @typedef {TransformerFunction | TransformerObject} Transform
116 */
117
118/**
119 * @callback Filter
120 * @param {string} filepath
121 * @returns {boolean | Promise<boolean>}
122 */
123
124/**
125 * @callback TransformAllFunction
126 * @param {{ data: Buffer, sourceFilename: string, absoluteFilename: string }[]} data
127 * @returns {string | Buffer | Promise<string> | Promise<Buffer>}
128 */
129
130/**
131 * @typedef { Record<string, any> | ((item: { absoluteFilename: string, sourceFilename: string, filename: string, toType: ToType }) => Record<string, any>) } Info
132 */
133
134/**
135 * @typedef {Object} ObjectPattern
136 * @property {From} from
137 * @property {GlobbyOptions} [globOptions]
138 * @property {Context} [context]
139 * @property {To} [to]
140 * @property {ToType} [toType]
141 * @property {Info} [info]
142 * @property {Filter} [filter]
143 * @property {Transform} [transform]
144 * @property {TransformAllFunction} [transformAll]
145 * @property {Force} [force]
146 * @property {number} [priority]
147 * @property {NoErrorOnMissing} [noErrorOnMissing]
148 */
149
150/**
151 * @typedef {StringPattern | ObjectPattern} Pattern
152 */
153
154/**
155 * @typedef {Object} AdditionalOptions
156 * @property {number} [concurrency]
157 */
158
159/**
160 * @typedef {Object} PluginOptions
161 * @property {Pattern[]} patterns
162 * @property {AdditionalOptions} [options]
163 */
164
165class CopyPlugin {
166 /**
167 * @param {PluginOptions} [options]
168 */
169 constructor(options = {
170 patterns: []
171 }) {
172 validate(
173 /** @type {Schema} */
174 schema, options, {
175 name: "Copy Plugin",
176 baseDataPath: "options"
177 });
178 /**
179 * @private
180 * @type {Pattern[]}
181 */
182
183 this.patterns = options.patterns;
184 /**
185 * @private
186 * @type {AdditionalOptions}
187 */
188
189 this.options = options.options || {};
190 }
191 /**
192 * @private
193 * @param {Compilation} compilation
194 * @param {number} startTime
195 * @param {string} dependency
196 * @returns {Promise<Snapshot | undefined>}
197 */
198
199
200 static async createSnapshot(compilation, startTime, dependency) {
201 // eslint-disable-next-line consistent-return
202 return new Promise((resolve, reject) => {
203 compilation.fileSystemInfo.createSnapshot(startTime, [dependency], // @ts-ignore
204 // eslint-disable-next-line no-undefined
205 undefined, // eslint-disable-next-line no-undefined
206 undefined, null, (error, snapshot) => {
207 if (error) {
208 reject(error);
209 return;
210 }
211
212 resolve(
213 /** @type {Snapshot} */
214 snapshot);
215 });
216 });
217 }
218 /**
219 * @private
220 * @param {Compilation} compilation
221 * @param {Snapshot} snapshot
222 * @returns {Promise<boolean | undefined>}
223 */
224
225
226 static async checkSnapshotValid(compilation, snapshot) {
227 // eslint-disable-next-line consistent-return
228 return new Promise((resolve, reject) => {
229 compilation.fileSystemInfo.checkSnapshotValid(snapshot, (error, isValid) => {
230 if (error) {
231 reject(error);
232 return;
233 }
234
235 resolve(isValid);
236 });
237 });
238 }
239 /**
240 * @private
241 * @param {Compiler} compiler
242 * @param {Compilation} compilation
243 * @param {Buffer} source
244 * @returns {string}
245 */
246
247
248 static getContentHash(compiler, compilation, source) {
249 const {
250 outputOptions
251 } = compilation;
252 const {
253 hashDigest,
254 hashDigestLength,
255 hashFunction,
256 hashSalt
257 } = outputOptions;
258 const hash = compiler.webpack.util.createHash(
259 /** @type {string} */
260 hashFunction);
261
262 if (hashSalt) {
263 hash.update(hashSalt);
264 }
265
266 hash.update(source);
267 const fullContentHash = hash.digest(hashDigest);
268 return fullContentHash.toString().slice(0, hashDigestLength);
269 }
270 /**
271 * @private
272 * @param {typeof import("globby").globby} globby
273 * @param {Compiler} compiler
274 * @param {Compilation} compilation
275 * @param {WebpackLogger} logger
276 * @param {CacheFacade} cache
277 * @param {ObjectPattern & { context: string }} inputPattern
278 * @param {number} index
279 * @returns {Promise<Array<CopiedResult | undefined> | undefined>}
280 */
281
282
283 static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
284 const {
285 RawSource
286 } = compiler.webpack.sources;
287 const pattern = { ...inputPattern
288 };
289 const originalFrom = pattern.from;
290 const normalizedOriginalFrom = path.normalize(originalFrom);
291 logger.log(`starting to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context`);
292 let absoluteFrom;
293
294 if (path.isAbsolute(normalizedOriginalFrom)) {
295 absoluteFrom = normalizedOriginalFrom;
296 } else {
297 absoluteFrom = path.resolve(pattern.context, normalizedOriginalFrom);
298 }
299
300 logger.debug(`getting stats for '${absoluteFrom}'...`);
301 const {
302 inputFileSystem
303 } = compiler;
304 let stats;
305
306 try {
307 stats = await stat(inputFileSystem, absoluteFrom);
308 } catch (error) {// Nothing
309 }
310 /**
311 * @type {"file" | "dir" | "glob"}
312 */
313
314
315 let fromType;
316
317 if (stats) {
318 if (stats.isDirectory()) {
319 fromType = "dir";
320 logger.debug(`determined '${absoluteFrom}' is a directory`);
321 } else if (stats.isFile()) {
322 fromType = "file";
323 logger.debug(`determined '${absoluteFrom}' is a file`);
324 } else {
325 // Fallback
326 fromType = "glob";
327 logger.debug(`determined '${absoluteFrom}' is unknown`);
328 }
329 } else {
330 fromType = "glob";
331 logger.debug(`determined '${absoluteFrom}' is a glob`);
332 }
333 /** @type {GlobbyOptions & { objectMode: true }} */
334
335
336 const globOptions = { ...{
337 followSymbolicLinks: true
338 },
339 ...(pattern.globOptions || {}),
340 ...{
341 cwd: pattern.context,
342 objectMode: true
343 }
344 }; // @ts-ignore
345
346 globOptions.fs = inputFileSystem;
347 let glob;
348
349 switch (fromType) {
350 case "dir":
351 compilation.contextDependencies.add(absoluteFrom);
352 logger.debug(`added '${absoluteFrom}' as a context dependency`);
353 pattern.context = absoluteFrom;
354 glob = path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom))), "**/*");
355 absoluteFrom = path.join(absoluteFrom, "**/*");
356
357 if (typeof globOptions.dot === "undefined") {
358 globOptions.dot = true;
359 }
360
361 break;
362
363 case "file":
364 compilation.fileDependencies.add(absoluteFrom);
365 logger.debug(`added '${absoluteFrom}' as a file dependency`);
366 pattern.context = path.dirname(absoluteFrom);
367 glob = fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom)));
368
369 if (typeof globOptions.dot === "undefined") {
370 globOptions.dot = true;
371 }
372
373 break;
374
375 case "glob":
376 default:
377 {
378 const contextDependencies = path.normalize(globParent(absoluteFrom));
379 compilation.contextDependencies.add(contextDependencies);
380 logger.debug(`added '${contextDependencies}' as a context dependency`);
381 glob = path.isAbsolute(originalFrom) ? originalFrom : path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(pattern.context))), originalFrom);
382 }
383 }
384
385 logger.log(`begin globbing '${glob}'...`);
386 /**
387 * @type {GlobEntry[]}
388 */
389
390 let globEntries;
391
392 try {
393 globEntries = await globby(glob, globOptions);
394 } catch (error) {
395 compilation.errors.push(
396 /** @type {WebpackError} */
397 error);
398 return;
399 }
400
401 if (globEntries.length === 0) {
402 if (pattern.noErrorOnMissing) {
403 logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context to '${pattern.to}'`);
404 return;
405 }
406
407 const missingError = new Error(`unable to locate '${glob}' glob`);
408 compilation.errors.push(
409 /** @type {WebpackError} */
410 missingError);
411 return;
412 }
413 /**
414 * @type {Array<CopiedResult | undefined>}
415 */
416
417
418 let copiedResult;
419
420 try {
421 copiedResult = await Promise.all(globEntries.map(
422 /**
423 * @param {GlobEntry} globEntry
424 * @returns {Promise<CopiedResult | undefined>}
425 */
426 async globEntry => {
427 // Exclude directories
428 if (!globEntry.dirent.isFile()) {
429 return;
430 }
431
432 if (pattern.filter) {
433 let isFiltered;
434
435 try {
436 isFiltered = await pattern.filter(globEntry.path);
437 } catch (error) {
438 compilation.errors.push(
439 /** @type {WebpackError} */
440 error);
441 return;
442 }
443
444 if (!isFiltered) {
445 logger.log(`skip '${globEntry.path}', because it was filtered`);
446 return;
447 }
448 }
449
450 const from = globEntry.path;
451 logger.debug(`found '${from}'`); // `globby`/`fast-glob` return the relative path when the path contains special characters on windows
452
453 const absoluteFilename = path.resolve(pattern.context, from);
454 const to = typeof pattern.to === "function" ? await pattern.to({
455 context: pattern.context,
456 absoluteFilename
457 }) : path.normalize(typeof pattern.to !== "undefined" ? pattern.to : "");
458 const toType = pattern.toType ? pattern.toType : template.test(to) ? "template" : path.extname(to) === "" || to.slice(-1) === path.sep ? "dir" : "file";
459 logger.log(`'to' option '${to}' determinated as '${toType}'`);
460 const relativeFrom = path.relative(pattern.context, absoluteFilename);
461 let filename = toType === "dir" ? path.join(to, relativeFrom) : to;
462
463 if (path.isAbsolute(filename)) {
464 filename = path.relative(
465 /** @type {string} */
466 compiler.options.output.path, filename);
467 }
468
469 logger.log(`determined that '${from}' should write to '${filename}'`);
470 const sourceFilename = normalizePath(path.relative(compiler.context, absoluteFilename)); // If this came from a glob or dir, add it to the file dependencies
471
472 if (fromType === "dir" || fromType === "glob") {
473 compilation.fileDependencies.add(absoluteFilename);
474 logger.debug(`added '${absoluteFilename}' as a file dependency`);
475 }
476
477 let cacheEntry;
478 logger.debug(`getting cache for '${absoluteFilename}'...`);
479
480 try {
481 cacheEntry = await cache.getPromise(`${sourceFilename}|${index}`, null);
482 } catch (error) {
483 compilation.errors.push(
484 /** @type {WebpackError} */
485 error);
486 return;
487 }
488 /**
489 * @type {Asset["source"] | undefined}
490 */
491
492
493 let source;
494
495 if (cacheEntry) {
496 logger.debug(`found cache for '${absoluteFilename}'...`);
497 let isValidSnapshot;
498 logger.debug(`checking snapshot on valid for '${absoluteFilename}'...`);
499
500 try {
501 isValidSnapshot = await CopyPlugin.checkSnapshotValid(compilation, cacheEntry.snapshot);
502 } catch (error) {
503 compilation.errors.push(
504 /** @type {WebpackError} */
505 error);
506 return;
507 }
508
509 if (isValidSnapshot) {
510 logger.debug(`snapshot for '${absoluteFilename}' is valid`);
511 ({
512 source
513 } = cacheEntry);
514 } else {
515 logger.debug(`snapshot for '${absoluteFilename}' is invalid`);
516 }
517 } else {
518 logger.debug(`missed cache for '${absoluteFilename}'`);
519 }
520
521 if (!source) {
522 const startTime = Date.now();
523 logger.debug(`reading '${absoluteFilename}'...`);
524 let data;
525
526 try {
527 data = await readFile(inputFileSystem, absoluteFilename);
528 } catch (error) {
529 compilation.errors.push(
530 /** @type {WebpackError} */
531 error);
532 return;
533 }
534
535 logger.debug(`read '${absoluteFilename}'`);
536 source = new RawSource(data);
537 let snapshot;
538 logger.debug(`creating snapshot for '${absoluteFilename}'...`);
539
540 try {
541 snapshot = await CopyPlugin.createSnapshot(compilation, startTime, absoluteFilename);
542 } catch (error) {
543 compilation.errors.push(
544 /** @type {WebpackError} */
545 error);
546 return;
547 }
548
549 if (snapshot) {
550 logger.debug(`created snapshot for '${absoluteFilename}'`);
551 logger.debug(`storing cache for '${absoluteFilename}'...`);
552
553 try {
554 await cache.storePromise(`${sourceFilename}|${index}`, null, {
555 source,
556 snapshot
557 });
558 } catch (error) {
559 compilation.errors.push(
560 /** @type {WebpackError} */
561 error);
562 return;
563 }
564
565 logger.debug(`stored cache for '${absoluteFilename}'`);
566 }
567 }
568
569 if (pattern.transform) {
570 /**
571 * @type {TransformerObject}
572 */
573 const transformObj = typeof pattern.transform === "function" ? {
574 transformer: pattern.transform
575 } : pattern.transform;
576
577 if (transformObj.transformer) {
578 logger.log(`transforming content for '${absoluteFilename}'...`);
579 const buffer = source.buffer();
580
581 if (transformObj.cache) {
582 // TODO: remove in the next major release
583 const hasher = compiler.webpack && compiler.webpack.util && compiler.webpack.util.createHash ? compiler.webpack.util.createHash("xxhash64") : // eslint-disable-next-line global-require
584 require("crypto").createHash("md4");
585 const defaultCacheKeys = {
586 version,
587 sourceFilename,
588 transform: transformObj.transformer,
589 contentHash: hasher.update(buffer).digest("hex"),
590 index
591 };
592 const cacheKeys = `transform|${serialize(typeof transformObj.cache === "boolean" ? defaultCacheKeys : typeof transformObj.cache.keys === "function" ? await transformObj.cache.keys(defaultCacheKeys, absoluteFilename) : { ...defaultCacheKeys,
593 ...transformObj.cache.keys
594 })}`;
595 logger.debug(`getting transformation cache for '${absoluteFilename}'...`);
596 const cacheItem = cache.getItemCache(cacheKeys, cache.getLazyHashedEtag(source));
597 source = await cacheItem.getPromise();
598 logger.debug(source ? `found transformation cache for '${absoluteFilename}'` : `no transformation cache for '${absoluteFilename}'`);
599
600 if (!source) {
601 const transformed = await transformObj.transformer(buffer, absoluteFilename);
602 source = new RawSource(transformed);
603 logger.debug(`caching transformation for '${absoluteFilename}'...`);
604 await cacheItem.storePromise(source);
605 logger.debug(`cached transformation for '${absoluteFilename}'`);
606 }
607 } else {
608 source = new RawSource(await transformObj.transformer(buffer, absoluteFilename));
609 }
610 }
611 }
612
613 let info = typeof pattern.info === "undefined" ? {} : typeof pattern.info === "function" ? pattern.info({
614 absoluteFilename,
615 sourceFilename,
616 filename,
617 toType
618 }) || {} : pattern.info || {};
619
620 if (toType === "template") {
621 logger.log(`interpolating template '${filename}' for '${sourceFilename}'...`);
622 const contentHash = CopyPlugin.getContentHash(compiler, compilation, source.buffer());
623 const ext = path.extname(sourceFilename);
624 const base = path.basename(sourceFilename);
625 const name = base.slice(0, base.length - ext.length);
626 const data = {
627 filename: normalizePath(path.relative(pattern.context, absoluteFilename)),
628 contentHash,
629 chunk: {
630 name,
631 id:
632 /** @type {string} */
633 sourceFilename,
634 hash: contentHash
635 }
636 };
637 const {
638 path: interpolatedFilename,
639 info: assetInfo
640 } = compilation.getPathWithInfo(normalizePath(filename), data);
641 info = { ...info,
642 ...assetInfo
643 };
644 filename = interpolatedFilename;
645 logger.log(`interpolated template '${filename}' for '${sourceFilename}'`);
646 } else {
647 filename = normalizePath(filename);
648 } // eslint-disable-next-line consistent-return
649
650
651 return {
652 sourceFilename,
653 absoluteFilename,
654 filename,
655 source,
656 info,
657 force: pattern.force
658 };
659 }));
660 } catch (error) {
661 compilation.errors.push(
662 /** @type {WebpackError} */
663 error);
664 return;
665 }
666
667 if (copiedResult.length === 0) {
668 if (pattern.noErrorOnMissing) {
669 logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context to '${pattern.to}'`);
670 return;
671 }
672
673 const missingError = new Error(`unable to locate '${glob}' glob after filtering paths`);
674 compilation.errors.push(
675 /** @type {WebpackError} */
676 missingError);
677 return;
678 }
679
680 logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context`); // eslint-disable-next-line consistent-return
681
682 return copiedResult;
683 }
684 /**
685 * @param {Compiler} compiler
686 */
687
688
689 apply(compiler) {
690 const pluginName = this.constructor.name;
691 compiler.hooks.thisCompilation.tap(pluginName, compilation => {
692 const logger = compilation.getLogger("copy-webpack-plugin");
693 const cache = compilation.getCache("CopyWebpackPlugin");
694 /**
695 * @type {typeof import("globby").globby}
696 */
697
698 let globby;
699 compilation.hooks.processAssets.tapAsync({
700 name: "copy-webpack-plugin",
701 stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
702 }, async (unusedAssets, callback) => {
703 if (typeof globby === "undefined") {
704 try {
705 // @ts-ignore
706 ({
707 globby
708 } = await import("globby"));
709 } catch (error) {
710 callback(
711 /** @type {Error} */
712 error);
713 return;
714 }
715 }
716
717 logger.log("starting to add additional assets...");
718 const copiedResultMap = new Map();
719 /**
720 * @type {(() => Promise<void>)[]}
721 */
722
723 const scheduledTasks = [];
724 this.patterns.map(
725 /**
726 * @param {Pattern} item
727 * @param {number} index
728 * @return {number}
729 */
730 (item, index) => scheduledTasks.push(async () => {
731 /**
732 * @type {ObjectPattern}
733 */
734 const normalizedPattern = typeof item === "string" ? {
735 from: item
736 } : { ...item
737 };
738 const context = typeof normalizedPattern.context === "undefined" ? compiler.context : path.isAbsolute(normalizedPattern.context) ? normalizedPattern.context : path.join(compiler.context, normalizedPattern.context);
739 normalizedPattern.context = context;
740 /**
741 * @type {Array<CopiedResult | undefined> | undefined}
742 */
743
744 let copiedResult;
745
746 try {
747 copiedResult = await CopyPlugin.runPattern(globby, compiler, compilation, logger, cache,
748 /** @type {ObjectPattern & { context: string }} */
749 normalizedPattern, index);
750 } catch (error) {
751 compilation.errors.push(
752 /** @type {WebpackError} */
753 error);
754 return;
755 }
756
757 if (!copiedResult) {
758 return;
759 }
760 /**
761 * @type {Array<CopiedResult>}
762 */
763
764
765 let filteredCopiedResult = copiedResult.filter(
766 /**
767 * @param {CopiedResult | undefined} result
768 * @returns {result is CopiedResult}
769 */
770 result => Boolean(result));
771
772 if (typeof normalizedPattern.transformAll !== "undefined") {
773 if (typeof normalizedPattern.to === "undefined") {
774 compilation.errors.push(
775 /** @type {WebpackError} */
776 new Error(`Invalid "pattern.to" for the "pattern.from": "${normalizedPattern.from}" and "pattern.transformAll" function. The "to" option must be specified.`));
777 return;
778 }
779
780 filteredCopiedResult.sort((a, b) => a.absoluteFilename > b.absoluteFilename ? 1 : a.absoluteFilename < b.absoluteFilename ? -1 : 0);
781 const mergedEtag = filteredCopiedResult.length === 1 ? cache.getLazyHashedEtag(filteredCopiedResult[0].source) : filteredCopiedResult.reduce(
782 /**
783 * @param {Etag} accumulator
784 * @param {CopiedResult} asset
785 * @param {number} i
786 * @return {Etag}
787 */
788 // @ts-ignore
789 (accumulator, asset, i) => {
790 // eslint-disable-next-line no-param-reassign
791 accumulator = cache.mergeEtags(i === 1 ? cache.getLazyHashedEtag(
792 /** @type {CopiedResult}*/
793 accumulator.source) : accumulator, cache.getLazyHashedEtag(asset.source));
794 return accumulator;
795 });
796 const cacheItem = cache.getItemCache(`transformAll|${serialize({
797 version,
798 from: normalizedPattern.from,
799 to: normalizedPattern.to,
800 transformAll: normalizedPattern.transformAll
801 })}`, mergedEtag);
802 let transformedAsset = await cacheItem.getPromise();
803
804 if (!transformedAsset) {
805 transformedAsset = {
806 filename: normalizedPattern.to
807 };
808
809 try {
810 transformedAsset.data = await normalizedPattern.transformAll(filteredCopiedResult.map(asset => {
811 return {
812 data: asset.source.buffer(),
813 sourceFilename: asset.sourceFilename,
814 absoluteFilename: asset.absoluteFilename
815 };
816 }));
817 } catch (error) {
818 compilation.errors.push(
819 /** @type {WebpackError} */
820 error);
821 return;
822 }
823
824 const filename = typeof normalizedPattern.to === "function" ? await normalizedPattern.to({
825 context
826 }) : normalizedPattern.to;
827
828 if (template.test(filename)) {
829 const contentHash = CopyPlugin.getContentHash(compiler, compilation, transformedAsset.data);
830 const {
831 path: interpolatedFilename,
832 info: assetInfo
833 } = compilation.getPathWithInfo(normalizePath(filename), {
834 contentHash,
835 chunk: {
836 id: "unknown-copied-asset",
837 hash: contentHash
838 }
839 });
840 transformedAsset.filename = interpolatedFilename;
841 transformedAsset.info = assetInfo;
842 }
843
844 const {
845 RawSource
846 } = compiler.webpack.sources;
847 transformedAsset.source = new RawSource(transformedAsset.data);
848 transformedAsset.force = normalizedPattern.force;
849 await cacheItem.storePromise(transformedAsset);
850 }
851
852 filteredCopiedResult = [transformedAsset];
853 }
854
855 const priority = normalizedPattern.priority || 0;
856
857 if (!copiedResultMap.has(priority)) {
858 copiedResultMap.set(priority, []);
859 }
860
861 copiedResultMap.get(priority).push(...filteredCopiedResult);
862 }));
863 await throttleAll(this.options.concurrency || 100, scheduledTasks);
864 const copiedResult = [...copiedResultMap.entries()].sort((a, b) => a[0] - b[0]); // Avoid writing assets inside `p-limit`, because it creates concurrency.
865 // It could potentially lead to an error - 'Multiple assets emit different content to the same filename'
866
867 copiedResult.reduce((acc, val) => acc.concat(val[1]), []).filter(Boolean).forEach(
868 /**
869 * @param {CopiedResult} result
870 * @returns {void}
871 */
872 result => {
873 const {
874 absoluteFilename,
875 sourceFilename,
876 filename,
877 source,
878 force
879 } = result;
880 const existingAsset = compilation.getAsset(filename);
881
882 if (existingAsset) {
883 if (force) {
884 const info = {
885 copied: true,
886 sourceFilename
887 };
888 logger.log(`force updating '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists...`);
889 compilation.updateAsset(filename, source, { ...info,
890 ...result.info
891 });
892 logger.log(`force updated '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists`);
893 return;
894 }
895
896 logger.log(`skip adding '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists`);
897 return;
898 }
899
900 const info = {
901 copied: true,
902 sourceFilename
903 };
904 logger.log(`writing '${filename}' from '${absoluteFilename}' to compilation assets...`);
905 compilation.emitAsset(filename, source, { ...info,
906 ...result.info
907 });
908 logger.log(`written '${filename}' from '${absoluteFilename}' to compilation assets`);
909 });
910 logger.log("finished to adding additional assets");
911 callback();
912 });
913
914 if (compilation.hooks.statsPrinter) {
915 compilation.hooks.statsPrinter.tap(pluginName, stats => {
916 stats.hooks.print.for("asset.info.copied").tap("copy-webpack-plugin", (copied, {
917 green,
918 formatFlag
919 }) => copied ?
920 /** @type {Function} */
921 green(
922 /** @type {Function} */
923 formatFlag("copied")) : "");
924 });
925 }
926 });
927 }
928
929}
930
931module.exports = CopyPlugin;
\No newline at end of file