source: frontend/node_modules/terser-webpack-plugin/dist/utils.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 50.3 KB
Line 
1"use strict";
2
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/** @typedef {import("./index.js").RawSourceMap} RawSourceMap */
10/** @typedef {import("./index.js").EXPECTED_OBJECT} EXPECTED_OBJECT */
11
12/**
13 * @typedef {string[]} ExtractedComments
14 */
15
16const JS_FILE_RE = /\.[cm]?js(\?.*)?$/i;
17const JSON_FILE_RE = /\.json(\?.*)?$/i;
18const HTML_FILE_RE = /\.html?(\?.*)?$/i;
19const CSS_FILE_RE = /\.css(\?.*)?$/i;
20
21/**
22 * Map a webpack `output.environment` configuration to the highest
23 * ECMAScript version that the target is known to support. Returns `5`
24 * when no ES2015+ features are flagged.
25 * @param {NonNullable<NonNullable<import("webpack").Configuration["output"]>["environment"]>} environment environment
26 * @returns {number} ecma version (5, 2015, 2017 or 2020)
27 */
28function getEcmaVersion(environment) {
29 // ES2020 (11th edition)
30 if (environment.bigIntLiteral || environment.dynamicImport || environment.dynamicImportInWorker || environment.globalThis || environment.optionalChaining) {
31 return 2020;
32 }
33
34 // ES2017 (8th edition)
35 if (environment.asyncFunction) {
36 return 2017;
37 }
38
39 // ES2015 (6th edition)
40 if (environment.arrowFunction || environment.const || environment.destructuring || environment.forOf || environment.methodShorthand || environment.module || environment.templateLiteral) {
41 return 2015;
42 }
43 return 5;
44}
45const notSettled = Symbol("not-settled");
46
47/**
48 * @template T
49 * @typedef {() => Promise<T>} Task
50 */
51
52/**
53 * Run tasks with limited concurrency.
54 * @template T
55 * @param {number} limit Limit of tasks that run at once.
56 * @param {Task<T>[]} tasks List of tasks to run.
57 * @returns {Promise<T[]>} A promise that fulfills to an array of the results
58 */
59function throttleAll(limit, tasks) {
60 return new Promise((resolve, reject) => {
61 const result = Array.from({
62 length: tasks.length
63 }).fill(notSettled);
64 const entries = tasks.entries();
65 const next = () => {
66 const {
67 done,
68 value
69 } = entries.next();
70 if (done) {
71 const isLast = !result.includes(notSettled);
72 if (isLast) resolve(result);
73 return;
74 }
75 const [index, task] = value;
76
77 /**
78 * @param {T} resultValue Result value
79 */
80 const onFulfilled = resultValue => {
81 result[index] = resultValue;
82 next();
83 };
84 task().then(onFulfilled, reject);
85 };
86 for (let i = 0; i < limit; i++) {
87 next();
88 }
89 });
90}
91
92/* istanbul ignore next */
93/**
94 * @param {Input} input input
95 * @param {RawSourceMap=} sourceMap source map
96 * @param {CustomOptions=} minimizerOptions options
97 * @param {ExtractCommentsOptions=} extractComments extract comments option
98 * @returns {Promise<MinimizedResult>} minimized result
99 */
100async function terserMinify(input, sourceMap, minimizerOptions, extractComments) {
101 /**
102 * @param {unknown} value value
103 * @returns {value is EXPECTED_OBJECT} true when value is object or function
104 */
105 const isObject = value => {
106 const type = typeof value;
107
108 // eslint-disable-next-line no-eq-null, eqeqeq
109 return value != null && (type === "object" || type === "function");
110 };
111
112 /**
113 * @param {import("terser").MinifyOptions & { sourceMap: import("terser").SourceMapOptions | undefined } & ({ output: import("terser").FormatOptions & { beautify: boolean } } | { format: import("terser").FormatOptions & { beautify: boolean } })} terserOptions terser options
114 * @param {ExtractedComments} extractedComments extracted comments
115 * @returns {ExtractCommentsFunction} function to extract comments
116 */
117 const buildComments = (terserOptions, extractedComments) => {
118 /** @type {{ [index: string]: ExtractCommentsCondition }} */
119 const condition = {};
120 let comments;
121 if (terserOptions.format) {
122 ({
123 comments
124 } = terserOptions.format);
125 } else if (terserOptions.output) {
126 ({
127 comments
128 } = terserOptions.output);
129 }
130 condition.preserve = typeof comments !== "undefined" ? comments : false;
131 if (typeof extractComments === "boolean" && extractComments) {
132 condition.extract = "some";
133 } else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
134 condition.extract = extractComments;
135 } else if (typeof extractComments === "function") {
136 condition.extract = extractComments;
137 } else if (extractComments && isObject(extractComments)) {
138 condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
139 } else {
140 // No extract
141 // Preserve using "commentsOpts" or "some"
142 condition.preserve = typeof comments !== "undefined" ? comments : "some";
143 condition.extract = false;
144 }
145
146 // Ensure that both conditions are functions
147 for (const key of ["preserve", "extract"]) {
148 /** @type {undefined | string} */
149 let regexStr;
150 /** @type {undefined | RegExp} */
151 let regex;
152 switch (typeof condition[key]) {
153 case "boolean":
154 condition[key] = condition[key] ? () => true : () => false;
155 break;
156 case "function":
157 break;
158 case "string":
159 if (condition[key] === "all") {
160 condition[key] = () => true;
161 break;
162 }
163 if (condition[key] === "some") {
164 condition[key] = /** @type {ExtractCommentsFunction} */
165 (astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
166 break;
167 }
168 regexStr = /** @type {string} */condition[key];
169 condition[key] = /** @type {ExtractCommentsFunction} */
170 (astNode, comment) => new RegExp(/** @type {string} */regexStr).test(comment.value);
171 break;
172 default:
173 regex = /** @type {RegExp} */condition[key];
174 condition[key] = /** @type {ExtractCommentsFunction} */
175 (astNode, comment) => /** @type {RegExp} */regex.test(comment.value);
176 }
177 }
178
179 // Redefine the comments function to extract and preserve
180 // comments according to the two conditions
181 return (astNode, comment) => {
182 if (/** @type {{ extract: ExtractCommentsFunction }} */
183 condition.extract(astNode, comment)) {
184 const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`;
185
186 // Don't include duplicate comments
187 if (!extractedComments.includes(commentText)) {
188 extractedComments.push(commentText);
189 }
190 }
191 return /** @type {{ preserve: ExtractCommentsFunction }} */condition.preserve(astNode, comment);
192 };
193 };
194
195 /**
196 * @param {import("terser").MinifyOptions=} terserOptions terser options
197 * @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
198 */
199 const buildTerserOptions = (terserOptions = {}) => (
200 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
201 {
202 ...terserOptions,
203 compress: typeof terserOptions.compress === "boolean" ? terserOptions.compress ? {} : false : {
204 ...terserOptions.compress
205 },
206 // ecma: terserOptions.ecma,
207 // ie8: terserOptions.ie8,
208 // keep_classnames: terserOptions.keep_classnames,
209 // keep_fnames: terserOptions.keep_fnames,
210 mangle:
211 // eslint-disable-next-line no-eq-null, eqeqeq
212 terserOptions.mangle == null ? true : typeof terserOptions.mangle === "boolean" ? terserOptions.mangle : {
213 ...terserOptions.mangle
214 },
215 // module: terserOptions.module,
216 // nameCache: { ...terserOptions.toplevel },
217 // the `output` option is deprecated
218 ...(terserOptions.format ? {
219 format: {
220 beautify: false,
221 ...terserOptions.format
222 }
223 } : {
224 output: {
225 beautify: false,
226 ...terserOptions.output
227 }
228 }),
229 parse: {
230 ...terserOptions.parse
231 },
232 // safari10: terserOptions.safari10,
233 // Ignoring sourceMap from options
234 sourceMap: undefined
235 // toplevel: terserOptions.toplevel
236 });
237 let minify;
238 try {
239 ({
240 minify
241 } = require("terser"));
242 } catch (err) {
243 return {
244 errors: [(/** @type {Error} */err)]
245 };
246 }
247
248 // Copy `terser` options
249 const terserOptions = buildTerserOptions(minimizerOptions);
250
251 // Let terser generate a SourceMap. The dispatcher in `minify.js`
252 // chains the previous step's map onto this one.
253 if (sourceMap) {
254 terserOptions.sourceMap = {
255 asObject: true
256 };
257 }
258
259 /** @type {ExtractedComments} */
260 const extractedComments = [];
261 if (terserOptions.output) {
262 terserOptions.output.comments = buildComments(terserOptions, extractedComments);
263 } else if (terserOptions.format) {
264 terserOptions.format.comments = buildComments(terserOptions, extractedComments);
265 }
266 if (terserOptions.compress) {
267 // More optimizations
268 if (typeof terserOptions.compress.ecma === "undefined") {
269 terserOptions.compress.ecma = terserOptions.ecma;
270 }
271
272 // https://github.com/webpack/webpack/issues/16135
273 if (terserOptions.ecma === 5 && typeof terserOptions.compress.arrows === "undefined") {
274 terserOptions.compress.arrows = false;
275 }
276 }
277 const [[filename, code]] = Object.entries(input);
278 const result = await minify({
279 [filename]: code
280 }, terserOptions);
281 return {
282 code: (/** @type {string} * */result.code),
283 map: result.map ? (/** @type {RawSourceMap} * */result.map) : undefined,
284 extractedComments
285 };
286}
287
288/**
289 * @returns {string | undefined} the minimizer version
290 */
291terserMinify.getMinimizerVersion = () => {
292 let packageJson;
293 try {
294 packageJson = require("terser/package.json");
295 } catch (_err) {
296 // Ignore
297 }
298 return packageJson && packageJson.version;
299};
300
301/**
302 * @returns {boolean | undefined} true if worker thread is supported, false otherwise
303 */
304terserMinify.supportsWorkerThreads = () => true;
305
306/**
307 * @param {string} name asset name
308 * @returns {boolean} true if `name` looks like a JavaScript file
309 */
310terserMinify.filter = name => JS_FILE_RE.test(name);
311
312/* istanbul ignore next */
313/**
314 * @param {Input} input input
315 * @param {RawSourceMap=} sourceMap source map
316 * @param {CustomOptions=} minimizerOptions options
317 * @param {ExtractCommentsOptions=} extractComments extract comments option
318 * @returns {Promise<MinimizedResult>} minimized result
319 */
320async function uglifyJsMinify(input, sourceMap, minimizerOptions, extractComments) {
321 /**
322 * @param {unknown} value value
323 * @returns {boolean} true when value is object or function
324 */
325 const isObject = value => {
326 const type = typeof value;
327
328 // eslint-disable-next-line no-eq-null, eqeqeq
329 return value != null && (type === "object" || type === "function");
330 };
331
332 /**
333 * @param {import("uglify-js").MinifyOptions & { sourceMap: boolean | import("uglify-js").SourceMapOptions | undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean } }} uglifyJsOptions uglify-js options
334 * @param {ExtractedComments} extractedComments extracted comments
335 * @returns {ExtractCommentsFunction} extract comments function
336 */
337 const buildComments = (uglifyJsOptions, extractedComments) => {
338 /** @type {{ [index: string]: ExtractCommentsCondition }} */
339 const condition = {};
340 const {
341 comments
342 } = uglifyJsOptions.output;
343 condition.preserve = typeof comments !== "undefined" ? comments : false;
344 if (typeof extractComments === "boolean" && extractComments) {
345 condition.extract = "some";
346 } else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
347 condition.extract = extractComments;
348 } else if (typeof extractComments === "function") {
349 condition.extract = extractComments;
350 } else if (extractComments && isObject(extractComments)) {
351 condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
352 } else {
353 // No extract
354 // Preserve using "commentsOpts" or "some"
355 condition.preserve = typeof comments !== "undefined" ? comments : "some";
356 condition.extract = false;
357 }
358
359 // Ensure that both conditions are functions
360 for (const key of ["preserve", "extract"]) {
361 /** @type {undefined | string} */
362 let regexStr;
363 /** @type {undefined | RegExp} */
364 let regex;
365 switch (typeof condition[key]) {
366 case "boolean":
367 condition[key] = condition[key] ? () => true : () => false;
368 break;
369 case "function":
370 break;
371 case "string":
372 if (condition[key] === "all") {
373 condition[key] = () => true;
374 break;
375 }
376 if (condition[key] === "some") {
377 condition[key] = /** @type {ExtractCommentsFunction} */
378 (astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
379 break;
380 }
381 regexStr = /** @type {string} */condition[key];
382 condition[key] = /** @type {ExtractCommentsFunction} */
383 (astNode, comment) => new RegExp(/** @type {string} */regexStr).test(comment.value);
384 break;
385 default:
386 regex = /** @type {RegExp} */condition[key];
387 condition[key] = /** @type {ExtractCommentsFunction} */
388 (astNode, comment) => /** @type {RegExp} */regex.test(comment.value);
389 }
390 }
391
392 // Redefine the comments function to extract and preserve
393 // comments according to the two conditions
394 return (astNode, comment) => {
395 if (/** @type {{ extract: ExtractCommentsFunction }} */
396 condition.extract(astNode, comment)) {
397 const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`;
398
399 // Don't include duplicate comments
400 if (!extractedComments.includes(commentText)) {
401 extractedComments.push(commentText);
402 }
403 }
404 return /** @type {{ preserve: ExtractCommentsFunction }} */condition.preserve(astNode, comment);
405 };
406 };
407
408 /**
409 * @param {import("uglify-js").MinifyOptions & { ecma?: number | string }=} uglifyJsOptions uglify-js options
410 * @returns {import("uglify-js").MinifyOptions & { sourceMap: boolean | import("uglify-js").SourceMapOptions | undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean } }} uglify-js options
411 */
412 const buildUglifyJsOptions = (uglifyJsOptions = {}) => {
413 if (typeof uglifyJsOptions.ecma !== "undefined") {
414 delete uglifyJsOptions.ecma;
415 }
416 if (typeof uglifyJsOptions.module !== "undefined") {
417 delete uglifyJsOptions.module;
418 }
419
420 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
421 return {
422 ...uglifyJsOptions,
423 // warnings: uglifyJsOptions.warnings,
424 parse: {
425 ...uglifyJsOptions.parse
426 },
427 compress: typeof uglifyJsOptions.compress === "boolean" ? uglifyJsOptions.compress : {
428 ...uglifyJsOptions.compress
429 },
430 mangle:
431 // eslint-disable-next-line no-eq-null, eqeqeq
432 uglifyJsOptions.mangle == null ? true : typeof uglifyJsOptions.mangle === "boolean" ? uglifyJsOptions.mangle : {
433 ...uglifyJsOptions.mangle
434 },
435 output: {
436 beautify: false,
437 ...uglifyJsOptions.output
438 },
439 // Ignoring sourceMap from options
440
441 sourceMap: undefined
442 // toplevel: uglifyJsOptions.toplevel
443 // nameCache: { ...uglifyJsOptions.toplevel },
444 // ie8: uglifyJsOptions.ie8,
445 // keep_fnames: uglifyJsOptions.keep_fnames,
446 };
447 };
448 let minify;
449 try {
450 ({
451 minify
452 } = require("uglify-js"));
453 } catch (err) {
454 return {
455 errors: [(/** @type {Error} */err)]
456 };
457 }
458
459 // Copy `uglify-js` options
460 const uglifyJsOptions = buildUglifyJsOptions(minimizerOptions);
461
462 // Let `uglify-js` generate a SourceMap. The dispatcher in `minify.js`
463 // chains the previous step's map onto this one.
464 if (sourceMap) {
465 uglifyJsOptions.sourceMap = true;
466 }
467
468 /** @type {ExtractedComments} */
469 const extractedComments = [];
470
471 // @ts-expect-error wrong types in uglify-js
472 uglifyJsOptions.output.comments = buildComments(uglifyJsOptions, extractedComments);
473 const [[filename, code]] = Object.entries(input);
474 const result = await minify({
475 [filename]: code
476 }, uglifyJsOptions);
477 return {
478 code: result.code,
479 map: result.map ? JSON.parse(result.map) : undefined,
480 errors: result.error ? [result.error] : [],
481 warnings: result.warnings || [],
482 extractedComments
483 };
484}
485
486/**
487 * @returns {string | undefined} the minimizer version
488 */
489uglifyJsMinify.getMinimizerVersion = () => {
490 let packageJson;
491 try {
492 packageJson = require("uglify-js/package.json");
493 } catch (_err) {
494 // Ignore
495 }
496 return packageJson && packageJson.version;
497};
498
499/**
500 * @returns {boolean | undefined} true if worker thread is supported, false otherwise
501 */
502uglifyJsMinify.supportsWorkerThreads = () => true;
503
504/**
505 * @param {string} name asset name
506 * @returns {boolean} true if `name` looks like a JavaScript file
507 */
508uglifyJsMinify.filter = name => JS_FILE_RE.test(name);
509
510/* istanbul ignore next */
511/**
512 * @param {Input} input input
513 * @param {RawSourceMap=} sourceMap source map
514 * @param {CustomOptions=} minimizerOptions options
515 * @param {ExtractCommentsOptions=} extractComments extract comments option
516 * @returns {Promise<MinimizedResult>} minimized result
517 */
518async function swcMinify(input, sourceMap, minimizerOptions, extractComments) {
519 /**
520 * @param {unknown} value value
521 * @returns {boolean} true when value is object or function
522 */
523 const isObject = value => {
524 const type = typeof value;
525
526 // eslint-disable-next-line no-eq-null, eqeqeq
527 return value != null && (type === "object" || type === "function");
528 };
529
530 /**
531 * @param {unknown} extractCommentsOptions extract comments option
532 * @returns {Error} error for unsupported extract comments option
533 */
534 const createExtractCommentsError = extractCommentsOptions => new Error(`The 'extractComments' option for 'swcMinify' only supports booleans, "some", "all", string patterns, RegExp values without flags, or object conditions that resolve to those forms. Received: ${extractCommentsOptions instanceof RegExp ? extractCommentsOptions.toString() : typeof extractCommentsOptions}.`);
535
536 /**
537 * @param {unknown} extractCommentsOptions extract comments option
538 * @returns {{ extractComments: false | true | "some" | "all" | { regex: string }, useDefaultPreserveComments: boolean }} normalized swc extract comments options
539 */
540 const normalizeExtractComments = extractCommentsOptions => {
541 if (typeof extractCommentsOptions === "boolean") {
542 return {
543 extractComments: extractCommentsOptions,
544 useDefaultPreserveComments: !extractCommentsOptions
545 };
546 }
547 if (typeof extractCommentsOptions === "string") {
548 return {
549 extractComments: extractCommentsOptions === "some" || extractCommentsOptions === "all" ? extractCommentsOptions : {
550 regex: extractCommentsOptions
551 },
552 useDefaultPreserveComments: false
553 };
554 }
555 if (extractCommentsOptions instanceof RegExp) {
556 if (extractCommentsOptions.flags) {
557 throw createExtractCommentsError(extractCommentsOptions);
558 }
559 return {
560 extractComments: {
561 regex: extractCommentsOptions.source
562 },
563 useDefaultPreserveComments: false
564 };
565 }
566 if (typeof extractCommentsOptions === "function") {
567 throw createExtractCommentsError(extractCommentsOptions);
568 }
569 if (extractCommentsOptions && isObject(extractCommentsOptions)) {
570 const {
571 condition = "some"
572 } = /** @type {{ condition?: unknown }} */
573 extractCommentsOptions;
574 if (typeof condition === "boolean") {
575 return {
576 extractComments: condition ? "some" : false,
577 useDefaultPreserveComments: false
578 };
579 }
580 if (typeof condition === "string") {
581 return {
582 extractComments: condition === "some" || condition === "all" ? condition : {
583 regex: condition
584 },
585 useDefaultPreserveComments: false
586 };
587 }
588 if (condition instanceof RegExp) {
589 if (condition.flags) {
590 throw createExtractCommentsError(condition);
591 }
592 return {
593 extractComments: {
594 regex: condition.source
595 },
596 useDefaultPreserveComments: false
597 };
598 }
599 throw createExtractCommentsError(condition);
600 }
601 return {
602 extractComments: false,
603 useDefaultPreserveComments: false
604 };
605 };
606
607 /**
608 * @param {import("@swc/core").JsMinifyOptions=} swcOptions swc options
609 * @returns {import("@swc/core").JsMinifyOptions & { extractComments?: false | true | "some" | "all" | { regex: string } } & { sourceMap: undefined | boolean } & { compress: import("@swc/core").TerserCompressOptions }} built swc options
610 */
611 const buildSwcOptions = (swcOptions = {}) => (
612 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
613 {
614 ...swcOptions,
615 compress: typeof swcOptions.compress === "boolean" ? swcOptions.compress ? {} : false : {
616 ...swcOptions.compress
617 },
618 mangle:
619 // eslint-disable-next-line no-eq-null, eqeqeq
620 swcOptions.mangle == null ? true : typeof swcOptions.mangle === "boolean" ? swcOptions.mangle : {
621 ...swcOptions.mangle
622 },
623 format: {
624 ...swcOptions.format
625 },
626 // ecma: swcOptions.ecma,
627 // keep_classnames: swcOptions.keep_classnames,
628 // keep_fnames: swcOptions.keep_fnames,
629 // module: swcOptions.module,
630 // safari10: swcOptions.safari10,
631 // toplevel: swcOptions.toplevel
632
633 sourceMap: undefined
634 });
635 let swc;
636 try {
637 swc = require("@swc/core");
638 } catch (err) {
639 return {
640 errors: [(/** @type {Error} */err)]
641 };
642 }
643
644 // Copy `swc` options
645 const swcOptions = buildSwcOptions(minimizerOptions);
646 const normalizedExtractComments = normalizeExtractComments(extractComments);
647 if (!swcOptions.format) {
648 swcOptions.format = {};
649 }
650
651 // Let `swc` generate a SourceMap.
652 if (sourceMap) {
653 swcOptions.sourceMap = true;
654 }
655 if (normalizedExtractComments.useDefaultPreserveComments && typeof swcOptions.format.comments === "undefined") {
656 swcOptions.format.comments = "some";
657 }
658 if (normalizedExtractComments.extractComments !== false) {
659 /** @type {import("@swc/core").JsMinifyOptions & { extractComments?: false | true | "some" | "all" | { regex: string } }} */
660 swcOptions.extractComments = normalizedExtractComments.extractComments;
661 }
662 if (swcOptions.compress) {
663 // More optimizations
664 if (typeof swcOptions.compress.ecma === "undefined") {
665 swcOptions.compress.ecma = swcOptions.ecma;
666 }
667
668 // https://github.com/webpack/webpack/issues/16135
669 if (swcOptions.ecma === 5 && typeof swcOptions.compress.arrows === "undefined") {
670 swcOptions.compress.arrows = false;
671 }
672 }
673 const [[filename, code]] = Object.entries(input);
674 const result = /** @type {import("@swc/core").Output & { extractedComments?: string[] }} */
675 await swc.minify(code, swcOptions);
676 let map;
677 if (result.map) {
678 map = JSON.parse(result.map);
679
680 // TODO workaround for swc because `filename` is not preset as in `swc` signature as for `terser`
681 map.sources = [filename];
682 delete map.sourcesContent;
683 }
684 return {
685 code: result.code,
686 map,
687 extractedComments: result.extractedComments || []
688 };
689}
690
691/**
692 * @returns {string | undefined} the minimizer version
693 */
694swcMinify.getMinimizerVersion = () => {
695 let packageJson;
696 try {
697 packageJson = require("@swc/core/package.json");
698 } catch (_err) {
699 // Ignore
700 }
701 return packageJson && packageJson.version;
702};
703
704/**
705 * @returns {boolean | undefined} true if worker thread is supported, false otherwise
706 */
707swcMinify.supportsWorkerThreads = () => false;
708
709/**
710 * @param {string} name asset name
711 * @returns {boolean} true if `name` looks like a JavaScript file
712 */
713swcMinify.filter = name => JS_FILE_RE.test(name);
714
715/* istanbul ignore next */
716/**
717 * @param {Input} input input
718 * @param {RawSourceMap=} sourceMap source map
719 * @param {CustomOptions=} minimizerOptions options
720 * @returns {Promise<MinimizedResult>} minimized result
721 */
722async function esbuildMinify(input, sourceMap, minimizerOptions) {
723 /**
724 * @param {import("esbuild").TransformOptions & { ecma?: string | number, module?: boolean }=} esbuildOptions esbuild options
725 * @returns {import("esbuild").TransformOptions} built esbuild options
726 */
727 const buildEsbuildOptions = (esbuildOptions = {}) => {
728 delete esbuildOptions.ecma;
729 if (esbuildOptions.module) {
730 esbuildOptions.format = "esm";
731 }
732 delete esbuildOptions.module;
733
734 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
735 return {
736 minify: true,
737 legalComments: "inline",
738 ...esbuildOptions,
739 sourcemap: false
740 };
741 };
742 let esbuild;
743 try {
744 esbuild = require("esbuild");
745 } catch (err) {
746 return {
747 errors: [(/** @type {Error} */err)]
748 };
749 }
750
751 // Copy `esbuild` options
752 const esbuildOptions = buildEsbuildOptions(minimizerOptions);
753
754 // Let `esbuild` generate a SourceMap
755 if (sourceMap) {
756 esbuildOptions.sourcemap = true;
757 esbuildOptions.sourcesContent = false;
758 }
759 const [[filename, code]] = Object.entries(input);
760 esbuildOptions.sourcefile = filename;
761 const result = await esbuild.transform(code, esbuildOptions);
762 return {
763 code: result.code,
764 map: result.map ? JSON.parse(result.map) : undefined,
765 warnings: result.warnings.length > 0 ? result.warnings.map(item => {
766 const plugin = item.pluginName ? `\nPlugin Name: ${item.pluginName}` : "";
767 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}` : "";
768 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")}` : "";
769 return `${item.text} [${item.id}]${plugin}${location}${item.detail ? `\nDetails:\n${item.detail}` : ""}${notes}`;
770 }) : []
771 };
772}
773
774/**
775 * @returns {string | undefined} the minimizer version
776 */
777esbuildMinify.getMinimizerVersion = () => {
778 let packageJson;
779 try {
780 packageJson = require("esbuild/package.json");
781 } catch (_err) {
782 // Ignore
783 }
784 return packageJson && packageJson.version;
785};
786
787/**
788 * @returns {boolean | undefined} true if worker thread is supported, false otherwise
789 */
790esbuildMinify.supportsWorkerThreads = () => false;
791
792/**
793 * @param {string} name asset name
794 * @returns {boolean} true if `name` looks like a JavaScript file
795 */
796esbuildMinify.filter = name => JS_FILE_RE.test(name);
797
798/* istanbul ignore next */
799/**
800 * @param {Input} input input
801 * @param {RawSourceMap=} sourceMap source map
802 * @param {CustomOptions=} minimizerOptions options
803 * @returns {Promise<MinimizedResult>} minimized result
804 */
805async function jsonMinify(input, sourceMap, minimizerOptions) {
806 const options = /** @type {{ replacer?: Parameters<typeof JSON.stringify>[1], space?: Parameters<typeof JSON.stringify>[2] }} */
807 minimizerOptions;
808 const [[, code]] = Object.entries(input);
809 const result = JSON.stringify(JSON.parse(code), options.replacer, options.space);
810 return {
811 code: result
812 };
813}
814jsonMinify.getMinimizerVersion = () => "1.0.0";
815jsonMinify.supportsWorker = () => false;
816jsonMinify.supportsWorkerThreads = () => false;
817
818/**
819 * @param {string} name asset name
820 * @returns {boolean} true if `name` looks like a JSON file
821 */
822jsonMinify.filter = name => JSON_FILE_RE.test(name);
823
824/* istanbul ignore next */
825/**
826 * Minify HTML using `html-minifier-terser`.
827 * @param {Input} input input
828 * @param {RawSourceMap=} sourceMap source map (ignored for HTML)
829 * @param {CustomOptions=} minimizerOptions options
830 * @returns {Promise<MinimizedResult>} minimized result
831 */
832async function htmlMinifierTerser(input, sourceMap, minimizerOptions) {
833 let htmlMinifier;
834 try {
835 htmlMinifier = require("html-minifier-terser");
836 } catch (err) {
837 return {
838 errors: [(/** @type {Error} */err)]
839 };
840 }
841 const [[, code]] = Object.entries(input);
842 /** @type {import("html-minifier-terser").Options} */
843 const defaultMinimizerOptions = {
844 caseSensitive: true,
845 // `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML
846 collapseWhitespace: true,
847 conservativeCollapse: true,
848 keepClosingSlash: true,
849 // We need ability to use cssnano, or setup own function without extra dependencies
850 minifyCSS: true,
851 minifyJS: true,
852 // `minifyURLs` is unsafe, because we can't guarantee what the base URL is
853 // `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this
854 removeComments: true,
855 // `removeEmptyAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
856 // `removeRedundantAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
857 removeScriptTypeAttributes: true,
858 removeStyleLinkTypeAttributes: true
859 // `useShortDoctype` is not safe for XHTML
860 };
861 const result = await htmlMinifier.minify(code, {
862 ...defaultMinimizerOptions,
863 ...(/** @type {import("html-minifier-terser").Options} */minimizerOptions)
864 });
865 return {
866 code: result
867 };
868}
869
870/**
871 * @returns {string | undefined} the minimizer version
872 */
873htmlMinifierTerser.getMinimizerVersion = () => {
874 let packageJson;
875 try {
876 packageJson = require("html-minifier-terser/package.json");
877 } catch (_err) {
878 // Ignore
879 }
880 return packageJson && packageJson.version;
881};
882
883/**
884 * @returns {boolean | undefined} true if worker threads are supported
885 */
886htmlMinifierTerser.supportsWorkerThreads = () => true;
887
888/**
889 * @param {string} name asset name
890 * @returns {boolean} true if `name` looks like an HTML file
891 */
892htmlMinifierTerser.filter = name => HTML_FILE_RE.test(name);
893
894/* istanbul ignore next */
895/**
896 * Minify HTML using `@minify-html/node`.
897 * @param {Input} input input
898 * @param {RawSourceMap=} sourceMap source map (ignored for HTML)
899 * @param {CustomOptions=} minimizerOptions options
900 * @returns {Promise<MinimizedResult>} minimized result
901 */
902async function minifyHtmlNode(input, sourceMap, minimizerOptions) {
903 let minifyHtmlPkg;
904 try {
905 minifyHtmlPkg = require("@minify-html/node");
906 } catch (err) {
907 return {
908 errors: [(/** @type {Error} */err)]
909 };
910 }
911 const [[, code]] = Object.entries(input);
912 const options = /** @type {Parameters<import("@minify-html/node").minify>[1]} */{
913 ...minimizerOptions
914 };
915 const result = await minifyHtmlPkg.minify(Buffer.from(code), options);
916 return {
917 code: result.toString()
918 };
919}
920
921/**
922 * @returns {string | undefined} the minimizer version
923 */
924minifyHtmlNode.getMinimizerVersion = () => {
925 let packageJson;
926 try {
927 packageJson = require("@minify-html/node/package.json");
928 } catch (_err) {
929 // Ignore
930 }
931 return packageJson && packageJson.version;
932};
933
934/**
935 * @returns {boolean | undefined} false because `@minify-html/node` is a native binding
936 */
937minifyHtmlNode.supportsWorkerThreads = () => false;
938
939/**
940 * @param {string} name asset name
941 * @returns {boolean} true if `name` looks like an HTML file
942 */
943minifyHtmlNode.filter = name => HTML_FILE_RE.test(name);
944
945/* istanbul ignore next */
946/**
947 * Map an `@swc/html` diagnostic to a regular `Error`.
948 * @param {EXPECTED_OBJECT} diagnostic diagnostic from `@swc/html`
949 * @returns {Error} error preserving `span` and `level` from the diagnostic
950 */
951function swcHtmlDiagnosticToError(diagnostic) {
952 const typed = /** @type {{ message: string, span?: unknown, level?: unknown }} */
953 diagnostic;
954 /** @type {Error & { span?: unknown, level?: unknown }} */
955 const error = new Error(typed.message);
956 error.span = typed.span;
957 error.level = typed.level;
958 return error;
959}
960
961/* istanbul ignore next */
962/**
963 * Minify a complete HTML document using `@swc/html`.
964 * @param {Input} input input
965 * @param {RawSourceMap=} sourceMap source map (ignored for HTML)
966 * @param {CustomOptions=} minimizerOptions options
967 * @returns {Promise<MinimizedResult>} minimized result
968 */
969async function swcMinifyHtml(input, sourceMap, minimizerOptions) {
970 let swcMinifier;
971 try {
972 swcMinifier = require("@swc/html");
973 } catch (err) {
974 return {
975 errors: [(/** @type {Error} */err)]
976 };
977 }
978 const [[, code]] = Object.entries(input);
979 const options = /** @type {import("@swc/html").Options} */{
980 ...minimizerOptions
981 };
982 const result = await swcMinifier.minify(Buffer.from(code), options);
983 return {
984 code: result.code,
985 errors: result.errors ? result.errors.map(swcHtmlDiagnosticToError) : undefined
986 };
987}
988
989/**
990 * @returns {string | undefined} the minimizer version
991 */
992swcMinifyHtml.getMinimizerVersion = () => {
993 let packageJson;
994 try {
995 packageJson = require("@swc/html/package.json");
996 } catch (_err) {
997 // Ignore
998 }
999 return packageJson && packageJson.version;
1000};
1001
1002/**
1003 * @returns {boolean | undefined} false because `@swc/html` is a native binding
1004 */
1005swcMinifyHtml.supportsWorkerThreads = () => false;
1006
1007/**
1008 * @param {string} name asset name
1009 * @returns {boolean} true if `name` looks like an HTML file
1010 */
1011swcMinifyHtml.filter = name => HTML_FILE_RE.test(name);
1012
1013/* istanbul ignore next */
1014/**
1015 * Minify an HTML fragment using `@swc/html`.
1016 *
1017 * Use this for partial HTML (e.g. inside `<template></template>` tags or
1018 * HTML strings that are inserted into another document).
1019 * @param {Input} input input
1020 * @param {RawSourceMap=} sourceMap source map (ignored for HTML)
1021 * @param {CustomOptions=} minimizerOptions options
1022 * @returns {Promise<MinimizedResult>} minimized result
1023 */
1024async function swcMinifyHtmlFragment(input, sourceMap, minimizerOptions) {
1025 let swcMinifier;
1026 try {
1027 swcMinifier = require("@swc/html");
1028 } catch (err) {
1029 return {
1030 errors: [(/** @type {Error} */err)]
1031 };
1032 }
1033 const [[, code]] = Object.entries(input);
1034 const options = /** @type {import("@swc/html").FragmentOptions} */{
1035 ...minimizerOptions
1036 };
1037 const result = await swcMinifier.minifyFragment(Buffer.from(code), options);
1038 return {
1039 code: result.code,
1040 errors: result.errors ? result.errors.map(swcHtmlDiagnosticToError) : undefined
1041 };
1042}
1043
1044/**
1045 * @returns {string | undefined} the minimizer version
1046 */
1047swcMinifyHtmlFragment.getMinimizerVersion = () => {
1048 let packageJson;
1049 try {
1050 packageJson = require("@swc/html/package.json");
1051 } catch (_err) {
1052 // Ignore
1053 }
1054 return packageJson && packageJson.version;
1055};
1056
1057/**
1058 * @returns {boolean | undefined} false because `@swc/html` is a native binding
1059 */
1060swcMinifyHtmlFragment.supportsWorkerThreads = () => false;
1061
1062/**
1063 * @param {string} name asset name
1064 * @returns {boolean} true if `name` looks like an HTML file
1065 */
1066swcMinifyHtmlFragment.filter = name => HTML_FILE_RE.test(name);
1067
1068/* istanbul ignore next */
1069/**
1070 * Minify CSS using `cssnano` (via `postcss`).
1071 * @param {Input} input input
1072 * @param {RawSourceMap=} sourceMap source map
1073 * @param {CustomOptions=} minimizerOptions options
1074 * @returns {Promise<MinimizedResult>} minimized result
1075 */
1076async function cssnanoMinify(input, sourceMap, minimizerOptions = {
1077 preset: "default"
1078}) {
1079 /**
1080 * @template T
1081 * @param {string} mod module to load
1082 * @returns {Promise<T>} loaded module
1083 */
1084 const load = async mod => {
1085 let exports;
1086 try {
1087 exports = require(mod);
1088 return exports;
1089 } catch (err) {
1090 let importESM;
1091 try {
1092 // eslint-disable-next-line no-new-func
1093 importESM = new Function("id", "return import(id);");
1094 } catch (_err) {
1095 importESM = null;
1096 }
1097 if (/** @type {Error & { code: string }} */
1098 err.code === "ERR_REQUIRE_ESM" && importESM) {
1099 exports = await importESM(mod);
1100 return exports.default;
1101 }
1102 throw err;
1103 }
1104 };
1105 let postcss;
1106 let cssnano;
1107 try {
1108 postcss = require("postcss");
1109 cssnano = require("cssnano");
1110 } catch (err) {
1111 return {
1112 errors: [(/** @type {Error} */err)]
1113 };
1114 }
1115 const [[name, code]] = Object.entries(input);
1116 /** @type {import("postcss").ProcessOptions} */
1117 const postcssOptions = {
1118 from: name,
1119 ... /** @type {{ processorOptions?: import("postcss").ProcessOptions }} */minimizerOptions.processorOptions
1120 };
1121 if (typeof postcssOptions.parser === "string") {
1122 try {
1123 postcssOptions.parser = await load(postcssOptions.parser);
1124 } catch (error) {
1125 throw new Error(`Loading PostCSS "${postcssOptions.parser}" parser failed: ${ /** @type {Error} */error.message}\n\n(@${name})`, {
1126 cause: error
1127 });
1128 }
1129 }
1130 if (typeof postcssOptions.stringifier === "string") {
1131 try {
1132 postcssOptions.stringifier = await load(postcssOptions.stringifier);
1133 } catch (error) {
1134 throw new Error(`Loading PostCSS "${postcssOptions.stringifier}" stringifier failed: ${ /** @type {Error} */error.message}\n\n(@${name})`, {
1135 cause: error
1136 });
1137 }
1138 }
1139 if (typeof postcssOptions.syntax === "string") {
1140 try {
1141 postcssOptions.syntax = await load(postcssOptions.syntax);
1142 } catch (error) {
1143 throw new Error(`Loading PostCSS "${postcssOptions.syntax}" syntax failed: ${ /** @type {Error} */error.message}\n\n(@${name})`, {
1144 cause: error
1145 });
1146 }
1147 }
1148 if (sourceMap) {
1149 postcssOptions.map = {
1150 annotation: false
1151 };
1152 }
1153 const result = await postcss.default([cssnano(minimizerOptions)]).process(code, postcssOptions);
1154 return {
1155 code: result.css,
1156 map: result.map ? (/** @type {RawSourceMap} */
1157 /** @type {unknown} */result.map.toJSON()) : undefined,
1158 warnings: result.warnings().map(String)
1159 };
1160}
1161
1162/**
1163 * @returns {string | undefined} the minimizer version
1164 */
1165cssnanoMinify.getMinimizerVersion = () => {
1166 let packageJson;
1167 try {
1168 packageJson = require("cssnano/package.json");
1169 } catch (_err) {
1170 // Ignore
1171 }
1172 return packageJson && packageJson.version;
1173};
1174
1175/**
1176 * @returns {boolean | undefined} true if worker threads are supported
1177 */
1178cssnanoMinify.supportsWorkerThreads = () => true;
1179
1180/**
1181 * @param {string} name asset name
1182 * @returns {boolean} true if `name` looks like a CSS file
1183 */
1184cssnanoMinify.filter = name => CSS_FILE_RE.test(name);
1185
1186/* istanbul ignore next */
1187/**
1188 * Minify CSS using `csso`.
1189 * @param {Input} input input
1190 * @param {RawSourceMap=} sourceMap source map
1191 * @param {CustomOptions=} minimizerOptions options
1192 * @returns {Promise<MinimizedResult>} minimized result
1193 */
1194async function cssoMinify(input, sourceMap, minimizerOptions) {
1195 let csso;
1196 try {
1197 csso = require("csso");
1198 } catch (err) {
1199 return {
1200 errors: [(/** @type {Error} */err)]
1201 };
1202 }
1203 const [[filename, code]] = Object.entries(input);
1204 const result = csso.minify(code, {
1205 filename,
1206 sourceMap: Boolean(sourceMap),
1207 ...minimizerOptions
1208 });
1209 return {
1210 code: result.css,
1211 map: result.map ? (/** @type {RawSourceMap} */
1212 /** @type {{ toJSON(): RawSourceMap }} */result.map.toJSON()) : undefined
1213 };
1214}
1215
1216/**
1217 * @returns {string | undefined} the minimizer version
1218 */
1219cssoMinify.getMinimizerVersion = () => {
1220 let packageJson;
1221 try {
1222 packageJson = require("csso/package.json");
1223 } catch (_err) {
1224 // Ignore
1225 }
1226 return packageJson && packageJson.version;
1227};
1228
1229/**
1230 * @returns {boolean | undefined} true if worker threads are supported
1231 */
1232cssoMinify.supportsWorkerThreads = () => true;
1233
1234/**
1235 * @param {string} name asset name
1236 * @returns {boolean} true if `name` looks like a CSS file
1237 */
1238cssoMinify.filter = name => CSS_FILE_RE.test(name);
1239
1240/* istanbul ignore next */
1241/**
1242 * Minify CSS using `clean-css`.
1243 * @param {Input} input input
1244 * @param {RawSourceMap=} sourceMap source map
1245 * @param {CustomOptions=} minimizerOptions options
1246 * @returns {Promise<MinimizedResult>} minimized result
1247 */
1248async function cleanCssMinify(input, sourceMap, minimizerOptions) {
1249 let CleanCSS;
1250 try {
1251 CleanCSS = require("clean-css");
1252 } catch (err) {
1253 return {
1254 errors: [(/** @type {Error} */err)]
1255 };
1256 }
1257 const [[name, code]] = Object.entries(input);
1258 const result = await new CleanCSS({
1259 sourceMap: Boolean(sourceMap),
1260 ...minimizerOptions,
1261 returnPromise: true
1262 }).minify({
1263 [name]: {
1264 styles: code
1265 }
1266 });
1267 const generatedSourceMap = result.sourceMap ? (/** @type {RawSourceMap} */
1268 /** @type {{ toJSON(): RawSourceMap }} */(/** @type {unknown} */result.sourceMap).toJSON()) : undefined;
1269
1270 // workaround for source maps on windows
1271 if (generatedSourceMap) {
1272 const isWindowsPathSep = require("path").sep === "\\";
1273 generatedSourceMap.sources = generatedSourceMap.sources.map(
1274 /**
1275 * @param {string | null} item path item
1276 * @returns {string} normalized path
1277 */
1278 item => isWindowsPathSep ? (item || "").replace(/\\/g, "/") : item || "");
1279 }
1280 return {
1281 code: result.styles,
1282 map: generatedSourceMap,
1283 warnings: result.warnings
1284 };
1285}
1286
1287/**
1288 * @returns {string | undefined} the minimizer version
1289 */
1290cleanCssMinify.getMinimizerVersion = () => {
1291 let packageJson;
1292 try {
1293 packageJson = require("clean-css/package.json");
1294 } catch (_err) {
1295 // Ignore
1296 }
1297 return packageJson && packageJson.version;
1298};
1299
1300/**
1301 * @returns {boolean | undefined} true if worker threads are supported
1302 */
1303cleanCssMinify.supportsWorkerThreads = () => true;
1304
1305/**
1306 * @param {string} name asset name
1307 * @returns {boolean} true if `name` looks like a CSS file
1308 */
1309cleanCssMinify.filter = name => CSS_FILE_RE.test(name);
1310
1311/* istanbul ignore next */
1312/**
1313 * Minify CSS using `esbuild` (with the CSS loader).
1314 * @param {Input} input input
1315 * @param {RawSourceMap=} sourceMap source map
1316 * @param {CustomOptions=} minimizerOptions options
1317 * @returns {Promise<MinimizedResult>} minimized result
1318 */
1319async function esbuildMinifyCss(input, sourceMap, minimizerOptions) {
1320 /**
1321 * @param {import("esbuild").TransformOptions & { ecma?: string | number, module?: boolean }=} esbuildOptions esbuild options
1322 * @returns {import("esbuild").TransformOptions} built esbuild options
1323 */
1324 const buildEsbuildOptions = (esbuildOptions = {}) => {
1325 // `module` and `ecma` are JavaScript-only concepts; the dispatcher
1326 // injects them for every minimizer, but esbuild's CSS transform
1327 // rejects unknown options.
1328 delete esbuildOptions.ecma;
1329 delete esbuildOptions.module;
1330
1331 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
1332 return {
1333 loader: "css",
1334 minify: true,
1335 legalComments: "inline",
1336 ...esbuildOptions,
1337 sourcemap: false
1338 };
1339 };
1340 let esbuild;
1341 try {
1342 esbuild = require("esbuild");
1343 } catch (err) {
1344 return {
1345 errors: [(/** @type {Error} */err)]
1346 };
1347 }
1348
1349 // Copy `esbuild` options
1350 const esbuildOptions = buildEsbuildOptions(minimizerOptions);
1351
1352 // Let `esbuild` generate a SourceMap
1353 if (sourceMap) {
1354 esbuildOptions.sourcemap = true;
1355 esbuildOptions.sourcesContent = false;
1356 }
1357 const [[filename, code]] = Object.entries(input);
1358 esbuildOptions.sourcefile = filename;
1359 const result = await esbuild.transform(code, esbuildOptions);
1360 return {
1361 code: result.code,
1362 map: result.map ? JSON.parse(result.map) : undefined,
1363 warnings: result.warnings.length > 0 ? result.warnings.map(item => {
1364 const plugin = item.pluginName ? `\nPlugin Name: ${item.pluginName}` : "";
1365 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}` : "";
1366 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")}` : "";
1367 return `${item.text} [${item.id}]${plugin}${location}${item.detail ? `\nDetails:\n${item.detail}` : ""}${notes}`;
1368 }) : []
1369 };
1370}
1371
1372/**
1373 * @returns {string | undefined} the minimizer version
1374 */
1375esbuildMinifyCss.getMinimizerVersion = () => {
1376 let packageJson;
1377 try {
1378 packageJson = require("esbuild/package.json");
1379 } catch (_err) {
1380 // Ignore
1381 }
1382 return packageJson && packageJson.version;
1383};
1384
1385/**
1386 * @returns {boolean | undefined} false because `esbuild` is a native binding
1387 */
1388esbuildMinifyCss.supportsWorkerThreads = () => false;
1389
1390/**
1391 * @param {string} name asset name
1392 * @returns {boolean} true if `name` looks like a CSS file
1393 */
1394esbuildMinifyCss.filter = name => CSS_FILE_RE.test(name);
1395
1396/* istanbul ignore next */
1397/**
1398 * Minify CSS using `lightningcss`.
1399 * @param {Input} input input
1400 * @param {RawSourceMap=} sourceMap source map
1401 * @param {CustomOptions=} minimizerOptions options
1402 * @returns {Promise<MinimizedResult>} minimized result
1403 */
1404async function lightningCssMinify(input, sourceMap, minimizerOptions) {
1405 let lightningCss;
1406 try {
1407 lightningCss = require("lightningcss");
1408 } catch (err) {
1409 return {
1410 errors: [(/** @type {Error} */err)]
1411 };
1412 }
1413 const [[filename, code]] = Object.entries(input);
1414 /**
1415 * @param {Partial<import("lightningcss").TransformOptions<import("lightningcss").CustomAtRules>>=} lightningCssOptions lightning css options
1416 * @returns {import("lightningcss").TransformOptions<import("lightningcss").CustomAtRules>} built lightning css options
1417 */
1418 const buildLightningCssOptions = (lightningCssOptions = {}) => (
1419 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
1420 {
1421 minify: true,
1422 ...lightningCssOptions,
1423 sourceMap: false,
1424 filename,
1425 code: new Uint8Array(Buffer.from(code))
1426 });
1427
1428 // Copy `lightningCss` options
1429 const lightningCssOptions = buildLightningCssOptions(minimizerOptions);
1430
1431 // Let `lightningcss` generate a SourceMap. The dispatcher in
1432 // `minify.js` chains the previous step's map onto this one.
1433 if (sourceMap) {
1434 lightningCssOptions.sourceMap = true;
1435 }
1436 const result = lightningCss.transform(lightningCssOptions);
1437 return {
1438 code: result.code.toString(),
1439 map: result.map ? JSON.parse(result.map.toString()) : undefined
1440 };
1441}
1442
1443/**
1444 * @returns {string | undefined} the minimizer version
1445 */
1446lightningCssMinify.getMinimizerVersion = () => {
1447 let packageJson;
1448 try {
1449 packageJson = require("lightningcss/package.json");
1450 } catch (_err) {
1451 // Ignore
1452 }
1453 return packageJson && packageJson.version;
1454};
1455
1456/**
1457 * @returns {boolean | undefined} false because `lightningcss` is a native binding
1458 */
1459lightningCssMinify.supportsWorkerThreads = () => false;
1460
1461/**
1462 * @param {string} name asset name
1463 * @returns {boolean} true if `name` looks like a CSS file
1464 */
1465lightningCssMinify.filter = name => CSS_FILE_RE.test(name);
1466
1467/* istanbul ignore next */
1468/**
1469 * Map a `@swc/css` diagnostic to a regular `Error`.
1470 * @param {EXPECTED_OBJECT} diagnostic diagnostic from `@swc/css`
1471 * @returns {Error} error preserving `span` and `level` from the diagnostic
1472 */
1473function swcCssDiagnosticToError(diagnostic) {
1474 const typed = /** @type {{ message: string, span?: unknown, level?: unknown }} */
1475 diagnostic;
1476 /** @type {Error & { span?: unknown, level?: unknown }} */
1477 const error = new Error(typed.message);
1478 error.span = typed.span;
1479 error.level = typed.level;
1480 return error;
1481}
1482
1483/* istanbul ignore next */
1484/**
1485 * Minify CSS using `@swc/css`.
1486 * @param {Input} input input
1487 * @param {RawSourceMap=} sourceMap source map
1488 * @param {CustomOptions=} minimizerOptions options
1489 * @returns {Promise<MinimizedResult>} minimized result
1490 */
1491async function swcMinifyCss(input, sourceMap, minimizerOptions) {
1492 let swc;
1493 try {
1494 swc = require("@swc/css");
1495 } catch (err) {
1496 return {
1497 errors: [(/** @type {Error} */err)]
1498 };
1499 }
1500 const [[filename, code]] = Object.entries(input);
1501 /**
1502 * @param {Partial<import("@swc/css").MinifyOptions>=} swcOptions swc options
1503 * @returns {import("@swc/css").MinifyOptions} built swc options
1504 */
1505 const buildSwcOptions = (swcOptions = {}) => (
1506 // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
1507 {
1508 ...swcOptions,
1509 filename
1510 });
1511
1512 // Copy `swc` options
1513 const swcOptions = buildSwcOptions(minimizerOptions);
1514
1515 // Let `swc` generate a SourceMap
1516 if (sourceMap) {
1517 swcOptions.sourceMap = true;
1518 }
1519 const result = await swc.minify(Buffer.from(code), swcOptions);
1520 return {
1521 code: result.code.toString(),
1522 map: result.map ? JSON.parse(result.map.toString()) : undefined,
1523 errors: result.errors ? result.errors.map(swcCssDiagnosticToError) : undefined
1524 };
1525}
1526
1527/**
1528 * @returns {string | undefined} the minimizer version
1529 */
1530swcMinifyCss.getMinimizerVersion = () => {
1531 let packageJson;
1532 try {
1533 packageJson = require("@swc/css/package.json");
1534 } catch (_err) {
1535 // Ignore
1536 }
1537 return packageJson && packageJson.version;
1538};
1539
1540/**
1541 * @returns {boolean | undefined} false because `@swc/css` is a native binding
1542 */
1543swcMinifyCss.supportsWorkerThreads = () => false;
1544
1545/**
1546 * @param {string} name asset name
1547 * @returns {boolean} true if `name` looks like a CSS file
1548 */
1549swcMinifyCss.filter = name => CSS_FILE_RE.test(name);
1550
1551/**
1552 * @template T
1553 * @typedef {() => T} FunctionReturning
1554 */
1555
1556/**
1557 * @template T
1558 * @param {FunctionReturning<T>} fn memorized function
1559 * @returns {FunctionReturning<T>} new function
1560 */
1561function memoize(fn) {
1562 let cache = false;
1563 /** @type {T} */
1564 let result;
1565 return () => {
1566 if (cache) {
1567 return result;
1568 }
1569 result = fn();
1570 cache = true;
1571 // Allow to clean up memory for fn
1572 // and all dependent resources
1573 /** @type {FunctionReturning<T> | undefined} */
1574 fn = undefined;
1575 return /** @type {T} */result;
1576 };
1577}
1578module.exports = {
1579 cleanCssMinify,
1580 cssnanoMinify,
1581 cssoMinify,
1582 esbuildMinify,
1583 esbuildMinifyCss,
1584 getEcmaVersion,
1585 htmlMinifierTerser,
1586 jsonMinify,
1587 lightningCssMinify,
1588 memoize,
1589 minifyHtmlNode,
1590 swcMinify,
1591 swcMinifyCss,
1592 swcMinifyHtml,
1593 swcMinifyHtmlFragment,
1594 terserMinify,
1595 throttleAll,
1596 uglifyJsMinify
1597};
Note: See TracBrowser for help on using the repository browser.