utils.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. "use strict";
  2. /** @typedef {import("@jridgewell/trace-mapping").SourceMapInput} SourceMapInput */
  3. /** @typedef {import("./index.js").ExtractCommentsOptions} ExtractCommentsOptions */
  4. /** @typedef {import("./index.js").ExtractCommentsFunction} ExtractCommentsFunction */
  5. /** @typedef {import("./index.js").ExtractCommentsCondition} ExtractCommentsCondition */
  6. /** @typedef {import("./index.js").Input} Input */
  7. /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
  8. /** @typedef {import("./index.js").CustomOptions} CustomOptions */
  9. /**
  10. * @template T
  11. * @typedef {import("./index.js").PredefinedOptions<T>} PredefinedOptions
  12. */
  13. /**
  14. * @typedef {Array<string>} ExtractedComments
  15. */
  16. const notSettled = Symbol(`not-settled`);
  17. /**
  18. * @template T
  19. * @typedef {() => Promise<T>} Task
  20. */
  21. /**
  22. * Run tasks with limited concurrency.
  23. * @template T
  24. * @param {number} limit - Limit of tasks that run at once.
  25. * @param {Task<T>[]} tasks - List of tasks to run.
  26. * @returns {Promise<T[]>} A promise that fulfills to an array of the results
  27. */
  28. function throttleAll(limit, tasks) {
  29. if (!Number.isInteger(limit) || limit < 1) {
  30. throw new TypeError(`Expected \`limit\` to be a finite number > 0, got \`${limit}\` (${typeof limit})`);
  31. }
  32. if (!Array.isArray(tasks) || !tasks.every(task => typeof task === `function`)) {
  33. throw new TypeError(`Expected \`tasks\` to be a list of functions returning a promise`);
  34. }
  35. return new Promise((resolve, reject) => {
  36. const result = Array(tasks.length).fill(notSettled);
  37. const entries = tasks.entries();
  38. const next = () => {
  39. const {
  40. done,
  41. value
  42. } = entries.next();
  43. if (done) {
  44. const isLast = !result.includes(notSettled);
  45. if (isLast) resolve( /** @type{T[]} **/result);
  46. return;
  47. }
  48. const [index, task] = value;
  49. /**
  50. * @param {T} x
  51. */
  52. const onFulfilled = x => {
  53. result[index] = x;
  54. next();
  55. };
  56. task().then(onFulfilled, reject);
  57. };
  58. Array(limit).fill(0).forEach(next);
  59. });
  60. }
  61. /* istanbul ignore next */
  62. /**
  63. * @param {Input} input
  64. * @param {SourceMapInput | undefined} sourceMap
  65. * @param {CustomOptions} minimizerOptions
  66. * @param {ExtractCommentsOptions | undefined} extractComments
  67. * @return {Promise<MinimizedResult>}
  68. */
  69. async function terserMinify(input, sourceMap, minimizerOptions, extractComments) {
  70. /**
  71. * @param {any} value
  72. * @returns {boolean}
  73. */
  74. const isObject = value => {
  75. const type = typeof value;
  76. return value != null && (type === "object" || type === "function");
  77. };
  78. /**
  79. * @param {import("terser").MinifyOptions & { sourceMap: undefined } & ({ output: import("terser").FormatOptions & { beautify: boolean } } | { format: import("terser").FormatOptions & { beautify: boolean } })} terserOptions
  80. * @param {ExtractedComments} extractedComments
  81. * @returns {ExtractCommentsFunction}
  82. */
  83. const buildComments = (terserOptions, extractedComments) => {
  84. /** @type {{ [index: string]: ExtractCommentsCondition }} */
  85. const condition = {};
  86. let comments;
  87. if (terserOptions.format) {
  88. ({
  89. comments
  90. } = terserOptions.format);
  91. } else if (terserOptions.output) {
  92. ({
  93. comments
  94. } = terserOptions.output);
  95. }
  96. condition.preserve = typeof comments !== "undefined" ? comments : false;
  97. if (typeof extractComments === "boolean" && extractComments) {
  98. condition.extract = "some";
  99. } else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
  100. condition.extract = extractComments;
  101. } else if (typeof extractComments === "function") {
  102. condition.extract = extractComments;
  103. } else if (extractComments && isObject(extractComments)) {
  104. condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
  105. } else {
  106. // No extract
  107. // Preserve using "commentsOpts" or "some"
  108. condition.preserve = typeof comments !== "undefined" ? comments : "some";
  109. condition.extract = false;
  110. }
  111. // Ensure that both conditions are functions
  112. ["preserve", "extract"].forEach(key => {
  113. /** @type {undefined | string} */
  114. let regexStr;
  115. /** @type {undefined | RegExp} */
  116. let regex;
  117. switch (typeof condition[key]) {
  118. case "boolean":
  119. condition[key] = condition[key] ? () => true : () => false;
  120. break;
  121. case "function":
  122. break;
  123. case "string":
  124. if (condition[key] === "all") {
  125. condition[key] = () => true;
  126. break;
  127. }
  128. if (condition[key] === "some") {
  129. condition[key] = /** @type {ExtractCommentsFunction} */
  130. (astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
  131. break;
  132. }
  133. regexStr = /** @type {string} */condition[key];
  134. condition[key] = /** @type {ExtractCommentsFunction} */
  135. (astNode, comment) => new RegExp( /** @type {string} */regexStr).test(comment.value);
  136. break;
  137. default:
  138. regex = /** @type {RegExp} */condition[key];
  139. condition[key] = /** @type {ExtractCommentsFunction} */
  140. (astNode, comment) => /** @type {RegExp} */regex.test(comment.value);
  141. }
  142. });
  143. // Redefine the comments function to extract and preserve
  144. // comments according to the two conditions
  145. return (astNode, comment) => {
  146. if ( /** @type {{ extract: ExtractCommentsFunction }} */
  147. condition.extract(astNode, comment)) {
  148. const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`;
  149. // Don't include duplicate comments
  150. if (!extractedComments.includes(commentText)) {
  151. extractedComments.push(commentText);
  152. }
  153. }
  154. return /** @type {{ preserve: ExtractCommentsFunction }} */condition.preserve(astNode, comment);
  155. };
  156. };
  157. /**
  158. * @param {PredefinedOptions<import("terser").MinifyOptions> & import("terser").MinifyOptions} [terserOptions={}]
  159. * @returns {import("terser").MinifyOptions & { sourceMap: undefined } & { compress: import("terser").CompressOptions } & ({ output: import("terser").FormatOptions & { beautify: boolean } } | { format: import("terser").FormatOptions & { beautify: boolean } })}
  160. */
  161. const buildTerserOptions = (terserOptions = {}) => {
  162. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  163. return {
  164. ...terserOptions,
  165. compress: typeof terserOptions.compress === "boolean" ? terserOptions.compress ? {} : false : {
  166. ...terserOptions.compress
  167. },
  168. // ecma: terserOptions.ecma,
  169. // ie8: terserOptions.ie8,
  170. // keep_classnames: terserOptions.keep_classnames,
  171. // keep_fnames: terserOptions.keep_fnames,
  172. mangle: terserOptions.mangle == null ? true : typeof terserOptions.mangle === "boolean" ? terserOptions.mangle : {
  173. ...terserOptions.mangle
  174. },
  175. // module: terserOptions.module,
  176. // nameCache: { ...terserOptions.toplevel },
  177. // the `output` option is deprecated
  178. ...(terserOptions.format ? {
  179. format: {
  180. beautify: false,
  181. ...terserOptions.format
  182. }
  183. } : {
  184. output: {
  185. beautify: false,
  186. ...terserOptions.output
  187. }
  188. }),
  189. parse: {
  190. ...terserOptions.parse
  191. },
  192. // safari10: terserOptions.safari10,
  193. // Ignoring sourceMap from options
  194. // eslint-disable-next-line no-undefined
  195. sourceMap: undefined
  196. // toplevel: terserOptions.toplevel
  197. };
  198. };
  199. // eslint-disable-next-line global-require
  200. const {
  201. minify
  202. } = require("terser");
  203. // Copy `terser` options
  204. const terserOptions = buildTerserOptions(minimizerOptions);
  205. // Let terser generate a SourceMap
  206. if (sourceMap) {
  207. // @ts-ignore
  208. terserOptions.sourceMap = {
  209. asObject: true
  210. };
  211. }
  212. /** @type {ExtractedComments} */
  213. const extractedComments = [];
  214. if (terserOptions.output) {
  215. terserOptions.output.comments = buildComments(terserOptions, extractedComments);
  216. } else if (terserOptions.format) {
  217. terserOptions.format.comments = buildComments(terserOptions, extractedComments);
  218. }
  219. if (terserOptions.compress) {
  220. // More optimizations
  221. if (typeof terserOptions.compress.ecma === "undefined") {
  222. terserOptions.compress.ecma = terserOptions.ecma;
  223. }
  224. // https://github.com/webpack/webpack/issues/16135
  225. if (terserOptions.ecma === 5 && typeof terserOptions.compress.arrows === "undefined") {
  226. terserOptions.compress.arrows = false;
  227. }
  228. }
  229. const [[filename, code]] = Object.entries(input);
  230. const result = await minify({
  231. [filename]: code
  232. }, terserOptions);
  233. return {
  234. code: ( /** @type {string} **/result.code),
  235. // @ts-ignore
  236. // eslint-disable-next-line no-undefined
  237. map: result.map ? ( /** @type {SourceMapInput} **/result.map) : undefined,
  238. extractedComments
  239. };
  240. }
  241. /**
  242. * @returns {string | undefined}
  243. */
  244. terserMinify.getMinimizerVersion = () => {
  245. let packageJson;
  246. try {
  247. // eslint-disable-next-line global-require
  248. packageJson = require("terser/package.json");
  249. } catch (error) {
  250. // Ignore
  251. }
  252. return packageJson && packageJson.version;
  253. };
  254. /**
  255. * @returns {boolean | undefined}
  256. */
  257. terserMinify.supportsWorkerThreads = () => true;
  258. /* istanbul ignore next */
  259. /**
  260. * @param {Input} input
  261. * @param {SourceMapInput | undefined} sourceMap
  262. * @param {CustomOptions} minimizerOptions
  263. * @param {ExtractCommentsOptions | undefined} extractComments
  264. * @return {Promise<MinimizedResult>}
  265. */
  266. async function uglifyJsMinify(input, sourceMap, minimizerOptions, extractComments) {
  267. /**
  268. * @param {any} value
  269. * @returns {boolean}
  270. */
  271. const isObject = value => {
  272. const type = typeof value;
  273. return value != null && (type === "object" || type === "function");
  274. };
  275. /**
  276. * @param {import("uglify-js").MinifyOptions & { sourceMap: undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}} uglifyJsOptions
  277. * @param {ExtractedComments} extractedComments
  278. * @returns {ExtractCommentsFunction}
  279. */
  280. const buildComments = (uglifyJsOptions, extractedComments) => {
  281. /** @type {{ [index: string]: ExtractCommentsCondition }} */
  282. const condition = {};
  283. const {
  284. comments
  285. } = uglifyJsOptions.output;
  286. condition.preserve = typeof comments !== "undefined" ? comments : false;
  287. if (typeof extractComments === "boolean" && extractComments) {
  288. condition.extract = "some";
  289. } else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
  290. condition.extract = extractComments;
  291. } else if (typeof extractComments === "function") {
  292. condition.extract = extractComments;
  293. } else if (extractComments && isObject(extractComments)) {
  294. condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
  295. } else {
  296. // No extract
  297. // Preserve using "commentsOpts" or "some"
  298. condition.preserve = typeof comments !== "undefined" ? comments : "some";
  299. condition.extract = false;
  300. }
  301. // Ensure that both conditions are functions
  302. ["preserve", "extract"].forEach(key => {
  303. /** @type {undefined | string} */
  304. let regexStr;
  305. /** @type {undefined | RegExp} */
  306. let regex;
  307. switch (typeof condition[key]) {
  308. case "boolean":
  309. condition[key] = condition[key] ? () => true : () => false;
  310. break;
  311. case "function":
  312. break;
  313. case "string":
  314. if (condition[key] === "all") {
  315. condition[key] = () => true;
  316. break;
  317. }
  318. if (condition[key] === "some") {
  319. condition[key] = /** @type {ExtractCommentsFunction} */
  320. (astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
  321. break;
  322. }
  323. regexStr = /** @type {string} */condition[key];
  324. condition[key] = /** @type {ExtractCommentsFunction} */
  325. (astNode, comment) => new RegExp( /** @type {string} */regexStr).test(comment.value);
  326. break;
  327. default:
  328. regex = /** @type {RegExp} */condition[key];
  329. condition[key] = /** @type {ExtractCommentsFunction} */
  330. (astNode, comment) => /** @type {RegExp} */regex.test(comment.value);
  331. }
  332. });
  333. // Redefine the comments function to extract and preserve
  334. // comments according to the two conditions
  335. return (astNode, comment) => {
  336. if ( /** @type {{ extract: ExtractCommentsFunction }} */
  337. condition.extract(astNode, comment)) {
  338. const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`;
  339. // Don't include duplicate comments
  340. if (!extractedComments.includes(commentText)) {
  341. extractedComments.push(commentText);
  342. }
  343. }
  344. return /** @type {{ preserve: ExtractCommentsFunction }} */condition.preserve(astNode, comment);
  345. };
  346. };
  347. /**
  348. * @param {PredefinedOptions<import("uglify-js").MinifyOptions> & import("uglify-js").MinifyOptions} [uglifyJsOptions={}]
  349. * @returns {import("uglify-js").MinifyOptions & { sourceMap: undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}}
  350. */
  351. const buildUglifyJsOptions = (uglifyJsOptions = {}) => {
  352. // eslint-disable-next-line no-param-reassign
  353. delete minimizerOptions.ecma;
  354. // eslint-disable-next-line no-param-reassign
  355. delete minimizerOptions.module;
  356. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  357. return {
  358. ...uglifyJsOptions,
  359. // warnings: uglifyJsOptions.warnings,
  360. parse: {
  361. ...uglifyJsOptions.parse
  362. },
  363. compress: typeof uglifyJsOptions.compress === "boolean" ? uglifyJsOptions.compress : {
  364. ...uglifyJsOptions.compress
  365. },
  366. mangle: uglifyJsOptions.mangle == null ? true : typeof uglifyJsOptions.mangle === "boolean" ? uglifyJsOptions.mangle : {
  367. ...uglifyJsOptions.mangle
  368. },
  369. output: {
  370. beautify: false,
  371. ...uglifyJsOptions.output
  372. },
  373. // Ignoring sourceMap from options
  374. // eslint-disable-next-line no-undefined
  375. sourceMap: undefined
  376. // toplevel: uglifyJsOptions.toplevel
  377. // nameCache: { ...uglifyJsOptions.toplevel },
  378. // ie8: uglifyJsOptions.ie8,
  379. // keep_fnames: uglifyJsOptions.keep_fnames,
  380. };
  381. };
  382. // eslint-disable-next-line global-require, import/no-extraneous-dependencies
  383. const {
  384. minify
  385. } = require("uglify-js");
  386. // Copy `uglify-js` options
  387. const uglifyJsOptions = buildUglifyJsOptions(minimizerOptions);
  388. // Let terser generate a SourceMap
  389. if (sourceMap) {
  390. // @ts-ignore
  391. uglifyJsOptions.sourceMap = true;
  392. }
  393. /** @type {ExtractedComments} */
  394. const extractedComments = [];
  395. // @ts-ignore
  396. uglifyJsOptions.output.comments = buildComments(uglifyJsOptions, extractedComments);
  397. const [[filename, code]] = Object.entries(input);
  398. const result = await minify({
  399. [filename]: code
  400. }, uglifyJsOptions);
  401. return {
  402. code: result.code,
  403. // eslint-disable-next-line no-undefined
  404. map: result.map ? JSON.parse(result.map) : undefined,
  405. errors: result.error ? [result.error] : [],
  406. warnings: result.warnings || [],
  407. extractedComments
  408. };
  409. }
  410. /**
  411. * @returns {string | undefined}
  412. */
  413. uglifyJsMinify.getMinimizerVersion = () => {
  414. let packageJson;
  415. try {
  416. // eslint-disable-next-line global-require, import/no-extraneous-dependencies
  417. packageJson = require("uglify-js/package.json");
  418. } catch (error) {
  419. // Ignore
  420. }
  421. return packageJson && packageJson.version;
  422. };
  423. /**
  424. * @returns {boolean | undefined}
  425. */
  426. uglifyJsMinify.supportsWorkerThreads = () => true;
  427. /* istanbul ignore next */
  428. /**
  429. * @param {Input} input
  430. * @param {SourceMapInput | undefined} sourceMap
  431. * @param {CustomOptions} minimizerOptions
  432. * @return {Promise<MinimizedResult>}
  433. */
  434. async function swcMinify(input, sourceMap, minimizerOptions) {
  435. /**
  436. * @param {PredefinedOptions<import("@swc/core").JsMinifyOptions> & import("@swc/core").JsMinifyOptions} [swcOptions={}]
  437. * @returns {import("@swc/core").JsMinifyOptions & { sourceMap: undefined } & { compress: import("@swc/core").TerserCompressOptions }}
  438. */
  439. const buildSwcOptions = (swcOptions = {}) => {
  440. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  441. return {
  442. ...swcOptions,
  443. compress: typeof swcOptions.compress === "boolean" ? swcOptions.compress ? {} : false : {
  444. ...swcOptions.compress
  445. },
  446. mangle: swcOptions.mangle == null ? true : typeof swcOptions.mangle === "boolean" ? swcOptions.mangle : {
  447. ...swcOptions.mangle
  448. },
  449. // ecma: swcOptions.ecma,
  450. // keep_classnames: swcOptions.keep_classnames,
  451. // keep_fnames: swcOptions.keep_fnames,
  452. // module: swcOptions.module,
  453. // safari10: swcOptions.safari10,
  454. // toplevel: swcOptions.toplevel
  455. // eslint-disable-next-line no-undefined
  456. sourceMap: undefined
  457. };
  458. };
  459. // eslint-disable-next-line import/no-extraneous-dependencies, global-require
  460. const swc = require("@swc/core");
  461. // Copy `swc` options
  462. const swcOptions = buildSwcOptions(minimizerOptions);
  463. // Let `swc` generate a SourceMap
  464. if (sourceMap) {
  465. // @ts-ignore
  466. swcOptions.sourceMap = true;
  467. }
  468. if (swcOptions.compress) {
  469. // More optimizations
  470. if (typeof swcOptions.compress.ecma === "undefined") {
  471. swcOptions.compress.ecma = swcOptions.ecma;
  472. }
  473. // https://github.com/webpack/webpack/issues/16135
  474. if (swcOptions.ecma === 5 && typeof swcOptions.compress.arrows === "undefined") {
  475. swcOptions.compress.arrows = false;
  476. }
  477. }
  478. const [[filename, code]] = Object.entries(input);
  479. const result = await swc.minify(code, swcOptions);
  480. let map;
  481. if (result.map) {
  482. map = JSON.parse(result.map);
  483. // TODO workaround for swc because `filename` is not preset as in `swc` signature as for `terser`
  484. map.sources = [filename];
  485. delete map.sourcesContent;
  486. }
  487. return {
  488. code: result.code,
  489. map
  490. };
  491. }
  492. /**
  493. * @returns {string | undefined}
  494. */
  495. swcMinify.getMinimizerVersion = () => {
  496. let packageJson;
  497. try {
  498. // eslint-disable-next-line global-require, import/no-extraneous-dependencies
  499. packageJson = require("@swc/core/package.json");
  500. } catch (error) {
  501. // Ignore
  502. }
  503. return packageJson && packageJson.version;
  504. };
  505. /**
  506. * @returns {boolean | undefined}
  507. */
  508. swcMinify.supportsWorkerThreads = () => false;
  509. /* istanbul ignore next */
  510. /**
  511. * @param {Input} input
  512. * @param {SourceMapInput | undefined} sourceMap
  513. * @param {CustomOptions} minimizerOptions
  514. * @return {Promise<MinimizedResult>}
  515. */
  516. async function esbuildMinify(input, sourceMap, minimizerOptions) {
  517. /**
  518. * @param {PredefinedOptions<import("esbuild").TransformOptions> & import("esbuild").TransformOptions} [esbuildOptions={}]
  519. * @returns {import("esbuild").TransformOptions}
  520. */
  521. const buildEsbuildOptions = (esbuildOptions = {}) => {
  522. // eslint-disable-next-line no-param-reassign
  523. delete esbuildOptions.ecma;
  524. if (esbuildOptions.module) {
  525. // eslint-disable-next-line no-param-reassign
  526. esbuildOptions.format = "esm";
  527. }
  528. // eslint-disable-next-line no-param-reassign
  529. delete esbuildOptions.module;
  530. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  531. return {
  532. minify: true,
  533. legalComments: "inline",
  534. ...esbuildOptions,
  535. sourcemap: false
  536. };
  537. };
  538. // eslint-disable-next-line import/no-extraneous-dependencies, global-require
  539. const esbuild = require("esbuild");
  540. // Copy `esbuild` options
  541. const esbuildOptions = buildEsbuildOptions(minimizerOptions);
  542. // Let `esbuild` generate a SourceMap
  543. if (sourceMap) {
  544. esbuildOptions.sourcemap = true;
  545. esbuildOptions.sourcesContent = false;
  546. }
  547. const [[filename, code]] = Object.entries(input);
  548. esbuildOptions.sourcefile = filename;
  549. const result = await esbuild.transform(code, esbuildOptions);
  550. return {
  551. code: result.code,
  552. // eslint-disable-next-line no-undefined
  553. map: result.map ? JSON.parse(result.map) : undefined,
  554. warnings: result.warnings.length > 0 ? result.warnings.map(item => {
  555. const plugin = item.pluginName ? `\nPlugin Name: ${item.pluginName}` : "";
  556. const location = item.location ? `\n\n${item.location.file}:${item.location.line}:${item.location.column}:\n ${item.location.line} | ${item.location.lineText}\n\nSuggestion: ${item.location.suggestion}` : "";
  557. const notes = item.notes.length > 0 ? `\n\nNotes:\n${item.notes.map(note => `${note.location ? `[${note.location.file}:${note.location.line}:${note.location.column}] ` : ""}${note.text}${note.location ? `\nSuggestion: ${note.location.suggestion}` : ""}${note.location ? `\nLine text:\n${note.location.lineText}\n` : ""}`).join("\n")}` : "";
  558. return `${item.text} [${item.id}]${plugin}${location}${item.detail ? `\nDetails:\n${item.detail}` : ""}${notes}`;
  559. }) : []
  560. };
  561. }
  562. /**
  563. * @returns {string | undefined}
  564. */
  565. esbuildMinify.getMinimizerVersion = () => {
  566. let packageJson;
  567. try {
  568. // eslint-disable-next-line global-require, import/no-extraneous-dependencies
  569. packageJson = require("esbuild/package.json");
  570. } catch (error) {
  571. // Ignore
  572. }
  573. return packageJson && packageJson.version;
  574. };
  575. /**
  576. * @returns {boolean | undefined}
  577. */
  578. esbuildMinify.supportsWorkerThreads = () => false;
  579. /**
  580. * @template T
  581. * @param fn {(function(): any) | undefined}
  582. * @returns {function(): T}
  583. */
  584. function memoize(fn) {
  585. let cache = false;
  586. /** @type {T} */
  587. let result;
  588. return () => {
  589. if (cache) {
  590. return result;
  591. }
  592. result = /** @type {function(): any} */fn();
  593. cache = true;
  594. // Allow to clean up memory for fn
  595. // and all dependent resources
  596. // eslint-disable-next-line no-undefined, no-param-reassign
  597. fn = undefined;
  598. return result;
  599. };
  600. }
  601. module.exports = {
  602. throttleAll,
  603. memoize,
  604. terserMinify,
  605. uglifyJsMinify,
  606. swcMinify,
  607. esbuildMinify
  608. };