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

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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