index.js 28 KB

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