index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. "use strict";
  2. const path = require("path");
  3. const os = require("os");
  4. const {
  5. SourceMapConsumer
  6. } = require("source-map");
  7. const {
  8. validate
  9. } = require("schema-utils");
  10. const serialize = require("serialize-javascript");
  11. const {
  12. Worker
  13. } = require("jest-worker");
  14. const {
  15. throttleAll,
  16. terserMinify,
  17. uglifyJsMinify,
  18. swcMinify,
  19. esbuildMinify
  20. } = require("./utils");
  21. const schema = require("./options.json");
  22. const {
  23. minify
  24. } = require("./minify");
  25. /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
  26. /** @typedef {import("webpack").Compiler} Compiler */
  27. /** @typedef {import("webpack").Compilation} Compilation */
  28. /** @typedef {import("webpack").WebpackError} WebpackError */
  29. /** @typedef {import("webpack").Asset} Asset */
  30. /** @typedef {import("./utils.js").TerserECMA} TerserECMA */
  31. /** @typedef {import("./utils.js").TerserOptions} TerserOptions */
  32. /** @typedef {import("jest-worker").Worker} JestWorker */
  33. /** @typedef {import("source-map").RawSourceMap} RawSourceMap */
  34. /** @typedef {RegExp | string} Rule */
  35. /** @typedef {Rule[] | Rule} Rules */
  36. /**
  37. * @callback ExtractCommentsFunction
  38. * @param {any} astNode
  39. * @param {{ value: string, type: 'comment1' | 'comment2' | 'comment3' | 'comment4', pos: number, line: number, col: number }} comment
  40. * @returns {boolean}
  41. */
  42. /**
  43. * @typedef {boolean | 'all' | 'some' | RegExp | ExtractCommentsFunction} ExtractCommentsCondition
  44. */
  45. /**
  46. * @typedef {string | ((fileData: any) => string)} ExtractCommentsFilename
  47. */
  48. /**
  49. * @typedef {boolean | string | ((commentsFile: string) => string)} ExtractCommentsBanner
  50. */
  51. /**
  52. * @typedef {Object} ExtractCommentsObject
  53. * @property {ExtractCommentsCondition} [condition]
  54. * @property {ExtractCommentsFilename} [filename]
  55. * @property {ExtractCommentsBanner} [banner]
  56. */
  57. /**
  58. * @typedef {ExtractCommentsCondition | ExtractCommentsObject} ExtractCommentsOptions
  59. */
  60. /**
  61. * @typedef {Object} MinimizedResult
  62. * @property {string} code
  63. * @property {RawSourceMap} [map]
  64. * @property {Array<Error | string>} [errors]
  65. * @property {Array<Error | string>} [warnings]
  66. * @property {Array<string>} [extractedComments]
  67. */
  68. /**
  69. * @typedef {{ [file: string]: string }} Input
  70. */
  71. /**
  72. * @typedef {{ [key: string]: any }} CustomOptions
  73. */
  74. /**
  75. * @template T
  76. * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType
  77. */
  78. /**
  79. * @typedef {Object} PredefinedOptions
  80. * @property {boolean} [module]
  81. * @property {any} [ecma]
  82. */
  83. /**
  84. * @template T
  85. * @typedef {PredefinedOptions & InferDefaultType<T>} MinimizerOptions
  86. */
  87. /**
  88. * @template T
  89. * @callback BasicMinimizerImplementation
  90. * @param {Input} input
  91. * @param {RawSourceMap | undefined} sourceMap
  92. * @param {MinimizerOptions<T>} minifyOptions
  93. * @param {ExtractCommentsOptions | undefined} extractComments
  94. * @returns {Promise<MinimizedResult>}
  95. */
  96. /**
  97. * @typedef {object} MinimizeFunctionHelpers
  98. * @property {() => string | undefined} [getMinimizerVersion]
  99. */
  100. /**
  101. * @template T
  102. * @typedef {BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} MinimizerImplementation
  103. */
  104. /**
  105. * @template T
  106. * @typedef {Object} InternalOptions
  107. * @property {string} name
  108. * @property {string} input
  109. * @property {RawSourceMap | undefined} inputSourceMap
  110. * @property {ExtractCommentsOptions | undefined} extractComments
  111. * @property {{ implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> }} minimizer
  112. */
  113. /**
  114. * @template T
  115. * @typedef {JestWorker & { transform: (options: string) => MinimizedResult, minify: (options: InternalOptions<T>) => MinimizedResult }} MinimizerWorker
  116. */
  117. /**
  118. * @typedef {undefined | boolean | number} Parallel
  119. */
  120. /**
  121. * @typedef {Object} BasePluginOptions
  122. * @property {Rules} [test]
  123. * @property {Rules} [include]
  124. * @property {Rules} [exclude]
  125. * @property {ExtractCommentsOptions} [extractComments]
  126. * @property {Parallel} [parallel]
  127. */
  128. /**
  129. * @template T
  130. * @typedef {T extends TerserOptions ? { minify?: MinimizerImplementation<T> | undefined, terserOptions?: MinimizerOptions<T> | undefined } : { minify: MinimizerImplementation<T>, terserOptions?: MinimizerOptions<T> | undefined }} DefinedDefaultMinimizerAndOptions
  131. */
  132. /**
  133. * @template T
  134. * @typedef {BasePluginOptions & { minimizer: { implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> } }} InternalPluginOptions
  135. */
  136. /**
  137. * @template [T=TerserOptions]
  138. */
  139. class TerserPlugin {
  140. /**
  141. * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions<T>} [options]
  142. */
  143. constructor(options) {
  144. validate(
  145. /** @type {Schema} */
  146. schema, options || {}, {
  147. name: "Terser Plugin",
  148. baseDataPath: "options"
  149. }); // TODO make `minimizer` option instead `minify` and `terserOptions` in the next major release, also rename `terserMinify` to `terserMinimize`
  150. const {
  151. minify =
  152. /** @type {MinimizerImplementation<T>} */
  153. terserMinify,
  154. terserOptions =
  155. /** @type {MinimizerOptions<T>} */
  156. {},
  157. test = /\.[cm]?js(\?.*)?$/i,
  158. extractComments = true,
  159. parallel = true,
  160. include,
  161. exclude
  162. } = options || {};
  163. /**
  164. * @private
  165. * @type {InternalPluginOptions<T>}
  166. */
  167. this.options = {
  168. test,
  169. extractComments,
  170. parallel,
  171. include,
  172. exclude,
  173. minimizer: {
  174. implementation: minify,
  175. options: terserOptions
  176. }
  177. };
  178. }
  179. /**
  180. * @private
  181. * @param {any} input
  182. * @returns {boolean}
  183. */
  184. static isSourceMap(input) {
  185. // All required options for `new SourceMapConsumer(...options)`
  186. // https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
  187. return Boolean(input && input.version && input.sources && Array.isArray(input.sources) && typeof input.mappings === "string");
  188. }
  189. /**
  190. * @private
  191. * @param {unknown} warning
  192. * @param {string} file
  193. * @returns {Error}
  194. */
  195. static buildWarning(warning, file) {
  196. /**
  197. * @type {Error & { hideStack: true, file: string }}
  198. */
  199. // @ts-ignore
  200. const builtWarning = new Error(warning.toString());
  201. builtWarning.name = "Warning";
  202. builtWarning.hideStack = true;
  203. builtWarning.file = file;
  204. return builtWarning;
  205. }
  206. /**
  207. * @private
  208. * @param {any} error
  209. * @param {string} file
  210. * @param {SourceMapConsumer} [sourceMap]
  211. * @param {Compilation["requestShortener"]} [requestShortener]
  212. * @returns {Error}
  213. */
  214. static buildError(error, file, sourceMap, requestShortener) {
  215. /**
  216. * @type {Error & { file?: string }}
  217. */
  218. let builtError;
  219. if (typeof error === "string") {
  220. builtError = new Error(`${file} from Terser plugin\n${error}`);
  221. builtError.file = file;
  222. return builtError;
  223. }
  224. if (error.line) {
  225. const original = sourceMap && sourceMap.originalPositionFor({
  226. line: error.line,
  227. column: error.col
  228. });
  229. if (original && original.source && requestShortener) {
  230. builtError = new Error(`${file} from Terser plugin\n${error.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : ""}`);
  231. builtError.file = file;
  232. return builtError;
  233. }
  234. builtError = new Error(`${file} from Terser plugin\n${error.message} [${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : ""}`);
  235. builtError.file = file;
  236. return builtError;
  237. }
  238. if (error.stack) {
  239. builtError = new Error(`${file} from Terser plugin\n${typeof error.message !== "undefined" ? error.message : ""}\n${error.stack}`);
  240. builtError.file = file;
  241. return builtError;
  242. }
  243. builtError = new Error(`${file} from Terser plugin\n${error.message}`);
  244. builtError.file = file;
  245. return builtError;
  246. }
  247. /**
  248. * @private
  249. * @param {Parallel} parallel
  250. * @returns {number}
  251. */
  252. static getAvailableNumberOfCores(parallel) {
  253. // In some cases cpus() returns undefined
  254. // https://github.com/nodejs/node/issues/19022
  255. const cpus = os.cpus() || {
  256. length: 1
  257. };
  258. return parallel === true ? cpus.length - 1 : Math.min(Number(parallel) || 0, cpus.length - 1);
  259. }
  260. /**
  261. * @private
  262. * @param {Compiler} compiler
  263. * @param {Compilation} compilation
  264. * @param {Record<string, import("webpack").sources.Source>} assets
  265. * @param {{availableNumberOfCores: number}} optimizeOptions
  266. * @returns {Promise<void>}
  267. */
  268. async optimize(compiler, compilation, assets, optimizeOptions) {
  269. const cache = compilation.getCache("TerserWebpackPlugin");
  270. let numberOfAssets = 0;
  271. const assetsForMinify = await Promise.all(Object.keys(assets).filter(name => {
  272. const {
  273. info
  274. } =
  275. /** @type {Asset} */
  276. compilation.getAsset(name);
  277. if ( // Skip double minimize assets from child compilation
  278. info.minimized || // Skip minimizing for extracted comments assets
  279. info.extractedComments) {
  280. return false;
  281. }
  282. if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind( // eslint-disable-next-line no-undefined
  283. undefined, this.options)(name)) {
  284. return false;
  285. }
  286. return true;
  287. }).map(async name => {
  288. const {
  289. info,
  290. source
  291. } =
  292. /** @type {Asset} */
  293. compilation.getAsset(name);
  294. const eTag = cache.getLazyHashedEtag(source);
  295. const cacheItem = cache.getItemCache(name, eTag);
  296. const output = await cacheItem.getPromise();
  297. if (!output) {
  298. numberOfAssets += 1;
  299. }
  300. return {
  301. name,
  302. info,
  303. inputSource: source,
  304. output,
  305. cacheItem
  306. };
  307. }));
  308. if (assetsForMinify.length === 0) {
  309. return;
  310. }
  311. /** @type {undefined | (() => MinimizerWorker<T>)} */
  312. let getWorker;
  313. /** @type {undefined | MinimizerWorker<T>} */
  314. let initializedWorker;
  315. /** @type {undefined | number} */
  316. let numberOfWorkers;
  317. if (optimizeOptions.availableNumberOfCores > 0) {
  318. // Do not create unnecessary workers when the number of files is less than the available cores, it saves memory
  319. numberOfWorkers = Math.min(numberOfAssets, optimizeOptions.availableNumberOfCores); // eslint-disable-next-line consistent-return
  320. getWorker = () => {
  321. if (initializedWorker) {
  322. return initializedWorker;
  323. }
  324. initializedWorker =
  325. /** @type {MinimizerWorker<T>} */
  326. new Worker(require.resolve("./minify"), {
  327. numWorkers: numberOfWorkers,
  328. enableWorkerThreads: true
  329. }); // https://github.com/facebook/jest/issues/8872#issuecomment-524822081
  330. const workerStdout = initializedWorker.getStdout();
  331. if (workerStdout) {
  332. workerStdout.on("data", chunk => process.stdout.write(chunk));
  333. }
  334. const workerStderr = initializedWorker.getStderr();
  335. if (workerStderr) {
  336. workerStderr.on("data", chunk => process.stderr.write(chunk));
  337. }
  338. return initializedWorker;
  339. };
  340. }
  341. const {
  342. SourceMapSource,
  343. ConcatSource,
  344. RawSource
  345. } = compiler.webpack.sources;
  346. /** @typedef {{ extractedCommentsSource : import("webpack").sources.RawSource, commentsFilename: string }} ExtractedCommentsInfo */
  347. /** @type {Map<string, ExtractedCommentsInfo>} */
  348. const allExtractedComments = new Map();
  349. const scheduledTasks = [];
  350. for (const asset of assetsForMinify) {
  351. scheduledTasks.push(async () => {
  352. const {
  353. name,
  354. inputSource,
  355. info,
  356. cacheItem
  357. } = asset;
  358. let {
  359. output
  360. } = asset;
  361. if (!output) {
  362. let input;
  363. /** @type {RawSourceMap | undefined} */
  364. let inputSourceMap;
  365. const {
  366. source: sourceFromInputSource,
  367. map
  368. } = inputSource.sourceAndMap();
  369. input = sourceFromInputSource;
  370. if (map) {
  371. if (!TerserPlugin.isSourceMap(map)) {
  372. compilation.warnings.push(
  373. /** @type {WebpackError} */
  374. new Error(`${name} contains invalid source map`));
  375. } else {
  376. inputSourceMap =
  377. /** @type {RawSourceMap} */
  378. map;
  379. }
  380. }
  381. if (Buffer.isBuffer(input)) {
  382. input = input.toString();
  383. }
  384. /**
  385. * @type {InternalOptions<T>}
  386. */
  387. const options = {
  388. name,
  389. input,
  390. inputSourceMap,
  391. minimizer: {
  392. implementation: this.options.minimizer.implementation,
  393. // @ts-ignore https://github.com/Microsoft/TypeScript/issues/10727
  394. options: { ...this.options.minimizer.options
  395. }
  396. },
  397. extractComments: this.options.extractComments
  398. };
  399. if (typeof options.minimizer.options.module === "undefined") {
  400. if (typeof info.javascriptModule !== "undefined") {
  401. options.minimizer.options.module = info.javascriptModule;
  402. } else if (/\.mjs(\?.*)?$/i.test(name)) {
  403. options.minimizer.options.module = true;
  404. } else if (/\.cjs(\?.*)?$/i.test(name)) {
  405. options.minimizer.options.module = false;
  406. }
  407. }
  408. if (typeof options.minimizer.options.ecma === "undefined") {
  409. options.minimizer.options.ecma = TerserPlugin.getEcmaVersion(compiler.options.output.environment || {});
  410. }
  411. try {
  412. output = await (getWorker ? getWorker().transform(serialize(options)) : minify(options));
  413. } catch (error) {
  414. const hasSourceMap = inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap);
  415. compilation.errors.push(
  416. /** @type {WebpackError} */
  417. TerserPlugin.buildError(error, name, hasSourceMap ? new SourceMapConsumer(
  418. /** @type {RawSourceMap} */
  419. inputSourceMap) : // eslint-disable-next-line no-undefined
  420. undefined, // eslint-disable-next-line no-undefined
  421. hasSourceMap ? compilation.requestShortener : undefined));
  422. return;
  423. }
  424. if (typeof output.code === "undefined") {
  425. compilation.errors.push(
  426. /** @type {WebpackError} */
  427. new Error(`${name} from Terser plugin\nMinimizer doesn't return result`));
  428. return;
  429. }
  430. if (output.warnings && output.warnings.length > 0) {
  431. output.warnings = output.warnings.map(
  432. /**
  433. * @param {Error | string} item
  434. */
  435. item => TerserPlugin.buildWarning(item, name));
  436. }
  437. if (output.errors && output.errors.length > 0) {
  438. const hasSourceMap = inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap);
  439. output.errors = output.errors.map(
  440. /**
  441. * @param {Error | string} item
  442. */
  443. item => TerserPlugin.buildError(item, name, hasSourceMap ? new SourceMapConsumer(
  444. /** @type {RawSourceMap} */
  445. inputSourceMap) : // eslint-disable-next-line no-undefined
  446. undefined, // eslint-disable-next-line no-undefined
  447. hasSourceMap ? compilation.requestShortener : undefined));
  448. }
  449. let shebang;
  450. if (
  451. /** @type {ExtractCommentsObject} */
  452. this.options.extractComments.banner !== false && output.extractedComments && output.extractedComments.length > 0 && output.code.startsWith("#!")) {
  453. const firstNewlinePosition = output.code.indexOf("\n");
  454. shebang = output.code.substring(0, firstNewlinePosition);
  455. output.code = output.code.substring(firstNewlinePosition + 1);
  456. }
  457. if (output.map) {
  458. output.source = new SourceMapSource(output.code, name, output.map, input,
  459. /** @type {RawSourceMap} */
  460. inputSourceMap, true);
  461. } else {
  462. output.source = new RawSource(output.code);
  463. }
  464. if (output.extractedComments && output.extractedComments.length > 0) {
  465. const commentsFilename =
  466. /** @type {ExtractCommentsObject} */
  467. this.options.extractComments.filename || "[file].LICENSE.txt[query]";
  468. let query = "";
  469. let filename = name;
  470. const querySplit = filename.indexOf("?");
  471. if (querySplit >= 0) {
  472. query = filename.substr(querySplit);
  473. filename = filename.substr(0, querySplit);
  474. }
  475. const lastSlashIndex = filename.lastIndexOf("/");
  476. const basename = lastSlashIndex === -1 ? filename : filename.substr(lastSlashIndex + 1);
  477. const data = {
  478. filename,
  479. basename,
  480. query
  481. };
  482. output.commentsFilename = compilation.getPath(commentsFilename, data);
  483. let banner; // Add a banner to the original file
  484. if (
  485. /** @type {ExtractCommentsObject} */
  486. this.options.extractComments.banner !== false) {
  487. banner =
  488. /** @type {ExtractCommentsObject} */
  489. this.options.extractComments.banner || `For license information please see ${path.relative(path.dirname(name), output.commentsFilename).replace(/\\/g, "/")}`;
  490. if (typeof banner === "function") {
  491. banner = banner(output.commentsFilename);
  492. }
  493. if (banner) {
  494. output.source = new ConcatSource(shebang ? `${shebang}\n` : "", `/*! ${banner} */\n`, output.source);
  495. }
  496. }
  497. const extractedCommentsString = output.extractedComments.sort().join("\n\n");
  498. output.extractedCommentsSource = new RawSource(`${extractedCommentsString}\n`);
  499. }
  500. await cacheItem.storePromise({
  501. source: output.source,
  502. errors: output.errors,
  503. warnings: output.warnings,
  504. commentsFilename: output.commentsFilename,
  505. extractedCommentsSource: output.extractedCommentsSource
  506. });
  507. }
  508. if (output.warnings && output.warnings.length > 0) {
  509. for (const warning of output.warnings) {
  510. compilation.warnings.push(
  511. /** @type {WebpackError} */
  512. warning);
  513. }
  514. }
  515. if (output.errors && output.errors.length > 0) {
  516. for (const error of output.errors) {
  517. compilation.errors.push(
  518. /** @type {WebpackError} */
  519. error);
  520. }
  521. }
  522. /** @type {Record<string, any>} */
  523. const newInfo = {
  524. minimized: true
  525. };
  526. const {
  527. source,
  528. extractedCommentsSource
  529. } = output; // Write extracted comments to commentsFilename
  530. if (extractedCommentsSource) {
  531. const {
  532. commentsFilename
  533. } = output;
  534. newInfo.related = {
  535. license: commentsFilename
  536. };
  537. allExtractedComments.set(name, {
  538. extractedCommentsSource,
  539. commentsFilename
  540. });
  541. }
  542. compilation.updateAsset(name, source, newInfo);
  543. });
  544. }
  545. const limit = getWorker && numberOfAssets > 0 ?
  546. /** @type {number} */
  547. numberOfWorkers : scheduledTasks.length;
  548. await throttleAll(limit, scheduledTasks);
  549. if (initializedWorker) {
  550. await initializedWorker.end();
  551. }
  552. /** @typedef {{ source: import("webpack").sources.Source, commentsFilename: string, from: string }} ExtractedCommentsInfoWIthFrom */
  553. await Array.from(allExtractedComments).sort().reduce(
  554. /**
  555. * @param {Promise<unknown>} previousPromise
  556. * @param {[string, ExtractedCommentsInfo]} extractedComments
  557. * @returns {Promise<ExtractedCommentsInfoWIthFrom>}
  558. */
  559. async (previousPromise, [from, value]) => {
  560. const previous =
  561. /** @type {ExtractedCommentsInfoWIthFrom | undefined} **/
  562. await previousPromise;
  563. const {
  564. commentsFilename,
  565. extractedCommentsSource
  566. } = value;
  567. if (previous && previous.commentsFilename === commentsFilename) {
  568. const {
  569. from: previousFrom,
  570. source: prevSource
  571. } = previous;
  572. const mergedName = `${previousFrom}|${from}`;
  573. const name = `${commentsFilename}|${mergedName}`;
  574. const eTag = [prevSource, extractedCommentsSource].map(item => cache.getLazyHashedEtag(item)).reduce((previousValue, currentValue) => cache.mergeEtags(previousValue, currentValue));
  575. let source = await cache.getPromise(name, eTag);
  576. if (!source) {
  577. source = new ConcatSource(Array.from(new Set([...
  578. /** @type {string}*/
  579. prevSource.source().split("\n\n"), ...
  580. /** @type {string}*/
  581. extractedCommentsSource.source().split("\n\n")])).join("\n\n"));
  582. await cache.storePromise(name, eTag, source);
  583. }
  584. compilation.updateAsset(commentsFilename, source);
  585. return {
  586. source,
  587. commentsFilename,
  588. from: mergedName
  589. };
  590. }
  591. const existingAsset = compilation.getAsset(commentsFilename);
  592. if (existingAsset) {
  593. return {
  594. source: existingAsset.source,
  595. commentsFilename,
  596. from: commentsFilename
  597. };
  598. }
  599. compilation.emitAsset(commentsFilename, extractedCommentsSource, {
  600. extractedComments: true
  601. });
  602. return {
  603. source: extractedCommentsSource,
  604. commentsFilename,
  605. from
  606. };
  607. },
  608. /** @type {Promise<unknown>} */
  609. Promise.resolve());
  610. }
  611. /**
  612. * @private
  613. * @param {any} environment
  614. * @returns {TerserECMA}
  615. */
  616. static getEcmaVersion(environment) {
  617. // ES 6th
  618. if (environment.arrowFunction || environment.const || environment.destructuring || environment.forOf || environment.module) {
  619. return 2015;
  620. } // ES 11th
  621. if (environment.bigIntLiteral || environment.dynamicImport) {
  622. return 2020;
  623. }
  624. return 5;
  625. }
  626. /**
  627. * @param {Compiler} compiler
  628. * @returns {void}
  629. */
  630. apply(compiler) {
  631. const pluginName = this.constructor.name;
  632. const availableNumberOfCores = TerserPlugin.getAvailableNumberOfCores(this.options.parallel);
  633. compiler.hooks.compilation.tap(pluginName, compilation => {
  634. const hooks = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
  635. const data = serialize({
  636. minimizer: typeof this.options.minimizer.implementation.getMinimizerVersion !== "undefined" ? this.options.minimizer.implementation.getMinimizerVersion() || "0.0.0" : "0.0.0",
  637. options: this.options.minimizer.options
  638. });
  639. hooks.chunkHash.tap(pluginName, (chunk, hash) => {
  640. hash.update("TerserPlugin");
  641. hash.update(data);
  642. });
  643. compilation.hooks.processAssets.tapPromise({
  644. name: pluginName,
  645. stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
  646. additionalAssets: true
  647. }, assets => this.optimize(compiler, compilation, assets, {
  648. availableNumberOfCores
  649. }));
  650. compilation.hooks.statsPrinter.tap(pluginName, stats => {
  651. stats.hooks.print.for("asset.info.minimized").tap("terser-webpack-plugin", (minimized, {
  652. green,
  653. formatFlag
  654. }) => minimized ?
  655. /** @type {Function} */
  656. green(
  657. /** @type {Function} */
  658. formatFlag("minimized")) : "");
  659. });
  660. });
  661. }
  662. }
  663. TerserPlugin.terserMinify = terserMinify;
  664. TerserPlugin.uglifyJsMinify = uglifyJsMinify;
  665. TerserPlugin.swcMinify = swcMinify;
  666. TerserPlugin.esbuildMinify = esbuildMinify;
  667. module.exports = TerserPlugin;