index.js 28 KB

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