index.js 24 KB

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