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