index.js 23 KB

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