utils.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. "use strict";
  2. /** @typedef {import("./index.js").ExtractCommentsOptions} ExtractCommentsOptions */
  3. /** @typedef {import("./index.js").ExtractCommentsFunction} ExtractCommentsFunction */
  4. /** @typedef {import("./index.js").ExtractCommentsCondition} ExtractCommentsCondition */
  5. /** @typedef {import("./index.js").Input} Input */
  6. /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
  7. /** @typedef {import("./index.js").CustomOptions} CustomOptions */
  8. /** @typedef {import("./index.js").RawSourceMap} RawSourceMap */
  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. return new Promise((resolve, reject) => {
  30. const result = Array.from({
  31. length: tasks.length
  32. }).fill(notSettled);
  33. const entries = tasks.entries();
  34. const next = () => {
  35. const {
  36. done,
  37. value
  38. } = entries.next();
  39. if (done) {
  40. const isLast = !result.includes(notSettled);
  41. if (isLast) resolve(result);
  42. return;
  43. }
  44. const [index, task] = value;
  45. /**
  46. * @param {T} resultValue Result value
  47. */
  48. const onFulfilled = resultValue => {
  49. result[index] = resultValue;
  50. next();
  51. };
  52. task().then(onFulfilled, reject);
  53. };
  54. for (let i = 0; i < limit; i++) {
  55. next();
  56. }
  57. });
  58. }
  59. /* istanbul ignore next */
  60. /**
  61. * @param {Input} input input
  62. * @param {RawSourceMap=} sourceMap source map
  63. * @param {CustomOptions=} minimizerOptions options
  64. * @param {ExtractCommentsOptions=} extractComments extract comments option
  65. * @returns {Promise<MinimizedResult>} minimized result
  66. */
  67. async function terserMinify(input, sourceMap, minimizerOptions, extractComments) {
  68. // eslint-disable-next-line jsdoc/no-restricted-syntax
  69. /**
  70. * @param {unknown} value value
  71. * @returns {value is object} true when value is object or function
  72. */
  73. const isObject = value => {
  74. const type = typeof value;
  75. // eslint-disable-next-line no-eq-null, eqeqeq
  76. return value != null && (type === "object" || type === "function");
  77. };
  78. /**
  79. * @param {import("terser").MinifyOptions & { sourceMap: import("terser").SourceMapOptions | undefined } & ({ output: import("terser").FormatOptions & { beautify: boolean } } | { format: import("terser").FormatOptions & { beautify: boolean } })} terserOptions terser options
  80. * @param {ExtractedComments} extractedComments extracted comments
  81. * @returns {ExtractCommentsFunction} function to extract comments
  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. for (const key of ["preserve", "extract"]) {
  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 terser options
  159. * @returns {import("terser").MinifyOptions & { sourceMap: import("terser").SourceMapOptions | undefined } & { compress: import("terser").CompressOptions } & ({ output: import("terser").FormatOptions & { beautify: boolean } } | { format: import("terser").FormatOptions & { beautify: boolean } })} built terser options
  160. */
  161. const buildTerserOptions = (terserOptions = {}) => (
  162. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  163. {
  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:
  173. // eslint-disable-next-line no-eq-null, eqeqeq
  174. terserOptions.mangle == null ? true : typeof terserOptions.mangle === "boolean" ? terserOptions.mangle : {
  175. ...terserOptions.mangle
  176. },
  177. // module: terserOptions.module,
  178. // nameCache: { ...terserOptions.toplevel },
  179. // the `output` option is deprecated
  180. ...(terserOptions.format ? {
  181. format: {
  182. beautify: false,
  183. ...terserOptions.format
  184. }
  185. } : {
  186. output: {
  187. beautify: false,
  188. ...terserOptions.output
  189. }
  190. }),
  191. parse: {
  192. ...terserOptions.parse
  193. },
  194. // safari10: terserOptions.safari10,
  195. // Ignoring sourceMap from options
  196. sourceMap: undefined
  197. // toplevel: terserOptions.toplevel
  198. });
  199. let minify;
  200. try {
  201. ({
  202. minify
  203. } = require("terser"));
  204. } catch (err) {
  205. return {
  206. errors: [( /** @type {Error} */err)]
  207. };
  208. }
  209. // Copy `terser` options
  210. const terserOptions = buildTerserOptions(minimizerOptions);
  211. // Let terser generate a SourceMap
  212. if (sourceMap) {
  213. terserOptions.sourceMap = {
  214. asObject: true
  215. };
  216. }
  217. /** @type {ExtractedComments} */
  218. const extractedComments = [];
  219. if (terserOptions.output) {
  220. terserOptions.output.comments = buildComments(terserOptions, extractedComments);
  221. } else if (terserOptions.format) {
  222. terserOptions.format.comments = buildComments(terserOptions, extractedComments);
  223. }
  224. if (terserOptions.compress) {
  225. // More optimizations
  226. if (typeof terserOptions.compress.ecma === "undefined") {
  227. terserOptions.compress.ecma = terserOptions.ecma;
  228. }
  229. // https://github.com/webpack/webpack/issues/16135
  230. if (terserOptions.ecma === 5 && typeof terserOptions.compress.arrows === "undefined") {
  231. terserOptions.compress.arrows = false;
  232. }
  233. }
  234. const [[filename, code]] = Object.entries(input);
  235. const result = await minify({
  236. [filename]: code
  237. }, terserOptions);
  238. return {
  239. code: ( /** @type {string} * */result.code),
  240. map: result.map ? ( /** @type {RawSourceMap} * */result.map) : undefined,
  241. extractedComments
  242. };
  243. }
  244. /**
  245. * @returns {string | undefined} the minimizer version
  246. */
  247. terserMinify.getMinimizerVersion = () => {
  248. let packageJson;
  249. try {
  250. packageJson = require("terser/package.json");
  251. } catch (_err) {
  252. // Ignore
  253. }
  254. return packageJson && packageJson.version;
  255. };
  256. /**
  257. * @returns {boolean | undefined} true if worker thread is supported, false otherwise
  258. */
  259. terserMinify.supportsWorkerThreads = () => true;
  260. /* istanbul ignore next */
  261. /**
  262. * @param {Input} input input
  263. * @param {RawSourceMap=} sourceMap source map
  264. * @param {CustomOptions=} minimizerOptions options
  265. * @param {ExtractCommentsOptions=} extractComments extract comments option
  266. * @returns {Promise<MinimizedResult>} minimized result
  267. */
  268. async function uglifyJsMinify(input, sourceMap, minimizerOptions, extractComments) {
  269. // eslint-disable-next-line jsdoc/no-restricted-syntax
  270. /**
  271. * @param {unknown} value value
  272. * @returns {value is object} true when value is object or function
  273. */
  274. const isObject = value => {
  275. const type = typeof value;
  276. // eslint-disable-next-line no-eq-null, eqeqeq
  277. return value != null && (type === "object" || type === "function");
  278. };
  279. /**
  280. * @param {import("uglify-js").MinifyOptions & { sourceMap: boolean | import("uglify-js").SourceMapOptions | undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}} uglifyJsOptions uglify-js options
  281. * @param {ExtractedComments} extractedComments extracted comments
  282. * @returns {ExtractCommentsFunction} extract comments function
  283. */
  284. const buildComments = (uglifyJsOptions, extractedComments) => {
  285. /** @type {{ [index: string]: ExtractCommentsCondition }} */
  286. const condition = {};
  287. const {
  288. comments
  289. } = uglifyJsOptions.output;
  290. condition.preserve = typeof comments !== "undefined" ? comments : false;
  291. if (typeof extractComments === "boolean" && extractComments) {
  292. condition.extract = "some";
  293. } else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
  294. condition.extract = extractComments;
  295. } else if (typeof extractComments === "function") {
  296. condition.extract = extractComments;
  297. } else if (extractComments && isObject(extractComments)) {
  298. condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
  299. } else {
  300. // No extract
  301. // Preserve using "commentsOpts" or "some"
  302. condition.preserve = typeof comments !== "undefined" ? comments : "some";
  303. condition.extract = false;
  304. }
  305. // Ensure that both conditions are functions
  306. for (const key of ["preserve", "extract"]) {
  307. /** @type {undefined | string} */
  308. let regexStr;
  309. /** @type {undefined | RegExp} */
  310. let regex;
  311. switch (typeof condition[key]) {
  312. case "boolean":
  313. condition[key] = condition[key] ? () => true : () => false;
  314. break;
  315. case "function":
  316. break;
  317. case "string":
  318. if (condition[key] === "all") {
  319. condition[key] = () => true;
  320. break;
  321. }
  322. if (condition[key] === "some") {
  323. condition[key] = /** @type {ExtractCommentsFunction} */
  324. (astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
  325. break;
  326. }
  327. regexStr = /** @type {string} */condition[key];
  328. condition[key] = /** @type {ExtractCommentsFunction} */
  329. (astNode, comment) => new RegExp( /** @type {string} */regexStr).test(comment.value);
  330. break;
  331. default:
  332. regex = /** @type {RegExp} */condition[key];
  333. condition[key] = /** @type {ExtractCommentsFunction} */
  334. (astNode, comment) => /** @type {RegExp} */regex.test(comment.value);
  335. }
  336. }
  337. // Redefine the comments function to extract and preserve
  338. // comments according to the two conditions
  339. return (astNode, comment) => {
  340. if ( /** @type {{ extract: ExtractCommentsFunction }} */
  341. condition.extract(astNode, comment)) {
  342. const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`;
  343. // Don't include duplicate comments
  344. if (!extractedComments.includes(commentText)) {
  345. extractedComments.push(commentText);
  346. }
  347. }
  348. return /** @type {{ preserve: ExtractCommentsFunction }} */condition.preserve(astNode, comment);
  349. };
  350. };
  351. /**
  352. * @param {PredefinedOptions<import("uglify-js").MinifyOptions> & import("uglify-js").MinifyOptions=} uglifyJsOptions uglify-js options
  353. * @returns {import("uglify-js").MinifyOptions & { sourceMap: boolean | import("uglify-js").SourceMapOptions | undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}} uglify-js options
  354. */
  355. const buildUglifyJsOptions = (uglifyJsOptions = {}) => {
  356. if (typeof uglifyJsOptions.ecma !== "undefined") {
  357. delete uglifyJsOptions.ecma;
  358. }
  359. if (typeof uglifyJsOptions.module !== "undefined") {
  360. delete uglifyJsOptions.module;
  361. }
  362. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  363. return {
  364. ...uglifyJsOptions,
  365. // warnings: uglifyJsOptions.warnings,
  366. parse: {
  367. ...uglifyJsOptions.parse
  368. },
  369. compress: typeof uglifyJsOptions.compress === "boolean" ? uglifyJsOptions.compress : {
  370. ...uglifyJsOptions.compress
  371. },
  372. mangle:
  373. // eslint-disable-next-line no-eq-null, eqeqeq
  374. uglifyJsOptions.mangle == null ? true : typeof uglifyJsOptions.mangle === "boolean" ? uglifyJsOptions.mangle : {
  375. ...uglifyJsOptions.mangle
  376. },
  377. output: {
  378. beautify: false,
  379. ...uglifyJsOptions.output
  380. },
  381. // Ignoring sourceMap from options
  382. sourceMap: undefined
  383. // toplevel: uglifyJsOptions.toplevel
  384. // nameCache: { ...uglifyJsOptions.toplevel },
  385. // ie8: uglifyJsOptions.ie8,
  386. // keep_fnames: uglifyJsOptions.keep_fnames,
  387. };
  388. };
  389. let minify;
  390. try {
  391. ({
  392. minify
  393. } = require("uglify-js"));
  394. } catch (err) {
  395. return {
  396. errors: [( /** @type {Error} */err)]
  397. };
  398. }
  399. // Copy `uglify-js` options
  400. const uglifyJsOptions = buildUglifyJsOptions(minimizerOptions);
  401. // Let terser generate a SourceMap
  402. if (sourceMap) {
  403. uglifyJsOptions.sourceMap = true;
  404. }
  405. /** @type {ExtractedComments} */
  406. const extractedComments = [];
  407. // @ts-expect-error wrong types in uglify-js
  408. uglifyJsOptions.output.comments = buildComments(uglifyJsOptions, extractedComments);
  409. const [[filename, code]] = Object.entries(input);
  410. const result = await minify({
  411. [filename]: code
  412. }, uglifyJsOptions);
  413. return {
  414. code: result.code,
  415. map: result.map ? JSON.parse(result.map) : undefined,
  416. errors: result.error ? [result.error] : [],
  417. warnings: result.warnings || [],
  418. extractedComments
  419. };
  420. }
  421. /**
  422. * @returns {string | undefined} the minimizer version
  423. */
  424. uglifyJsMinify.getMinimizerVersion = () => {
  425. let packageJson;
  426. try {
  427. packageJson = require("uglify-js/package.json");
  428. } catch (_err) {
  429. // Ignore
  430. }
  431. return packageJson && packageJson.version;
  432. };
  433. /**
  434. * @returns {boolean | undefined} true if worker thread is supported, false otherwise
  435. */
  436. uglifyJsMinify.supportsWorkerThreads = () => true;
  437. /* istanbul ignore next */
  438. /**
  439. * @param {Input} input input
  440. * @param {RawSourceMap=} sourceMap source map
  441. * @param {CustomOptions=} minimizerOptions options
  442. * @returns {Promise<MinimizedResult>} minimized result
  443. */
  444. async function swcMinify(input, sourceMap, minimizerOptions) {
  445. /**
  446. * @param {PredefinedOptions<import("@swc/core").JsMinifyOptions> & import("@swc/core").JsMinifyOptions=} swcOptions swc options
  447. * @returns {import("@swc/core").JsMinifyOptions & { sourceMap: undefined | boolean } & { compress: import("@swc/core").TerserCompressOptions }} built swc options
  448. */
  449. const buildSwcOptions = (swcOptions = {}) => (
  450. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  451. {
  452. ...swcOptions,
  453. compress: typeof swcOptions.compress === "boolean" ? swcOptions.compress ? {} : false : {
  454. ...swcOptions.compress
  455. },
  456. mangle:
  457. // eslint-disable-next-line no-eq-null, eqeqeq
  458. swcOptions.mangle == null ? true : typeof swcOptions.mangle === "boolean" ? swcOptions.mangle : {
  459. ...swcOptions.mangle
  460. },
  461. // ecma: swcOptions.ecma,
  462. // keep_classnames: swcOptions.keep_classnames,
  463. // keep_fnames: swcOptions.keep_fnames,
  464. // module: swcOptions.module,
  465. // safari10: swcOptions.safari10,
  466. // toplevel: swcOptions.toplevel
  467. sourceMap: undefined
  468. });
  469. let swc;
  470. try {
  471. swc = require("@swc/core");
  472. } catch (err) {
  473. return {
  474. errors: [( /** @type {Error} */err)]
  475. };
  476. }
  477. // Copy `swc` options
  478. const swcOptions = buildSwcOptions(minimizerOptions);
  479. // Let `swc` generate a SourceMap
  480. if (sourceMap) {
  481. swcOptions.sourceMap = true;
  482. }
  483. if (swcOptions.compress) {
  484. // More optimizations
  485. if (typeof swcOptions.compress.ecma === "undefined") {
  486. swcOptions.compress.ecma = swcOptions.ecma;
  487. }
  488. // https://github.com/webpack/webpack/issues/16135
  489. if (swcOptions.ecma === 5 && typeof swcOptions.compress.arrows === "undefined") {
  490. swcOptions.compress.arrows = false;
  491. }
  492. }
  493. const [[filename, code]] = Object.entries(input);
  494. const result = await swc.minify(code, swcOptions);
  495. let map;
  496. if (result.map) {
  497. map = JSON.parse(result.map);
  498. // TODO workaround for swc because `filename` is not preset as in `swc` signature as for `terser`
  499. map.sources = [filename];
  500. delete map.sourcesContent;
  501. }
  502. return {
  503. code: result.code,
  504. map
  505. };
  506. }
  507. /**
  508. * @returns {string | undefined} the minimizer version
  509. */
  510. swcMinify.getMinimizerVersion = () => {
  511. let packageJson;
  512. try {
  513. packageJson = require("@swc/core/package.json");
  514. } catch (_err) {
  515. // Ignore
  516. }
  517. return packageJson && packageJson.version;
  518. };
  519. /**
  520. * @returns {boolean | undefined} true if worker thread is supported, false otherwise
  521. */
  522. swcMinify.supportsWorkerThreads = () => false;
  523. /* istanbul ignore next */
  524. /**
  525. * @param {Input} input input
  526. * @param {RawSourceMap=} sourceMap source map
  527. * @param {CustomOptions=} minimizerOptions options
  528. * @returns {Promise<MinimizedResult>} minimized result
  529. */
  530. async function esbuildMinify(input, sourceMap, minimizerOptions) {
  531. /**
  532. * @param {PredefinedOptions<import("esbuild").TransformOptions> & import("esbuild").TransformOptions=} esbuildOptions esbuild options
  533. * @returns {import("esbuild").TransformOptions} built esbuild options
  534. */
  535. const buildEsbuildOptions = (esbuildOptions = {}) => {
  536. delete esbuildOptions.ecma;
  537. if (esbuildOptions.module) {
  538. esbuildOptions.format = "esm";
  539. }
  540. delete esbuildOptions.module;
  541. // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
  542. return {
  543. minify: true,
  544. legalComments: "inline",
  545. ...esbuildOptions,
  546. sourcemap: false
  547. };
  548. };
  549. let esbuild;
  550. try {
  551. esbuild = require("esbuild");
  552. } catch (err) {
  553. return {
  554. errors: [( /** @type {Error} */err)]
  555. };
  556. }
  557. // Copy `esbuild` options
  558. const esbuildOptions = buildEsbuildOptions(minimizerOptions);
  559. // Let `esbuild` generate a SourceMap
  560. if (sourceMap) {
  561. esbuildOptions.sourcemap = true;
  562. esbuildOptions.sourcesContent = false;
  563. }
  564. const [[filename, code]] = Object.entries(input);
  565. esbuildOptions.sourcefile = filename;
  566. const result = await esbuild.transform(code, esbuildOptions);
  567. return {
  568. code: result.code,
  569. map: result.map ? JSON.parse(result.map) : undefined,
  570. warnings: result.warnings.length > 0 ? result.warnings.map(item => {
  571. const plugin = item.pluginName ? `\nPlugin Name: ${item.pluginName}` : "";
  572. 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}` : "";
  573. 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")}` : "";
  574. return `${item.text} [${item.id}]${plugin}${location}${item.detail ? `\nDetails:\n${item.detail}` : ""}${notes}`;
  575. }) : []
  576. };
  577. }
  578. /**
  579. * @returns {string | undefined} the minimizer version
  580. */
  581. esbuildMinify.getMinimizerVersion = () => {
  582. let packageJson;
  583. try {
  584. packageJson = require("esbuild/package.json");
  585. } catch (_err) {
  586. // Ignore
  587. }
  588. return packageJson && packageJson.version;
  589. };
  590. /**
  591. * @returns {boolean | undefined} true if worker thread is supported, false otherwise
  592. */
  593. esbuildMinify.supportsWorkerThreads = () => false;
  594. /**
  595. * @template T
  596. * @typedef {() => T} FunctionReturning
  597. */
  598. /**
  599. * @template T
  600. * @param {FunctionReturning<T>} fn memorized function
  601. * @returns {FunctionReturning<T>} new function
  602. */
  603. function memoize(fn) {
  604. let cache = false;
  605. /** @type {T} */
  606. let result;
  607. return () => {
  608. if (cache) {
  609. return result;
  610. }
  611. result = fn();
  612. cache = true;
  613. // Allow to clean up memory for fn
  614. // and all dependent resources
  615. /** @type {FunctionReturning<T> | undefined} */
  616. fn = undefined;
  617. return /** @type {T} */result;
  618. };
  619. }
  620. module.exports = {
  621. esbuildMinify,
  622. memoize,
  623. swcMinify,
  624. terserMinify,
  625. throttleAll,
  626. uglifyJsMinify
  627. };