UNPKG

42.7 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { equals } = require("./util/ArrayHelpers");
9const SortableSet = require("./util/SortableSet");
10const makeSerializable = require("./util/makeSerializable");
11const { forEachRuntime } = require("./util/runtime");
12
13/** @typedef {import("./Dependency").RuntimeSpec} RuntimeSpec */
14/** @typedef {import("./Module")} Module */
15/** @typedef {import("./ModuleGraph")} ModuleGraph */
16/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */
17/** @typedef {import("./util/Hash")} Hash */
18
19/** @typedef {typeof UsageState.OnlyPropertiesUsed | typeof UsageState.NoInfo | typeof UsageState.Unknown | typeof UsageState.Used} RuntimeUsageStateType */
20/** @typedef {typeof UsageState.Unused | RuntimeUsageStateType} UsageStateType */
21
22const UsageState = Object.freeze({
23 Unused: /** @type {0} */ (0),
24 OnlyPropertiesUsed: /** @type {1} */ (1),
25 NoInfo: /** @type {2} */ (2),
26 Unknown: /** @type {3} */ (3),
27 Used: /** @type {4} */ (4)
28});
29
30const RETURNS_TRUE = () => true;
31
32const CIRCULAR = Symbol("circular target");
33
34class RestoreProvidedData {
35 constructor(
36 exports,
37 otherProvided,
38 otherCanMangleProvide,
39 otherTerminalBinding
40 ) {
41 this.exports = exports;
42 this.otherProvided = otherProvided;
43 this.otherCanMangleProvide = otherCanMangleProvide;
44 this.otherTerminalBinding = otherTerminalBinding;
45 }
46
47 serialize({ write }) {
48 write(this.exports);
49 write(this.otherProvided);
50 write(this.otherCanMangleProvide);
51 write(this.otherTerminalBinding);
52 }
53
54 static deserialize({ read }) {
55 return new RestoreProvidedData(read(), read(), read(), read());
56 }
57}
58
59makeSerializable(
60 RestoreProvidedData,
61 "webpack/lib/ModuleGraph",
62 "RestoreProvidedData"
63);
64
65class ExportsInfo {
66 constructor() {
67 /** @type {Map<string, ExportInfo>} */
68 this._exports = new Map();
69 this._otherExportsInfo = new ExportInfo(null);
70 this._sideEffectsOnlyInfo = new ExportInfo("*side effects only*");
71 this._exportsAreOrdered = false;
72 /** @type {ExportsInfo=} */
73 this._redirectTo = undefined;
74 }
75
76 /**
77 * @returns {Iterable<ExportInfo>} all owned exports in any order
78 */
79 get ownedExports() {
80 return this._exports.values();
81 }
82
83 /**
84 * @returns {Iterable<ExportInfo>} all owned exports in order
85 */
86 get orderedOwnedExports() {
87 if (!this._exportsAreOrdered) {
88 this._sortExports();
89 }
90 return this._exports.values();
91 }
92
93 /**
94 * @returns {Iterable<ExportInfo>} all exports in any order
95 */
96 get exports() {
97 if (this._redirectTo !== undefined) {
98 const map = new Map(this._redirectTo._exports);
99 for (const [key, value] of this._exports) {
100 map.set(key, value);
101 }
102 return map.values();
103 }
104 return this._exports.values();
105 }
106
107 /**
108 * @returns {Iterable<ExportInfo>} all exports in order
109 */
110 get orderedExports() {
111 if (!this._exportsAreOrdered) {
112 this._sortExports();
113 }
114 if (this._redirectTo !== undefined) {
115 const map = new Map(
116 Array.from(this._redirectTo.orderedExports, item => [item.name, item])
117 );
118 for (const [key, value] of this._exports) {
119 map.set(key, value);
120 }
121 // sorting should be pretty fast as map contains
122 // a lot of presorted items
123 this._sortExportsMap(map);
124 return map.values();
125 }
126 return this._exports.values();
127 }
128
129 /**
130 * @returns {ExportInfo} the export info of unlisted exports
131 */
132 get otherExportsInfo() {
133 if (this._redirectTo !== undefined)
134 return this._redirectTo.otherExportsInfo;
135 return this._otherExportsInfo;
136 }
137
138 _sortExportsMap(exports) {
139 if (exports.size > 1) {
140 const entriesInOrder = Array.from(exports.values());
141 if (
142 entriesInOrder.length !== 2 ||
143 entriesInOrder[0].name > entriesInOrder[1].name
144 ) {
145 entriesInOrder.sort((a, b) => {
146 return a.name < b.name ? -1 : 1;
147 });
148 exports.clear();
149 for (const entry of entriesInOrder) {
150 exports.set(entry.name, entry);
151 }
152 }
153 }
154 }
155
156 _sortExports() {
157 this._sortExportsMap(this._exports);
158 this._exportsAreOrdered = true;
159 }
160
161 setRedirectNamedTo(exportsInfo) {
162 if (this._redirectTo === exportsInfo) return false;
163 this._redirectTo = exportsInfo;
164 return true;
165 }
166
167 setHasProvideInfo() {
168 for (const exportInfo of this._exports.values()) {
169 if (exportInfo.provided === undefined) {
170 exportInfo.provided = false;
171 }
172 if (exportInfo.canMangleProvide === undefined) {
173 exportInfo.canMangleProvide = true;
174 }
175 }
176 if (this._redirectTo !== undefined) {
177 this._redirectTo.setHasProvideInfo();
178 } else {
179 if (this._otherExportsInfo.provided === undefined) {
180 this._otherExportsInfo.provided = false;
181 }
182 if (this._otherExportsInfo.canMangleProvide === undefined) {
183 this._otherExportsInfo.canMangleProvide = true;
184 }
185 }
186 }
187
188 setHasUseInfo() {
189 for (const exportInfo of this._exports.values()) {
190 exportInfo.setHasUseInfo();
191 }
192 this._sideEffectsOnlyInfo.setHasUseInfo();
193 if (this._redirectTo !== undefined) {
194 this._redirectTo.setHasUseInfo();
195 } else {
196 this._otherExportsInfo.setHasUseInfo();
197 if (this._otherExportsInfo.canMangleUse === undefined) {
198 this._otherExportsInfo.canMangleUse = true;
199 }
200 }
201 }
202
203 /**
204 * @param {string} name export name
205 * @returns {ExportInfo} export info for this name
206 */
207 getOwnExportInfo(name) {
208 const info = this._exports.get(name);
209 if (info !== undefined) return info;
210 const newInfo = new ExportInfo(name, this._otherExportsInfo);
211 this._exports.set(name, newInfo);
212 this._exportsAreOrdered = false;
213 return newInfo;
214 }
215
216 /**
217 * @param {string} name export name
218 * @returns {ExportInfo} export info for this name
219 */
220 getExportInfo(name) {
221 const info = this._exports.get(name);
222 if (info !== undefined) return info;
223 if (this._redirectTo !== undefined)
224 return this._redirectTo.getExportInfo(name);
225 const newInfo = new ExportInfo(name, this._otherExportsInfo);
226 this._exports.set(name, newInfo);
227 this._exportsAreOrdered = false;
228 return newInfo;
229 }
230
231 /**
232 * @param {string} name export name
233 * @returns {ExportInfo} export info for this name
234 */
235 getReadOnlyExportInfo(name) {
236 const info = this._exports.get(name);
237 if (info !== undefined) return info;
238 if (this._redirectTo !== undefined)
239 return this._redirectTo.getReadOnlyExportInfo(name);
240 return this._otherExportsInfo;
241 }
242
243 /**
244 * @param {string[]} name export name
245 * @returns {ExportInfo | undefined} export info for this name
246 */
247 getReadOnlyExportInfoRecursive(name) {
248 const exportInfo = this.getReadOnlyExportInfo(name[0]);
249 if (name.length === 1) return exportInfo;
250 if (!exportInfo.exportsInfo) return undefined;
251 return exportInfo.exportsInfo.getReadOnlyExportInfoRecursive(name.slice(1));
252 }
253
254 /**
255 * @param {string[]=} name the export name
256 * @returns {ExportsInfo | undefined} the nested exports info
257 */
258 getNestedExportsInfo(name) {
259 if (Array.isArray(name) && name.length > 0) {
260 const info = this.getReadOnlyExportInfo(name[0]);
261 if (!info.exportsInfo) return undefined;
262 return info.exportsInfo.getNestedExportsInfo(name.slice(1));
263 }
264 return this;
265 }
266
267 /**
268 * @param {boolean=} canMangle true, if exports can still be mangled (defaults to false)
269 * @param {Set<string>=} excludeExports list of unaffected exports
270 * @param {any=} targetKey use this as key for the target
271 * @param {ModuleGraphConnection=} targetModule set this module as target
272 * @returns {boolean} true, if this call changed something
273 */
274 setUnknownExportsProvided(
275 canMangle,
276 excludeExports,
277 targetKey,
278 targetModule
279 ) {
280 let changed = false;
281 if (excludeExports) {
282 for (const name of excludeExports) {
283 // Make sure these entries exist, so they can get different info
284 this.getExportInfo(name);
285 }
286 }
287 for (const exportInfo of this._exports.values()) {
288 if (excludeExports && excludeExports.has(exportInfo.name)) continue;
289 if (exportInfo.provided !== true && exportInfo.provided !== null) {
290 exportInfo.provided = null;
291 changed = true;
292 }
293 if (!canMangle && exportInfo.canMangleProvide !== false) {
294 exportInfo.canMangleProvide = false;
295 changed = true;
296 }
297 if (targetKey) {
298 exportInfo.setTarget(targetKey, targetModule, [exportInfo.name]);
299 }
300 }
301 if (this._redirectTo !== undefined) {
302 if (
303 this._redirectTo.setUnknownExportsProvided(
304 canMangle,
305 excludeExports,
306 targetKey,
307 targetModule
308 )
309 ) {
310 changed = true;
311 }
312 } else {
313 if (
314 this._otherExportsInfo.provided !== true &&
315 this._otherExportsInfo.provided !== null
316 ) {
317 this._otherExportsInfo.provided = null;
318 changed = true;
319 }
320 if (!canMangle && this._otherExportsInfo.canMangleProvide !== false) {
321 this._otherExportsInfo.canMangleProvide = false;
322 changed = true;
323 }
324 if (targetKey) {
325 this._otherExportsInfo.setTarget(targetKey, targetModule, undefined);
326 }
327 }
328 return changed;
329 }
330
331 /**
332 * @param {RuntimeSpec} runtime the runtime
333 * @returns {boolean} true, when something changed
334 */
335 setUsedInUnknownWay(runtime) {
336 let changed = false;
337 for (const exportInfo of this._exports.values()) {
338 if (exportInfo.setUsedInUnknownWay(runtime)) {
339 changed = true;
340 }
341 }
342 if (this._redirectTo !== undefined) {
343 if (this._redirectTo.setUsedInUnknownWay(runtime)) {
344 changed = true;
345 }
346 } else {
347 if (
348 this._otherExportsInfo.setUsedConditionally(
349 used => used < UsageState.Unknown,
350 UsageState.Unknown,
351 runtime
352 )
353 ) {
354 changed = true;
355 }
356 if (this._otherExportsInfo.canMangleUse !== false) {
357 this._otherExportsInfo.canMangleUse = false;
358 changed = true;
359 }
360 }
361 return changed;
362 }
363
364 /**
365 * @param {RuntimeSpec} runtime the runtime
366 * @returns {boolean} true, when something changed
367 */
368 setUsedWithoutInfo(runtime) {
369 let changed = false;
370 for (const exportInfo of this._exports.values()) {
371 if (exportInfo.setUsedWithoutInfo(runtime)) {
372 changed = true;
373 }
374 }
375 if (this._redirectTo !== undefined) {
376 if (this._redirectTo.setUsedWithoutInfo(runtime)) {
377 changed = true;
378 }
379 } else {
380 if (this._otherExportsInfo.setUsed(UsageState.NoInfo, runtime)) {
381 changed = true;
382 }
383 if (this._otherExportsInfo.canMangleUse !== false) {
384 this._otherExportsInfo.canMangleUse = false;
385 changed = true;
386 }
387 }
388 return changed;
389 }
390
391 /**
392 * @param {RuntimeSpec} runtime the runtime
393 * @returns {boolean} true, when something changed
394 */
395 setAllKnownExportsUsed(runtime) {
396 let changed = false;
397 for (const exportInfo of this._exports.values()) {
398 if (!exportInfo.provided) continue;
399 if (exportInfo.setUsed(UsageState.Used, runtime)) {
400 changed = true;
401 }
402 }
403 return changed;
404 }
405
406 /**
407 * @param {RuntimeSpec} runtime the runtime
408 * @returns {boolean} true, when something changed
409 */
410 setUsedForSideEffectsOnly(runtime) {
411 return this._sideEffectsOnlyInfo.setUsedConditionally(
412 used => used === UsageState.Unused,
413 UsageState.Used,
414 runtime
415 );
416 }
417
418 /**
419 * @param {RuntimeSpec} runtime the runtime
420 * @returns {boolean} true, when the module exports are used in any way
421 */
422 isUsed(runtime) {
423 if (this._redirectTo !== undefined) {
424 if (this._redirectTo.isUsed(runtime)) {
425 return true;
426 }
427 } else {
428 if (this._otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
429 return true;
430 }
431 }
432 for (const exportInfo of this._exports.values()) {
433 if (exportInfo.getUsed(runtime) !== UsageState.Unused) {
434 return true;
435 }
436 }
437 return false;
438 }
439
440 /**
441 * @param {RuntimeSpec} runtime the runtime
442 * @returns {boolean} true, when the module is used in any way
443 */
444 isModuleUsed(runtime) {
445 if (this.isUsed(runtime)) return true;
446 if (this._sideEffectsOnlyInfo.getUsed(runtime) !== UsageState.Unused)
447 return true;
448 return false;
449 }
450
451 /**
452 * @param {RuntimeSpec} runtime the runtime
453 * @returns {SortableSet<string> | boolean | null} set of used exports, or true (when namespace object is used), or false (when unused), or null (when unknown)
454 */
455 getUsedExports(runtime) {
456 if (!this._redirectTo !== undefined) {
457 switch (this._otherExportsInfo.getUsed(runtime)) {
458 case UsageState.NoInfo:
459 return null;
460 case UsageState.Unknown:
461 case UsageState.OnlyPropertiesUsed:
462 case UsageState.Used:
463 return true;
464 }
465 }
466 const array = [];
467 if (!this._exportsAreOrdered) this._sortExports();
468 for (const exportInfo of this._exports.values()) {
469 switch (exportInfo.getUsed(runtime)) {
470 case UsageState.NoInfo:
471 return null;
472 case UsageState.Unknown:
473 return true;
474 case UsageState.OnlyPropertiesUsed:
475 case UsageState.Used:
476 array.push(exportInfo.name);
477 }
478 }
479 if (this._redirectTo !== undefined) {
480 const inner = this._redirectTo.getUsedExports(runtime);
481 if (inner === null) return null;
482 if (inner === true) return true;
483 if (inner !== false) {
484 for (const item of inner) {
485 array.push(item);
486 }
487 }
488 }
489 if (array.length === 0) {
490 switch (this._sideEffectsOnlyInfo.getUsed(runtime)) {
491 case UsageState.NoInfo:
492 return null;
493 case UsageState.Unused:
494 return false;
495 }
496 }
497 return new SortableSet(array);
498 }
499
500 /**
501 * @returns {null | true | string[]} list of exports when known
502 */
503 getProvidedExports() {
504 if (!this._redirectTo !== undefined) {
505 switch (this._otherExportsInfo.provided) {
506 case undefined:
507 return null;
508 case null:
509 return true;
510 case true:
511 return true;
512 }
513 }
514 const array = [];
515 if (!this._exportsAreOrdered) this._sortExports();
516 for (const exportInfo of this._exports.values()) {
517 switch (exportInfo.provided) {
518 case undefined:
519 return null;
520 case null:
521 return true;
522 case true:
523 array.push(exportInfo.name);
524 }
525 }
526 if (this._redirectTo !== undefined) {
527 const inner = this._redirectTo.getProvidedExports();
528 if (inner === null) return null;
529 if (inner === true) return true;
530 for (const item of inner) {
531 if (!array.includes(item)) {
532 array.push(item);
533 }
534 }
535 }
536 return array;
537 }
538
539 /**
540 * @param {RuntimeSpec} runtime the runtime
541 * @returns {ExportInfo[]} exports that are relevant (not unused and potential provided)
542 */
543 getRelevantExports(runtime) {
544 const list = [];
545 for (const exportInfo of this._exports.values()) {
546 const used = exportInfo.getUsed(runtime);
547 if (used === UsageState.Unused) continue;
548 if (exportInfo.provided === false) continue;
549 list.push(exportInfo);
550 }
551 if (this._redirectTo !== undefined) {
552 for (const exportInfo of this._redirectTo.getRelevantExports(runtime)) {
553 if (!this._exports.has(exportInfo.name)) list.push(exportInfo);
554 }
555 }
556 if (
557 this._otherExportsInfo.provided !== false &&
558 this._otherExportsInfo.getUsed(runtime) !== UsageState.Unused
559 ) {
560 list.push(this._otherExportsInfo);
561 }
562 return list;
563 }
564
565 /**
566 * @param {string | string[]} name the name of the export
567 * @returns {boolean | undefined | null} if the export is provided
568 */
569 isExportProvided(name) {
570 if (Array.isArray(name)) {
571 const info = this.getReadOnlyExportInfo(name[0]);
572 if (info.exportsInfo && name.length > 1) {
573 return info.exportsInfo.isExportProvided(name.slice(1));
574 }
575 return info.provided;
576 }
577 const info = this.getReadOnlyExportInfo(name);
578 return info.provided;
579 }
580
581 /**
582 * @param {RuntimeSpec} runtime runtime
583 * @returns {string} key representing the usage
584 */
585 getUsageKey(runtime) {
586 const key = [];
587 if (this._redirectTo !== undefined) {
588 key.push(this._redirectTo.getUsageKey(runtime));
589 } else {
590 key.push(this._otherExportsInfo.getUsed(runtime));
591 }
592 key.push(this._sideEffectsOnlyInfo.getUsed(runtime));
593 for (const exportInfo of this.orderedOwnedExports) {
594 key.push(exportInfo.getUsed(runtime));
595 }
596 return key.join("|");
597 }
598
599 /**
600 * @param {RuntimeSpec} runtimeA first runtime
601 * @param {RuntimeSpec} runtimeB second runtime
602 * @returns {boolean} true, when equally used
603 */
604 isEquallyUsed(runtimeA, runtimeB) {
605 if (this._redirectTo !== undefined) {
606 if (!this._redirectTo.isEquallyUsed(runtimeA, runtimeB)) return false;
607 } else {
608 if (
609 this._otherExportsInfo.getUsed(runtimeA) !==
610 this._otherExportsInfo.getUsed(runtimeB)
611 ) {
612 return false;
613 }
614 }
615 if (
616 this._sideEffectsOnlyInfo.getUsed(runtimeA) !==
617 this._sideEffectsOnlyInfo.getUsed(runtimeB)
618 ) {
619 return false;
620 }
621 for (const exportInfo of this.ownedExports) {
622 if (exportInfo.getUsed(runtimeA) !== exportInfo.getUsed(runtimeB))
623 return false;
624 }
625 return true;
626 }
627
628 /**
629 * @param {string | string[]} name export name
630 * @param {RuntimeSpec} runtime check usage for this runtime only
631 * @returns {UsageStateType} usage status
632 */
633 getUsed(name, runtime) {
634 if (Array.isArray(name)) {
635 if (name.length === 0) return this.otherExportsInfo.getUsed(runtime);
636 let info = this.getReadOnlyExportInfo(name[0]);
637 if (info.exportsInfo && name.length > 1) {
638 return info.exportsInfo.getUsed(name.slice(1), runtime);
639 }
640 return info.getUsed(runtime);
641 }
642 let info = this.getReadOnlyExportInfo(name);
643 return info.getUsed(runtime);
644 }
645
646 /**
647 * @param {string | string[]} name the export name
648 * @param {RuntimeSpec} runtime check usage for this runtime only
649 * @returns {string | string[] | false} the used name
650 */
651 getUsedName(name, runtime) {
652 if (Array.isArray(name)) {
653 // TODO improve this
654 if (name.length === 0) {
655 if (!this.isUsed(runtime)) return false;
656 return name;
657 }
658 let info = this.getReadOnlyExportInfo(name[0]);
659 const x = info.getUsedName(name[0], runtime);
660 if (x === false) return false;
661 const arr = x === name[0] && name.length === 1 ? name : [x];
662 if (name.length === 1) {
663 return arr;
664 }
665 if (
666 info.exportsInfo &&
667 info.getUsed(runtime) === UsageState.OnlyPropertiesUsed
668 ) {
669 const nested = info.exportsInfo.getUsedName(name.slice(1), runtime);
670 if (!nested) return false;
671 return arr.concat(nested);
672 } else {
673 return arr.concat(name.slice(1));
674 }
675 } else {
676 let info = this.getReadOnlyExportInfo(name);
677 const usedName = info.getUsedName(name, runtime);
678 return usedName;
679 }
680 }
681
682 /**
683 * @param {Hash} hash the hash
684 * @param {RuntimeSpec} runtime the runtime
685 * @returns {void}
686 */
687 updateHash(hash, runtime) {
688 this._updateHash(hash, runtime, new Set());
689 }
690
691 /**
692 * @param {Hash} hash the hash
693 * @param {RuntimeSpec} runtime the runtime
694 * @param {Set<ExportsInfo>} alreadyVisitedExportsInfo for circular references
695 * @returns {void}
696 */
697 _updateHash(hash, runtime, alreadyVisitedExportsInfo) {
698 const set = new Set(alreadyVisitedExportsInfo);
699 set.add(this);
700 for (const exportInfo of this.orderedExports) {
701 if (exportInfo.hasInfo(this._otherExportsInfo, runtime)) {
702 exportInfo._updateHash(hash, runtime, set);
703 }
704 }
705 this._sideEffectsOnlyInfo._updateHash(hash, runtime, set);
706 this._otherExportsInfo._updateHash(hash, runtime, set);
707 if (this._redirectTo !== undefined) {
708 this._redirectTo._updateHash(hash, runtime, set);
709 }
710 }
711
712 getRestoreProvidedData() {
713 const otherProvided = this._otherExportsInfo.provided;
714 const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide;
715 const otherTerminalBinding = this._otherExportsInfo.terminalBinding;
716 const exports = [];
717 for (const exportInfo of this._exports.values()) {
718 if (
719 exportInfo.provided !== otherProvided ||
720 exportInfo.canMangleProvide !== otherCanMangleProvide ||
721 exportInfo.terminalBinding !== otherTerminalBinding ||
722 exportInfo.exportsInfoOwned
723 ) {
724 exports.push({
725 name: exportInfo.name,
726 provided: exportInfo.provided,
727 canMangleProvide: exportInfo.canMangleProvide,
728 terminalBinding: exportInfo.terminalBinding,
729 exportsInfo: exportInfo.exportsInfoOwned
730 ? exportInfo.exportsInfo.getRestoreProvidedData()
731 : undefined
732 });
733 }
734 }
735 return new RestoreProvidedData(
736 exports,
737 otherProvided,
738 otherCanMangleProvide,
739 otherTerminalBinding
740 );
741 }
742
743 restoreProvided({
744 otherProvided,
745 otherCanMangleProvide,
746 otherTerminalBinding,
747 exports
748 }) {
749 for (const exportInfo of this._exports.values()) {
750 exportInfo.provided = otherProvided;
751 exportInfo.canMangleProvide = otherCanMangleProvide;
752 exportInfo.terminalBinding = otherTerminalBinding;
753 }
754 this._otherExportsInfo.provided = otherProvided;
755 this._otherExportsInfo.canMangleProvide = otherCanMangleProvide;
756 this._otherExportsInfo.terminalBinding = otherTerminalBinding;
757 for (const exp of exports) {
758 const exportInfo = this.getExportInfo(exp.name);
759 exportInfo.provided = exp.provided;
760 exportInfo.canMangleProvide = exp.canMangleProvide;
761 exportInfo.terminalBinding = exp.terminalBinding;
762 if (exp.exportsInfo) {
763 const exportsInfo = exportInfo.createNestedExportsInfo();
764 exportsInfo.restoreProvided(exp.exportsInfo);
765 }
766 }
767 }
768}
769
770class ExportInfo {
771 /**
772 * @param {string} name the original name of the export
773 * @param {ExportInfo=} initFrom init values from this ExportInfo
774 */
775 constructor(name, initFrom) {
776 /** @type {string} */
777 this.name = name;
778 /** @private @type {string | null} */
779 this._usedName = initFrom ? initFrom._usedName : null;
780 /** @private @type {UsageStateType} */
781 this._globalUsed = initFrom ? initFrom._globalUsed : undefined;
782 /** @private @type {Map<string, RuntimeUsageStateType>} */
783 this._usedInRuntime =
784 initFrom && initFrom._usedInRuntime
785 ? new Map(initFrom._usedInRuntime)
786 : undefined;
787 /** @private @type {boolean} */
788 this._hasUseInRuntimeInfo = initFrom
789 ? initFrom._hasUseInRuntimeInfo
790 : false;
791 /**
792 * true: it is provided
793 * false: it is not provided
794 * null: only the runtime knows if it is provided
795 * undefined: it was not determined if it is provided
796 * @type {boolean | null | undefined}
797 */
798 this.provided = initFrom ? initFrom.provided : undefined;
799 /**
800 * is the export a terminal binding that should be checked for export star conflicts
801 * @type {boolean}
802 */
803 this.terminalBinding = initFrom ? initFrom.terminalBinding : false;
804 /**
805 * true: it can be mangled
806 * false: is can not be mangled
807 * undefined: it was not determined if it can be mangled
808 * @type {boolean | undefined}
809 */
810 this.canMangleProvide = initFrom ? initFrom.canMangleProvide : undefined;
811 /**
812 * true: it can be mangled
813 * false: is can not be mangled
814 * undefined: it was not determined if it can be mangled
815 * @type {boolean | undefined}
816 */
817 this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
818 /** @type {boolean} */
819 this.exportsInfoOwned = false;
820 /** @type {ExportsInfo=} */
821 this.exportsInfo = undefined;
822 /** @type {Map<any, { connection: ModuleGraphConnection, export: string[] } | null>=} */
823 this._target = undefined;
824 if (initFrom && initFrom._target) {
825 this._target = new Map();
826 for (const [key, value] of initFrom._target) {
827 this._target.set(
828 key,
829 value ? { connection: value.connection, export: [name] } : null
830 );
831 }
832 }
833 }
834
835 // TODO webpack 5 remove
836 /** @private */
837 get used() {
838 throw new Error("REMOVED");
839 }
840 /** @private */
841 get usedName() {
842 throw new Error("REMOVED");
843 }
844 /**
845 * @private
846 * @param {*} v v
847 */
848 set used(v) {
849 throw new Error("REMOVED");
850 }
851 /**
852 * @private
853 * @param {*} v v
854 */
855 set usedName(v) {
856 throw new Error("REMOVED");
857 }
858
859 get canMangle() {
860 switch (this.canMangleProvide) {
861 case undefined:
862 return this.canMangleUse === false ? false : undefined;
863 case false:
864 return false;
865 case true:
866 switch (this.canMangleUse) {
867 case undefined:
868 return undefined;
869 case false:
870 return false;
871 case true:
872 return true;
873 }
874 }
875 throw new Error(
876 `Unexpected flags for canMangle ${this.canMangleProvide} ${this.canMangleUse}`
877 );
878 }
879
880 /**
881 * @param {RuntimeSpec} runtime only apply to this runtime
882 * @returns {boolean} true, when something changed
883 */
884 setUsedInUnknownWay(runtime) {
885 let changed = false;
886 if (
887 this.setUsedConditionally(
888 used => used < UsageState.Unknown,
889 UsageState.Unknown,
890 runtime
891 )
892 ) {
893 changed = true;
894 }
895 if (this.canMangleUse !== false) {
896 this.canMangleUse = false;
897 changed = true;
898 }
899 return changed;
900 }
901
902 /**
903 * @param {RuntimeSpec} runtime only apply to this runtime
904 * @returns {boolean} true, when something changed
905 */
906 setUsedWithoutInfo(runtime) {
907 let changed = false;
908 if (this.setUsed(UsageState.NoInfo, runtime)) {
909 changed = true;
910 }
911 if (this.canMangleUse !== false) {
912 this.canMangleUse = false;
913 changed = true;
914 }
915 return changed;
916 }
917
918 setHasUseInfo() {
919 if (!this._hasUseInRuntimeInfo) {
920 this._hasUseInRuntimeInfo = true;
921 }
922 if (this.canMangleUse === undefined) {
923 this.canMangleUse = true;
924 }
925 if (this.exportsInfoOwned) {
926 this.exportsInfo.setHasUseInfo();
927 }
928 }
929
930 /**
931 * @param {function(UsageStateType): boolean} condition compare with old value
932 * @param {UsageStateType} newValue set when condition is true
933 * @param {RuntimeSpec} runtime only apply to this runtime
934 * @returns {boolean} true when something has changed
935 */
936 setUsedConditionally(condition, newValue, runtime) {
937 if (runtime === undefined) {
938 if (this._globalUsed === undefined) {
939 this._globalUsed = newValue;
940 return true;
941 } else {
942 if (this._globalUsed !== newValue && condition(this._globalUsed)) {
943 this._globalUsed = newValue;
944 return true;
945 }
946 }
947 } else if (this._usedInRuntime === undefined) {
948 if (newValue !== UsageState.Unused && condition(UsageState.Unused)) {
949 this._usedInRuntime = new Map();
950 forEachRuntime(runtime, runtime =>
951 this._usedInRuntime.set(runtime, newValue)
952 );
953 return true;
954 }
955 } else {
956 let changed = false;
957 forEachRuntime(runtime, runtime => {
958 /** @type {UsageStateType} */
959 let oldValue = this._usedInRuntime.get(runtime);
960 if (oldValue === undefined) oldValue = UsageState.Unused;
961 if (newValue !== oldValue && condition(oldValue)) {
962 if (newValue === UsageState.Unused) {
963 this._usedInRuntime.delete(runtime);
964 } else {
965 this._usedInRuntime.set(runtime, newValue);
966 }
967 changed = true;
968 }
969 });
970 if (changed) {
971 if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
972 return true;
973 }
974 }
975 return false;
976 }
977
978 /**
979 * @param {UsageStateType} newValue new value of the used state
980 * @param {RuntimeSpec} runtime only apply to this runtime
981 * @returns {boolean} true when something has changed
982 */
983 setUsed(newValue, runtime) {
984 if (runtime === undefined) {
985 if (this._globalUsed !== newValue) {
986 this._globalUsed = newValue;
987 return true;
988 }
989 } else if (this._usedInRuntime === undefined) {
990 if (newValue !== UsageState.Unused) {
991 this._usedInRuntime = new Map();
992 forEachRuntime(runtime, runtime =>
993 this._usedInRuntime.set(runtime, newValue)
994 );
995 return true;
996 }
997 } else {
998 let changed = false;
999 forEachRuntime(runtime, runtime => {
1000 /** @type {UsageStateType} */
1001 let oldValue = this._usedInRuntime.get(runtime);
1002 if (oldValue === undefined) oldValue = UsageState.Unused;
1003 if (newValue !== oldValue) {
1004 if (newValue === UsageState.Unused) {
1005 this._usedInRuntime.delete(runtime);
1006 } else {
1007 this._usedInRuntime.set(runtime, newValue);
1008 }
1009 changed = true;
1010 }
1011 });
1012 if (changed) {
1013 if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
1014 return true;
1015 }
1016 }
1017 return false;
1018 }
1019
1020 /**
1021 * @param {any} key the key
1022 * @returns {boolean} true, if something has changed
1023 */
1024 unsetTarget(key) {
1025 if (!this._target) return false;
1026 return this._target.delete(key);
1027 }
1028
1029 /**
1030 * @param {any} key the key
1031 * @param {ModuleGraphConnection=} connection the target module if a single one
1032 * @param {string[]=} exportName the exported name
1033 * @returns {boolean} true, if something has changed
1034 */
1035 setTarget(key, connection, exportName) {
1036 if (exportName) exportName = [...exportName];
1037 if (!this._target) {
1038 this._target = new Map();
1039 this._target.set(
1040 key,
1041 connection ? { connection, export: exportName } : null
1042 );
1043 return true;
1044 }
1045 const oldTarget = this._target.get(key);
1046 if (!oldTarget) {
1047 if (oldTarget === null && !connection) return false;
1048 this._target.set(
1049 key,
1050 connection ? { connection, export: exportName } : null
1051 );
1052 return true;
1053 }
1054 if (!connection) {
1055 this._target.set(key, null);
1056 return true;
1057 }
1058 if (
1059 oldTarget.connection !== connection ||
1060 (exportName
1061 ? !oldTarget.export || !equals(oldTarget.export, exportName)
1062 : oldTarget.export)
1063 ) {
1064 oldTarget.connection = connection;
1065 oldTarget.export = exportName;
1066 return true;
1067 }
1068 return false;
1069 }
1070
1071 /**
1072 * @param {RuntimeSpec} runtime for this runtime
1073 * @returns {UsageStateType} usage state
1074 */
1075 getUsed(runtime) {
1076 if (!this._hasUseInRuntimeInfo) return UsageState.NoInfo;
1077 if (this._globalUsed !== undefined) return this._globalUsed;
1078 if (this._usedInRuntime === undefined) {
1079 return UsageState.Unused;
1080 } else if (typeof runtime === "string") {
1081 const value = this._usedInRuntime.get(runtime);
1082 return value === undefined ? UsageState.Unused : value;
1083 } else if (runtime === undefined) {
1084 /** @type {UsageStateType} */
1085 let max = UsageState.Unused;
1086 for (const value of this._usedInRuntime.values()) {
1087 if (value === UsageState.Used) {
1088 return UsageState.Used;
1089 }
1090 if (max < value) max = value;
1091 }
1092 return max;
1093 } else {
1094 /** @type {UsageStateType} */
1095 let max = UsageState.Unused;
1096 for (const item of runtime) {
1097 const value = this._usedInRuntime.get(item);
1098 if (value !== undefined) {
1099 if (value === UsageState.Used) {
1100 return UsageState.Used;
1101 }
1102 if (max < value) max = value;
1103 }
1104 }
1105 return max;
1106 }
1107 }
1108
1109 /**
1110 * get used name
1111 * @param {string | undefined} fallbackName fallback name for used exports with no name
1112 * @param {RuntimeSpec} runtime check usage for this runtime only
1113 * @returns {string | false} used name
1114 */
1115 getUsedName(fallbackName, runtime) {
1116 if (this._hasUseInRuntimeInfo) {
1117 if (this._globalUsed !== undefined) {
1118 if (this._globalUsed === UsageState.Unused) return false;
1119 } else {
1120 if (this._usedInRuntime === undefined) return false;
1121 if (typeof runtime === "string") {
1122 if (!this._usedInRuntime.has(runtime)) {
1123 return false;
1124 }
1125 } else if (runtime !== undefined) {
1126 if (
1127 Array.from(runtime).every(
1128 runtime => !this._usedInRuntime.has(runtime)
1129 )
1130 ) {
1131 return false;
1132 }
1133 }
1134 }
1135 }
1136 if (this._usedName !== null) return this._usedName;
1137 return this.name || fallbackName;
1138 }
1139
1140 /**
1141 * @returns {boolean} true, when a mangled name of this export is set
1142 */
1143 hasUsedName() {
1144 return this._usedName !== null;
1145 }
1146
1147 /**
1148 * Sets the mangled name of this export
1149 * @param {string} name the new name
1150 * @returns {void}
1151 */
1152 setUsedName(name) {
1153 this._usedName = name;
1154 }
1155
1156 /**
1157 * @param {ModuleGraph} moduleGraph the module graph
1158 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1159 * @returns {ExportInfo | ExportsInfo | undefined} the terminal binding export(s) info if known
1160 */
1161 getTerminalBinding(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
1162 if (this.terminalBinding) return this;
1163 const target = this.getTarget(moduleGraph, resolveTargetFilter);
1164 if (!target) return undefined;
1165 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1166 if (!target.export) return exportsInfo;
1167 return exportsInfo.getReadOnlyExportInfoRecursive(target.export);
1168 }
1169
1170 isReexport() {
1171 return !this.terminalBinding && this._target && this._target.size > 0;
1172 }
1173
1174 /**
1175 * @param {ModuleGraph} moduleGraph the module graph
1176 * @param {function(Module): boolean} validTargetModuleFilter a valid target module
1177 * @returns {{ module: Module, export: string[] | undefined } | undefined | false} the target, undefined when there is no target, false when no target is valid
1178 */
1179 findTarget(moduleGraph, validTargetModuleFilter) {
1180 return this._findTarget(moduleGraph, validTargetModuleFilter, new Set());
1181 }
1182
1183 /**
1184 * @param {ModuleGraph} moduleGraph the module graph
1185 * @param {function(Module): boolean} validTargetModuleFilter a valid target module
1186 * @param {Set<ExportInfo> | undefined} alreadyVisited set of already visited export info to avoid circular references
1187 * @returns {{ module: Module, export: string[] | undefined } | undefined | false} the target, undefined when there is no target, false when no target is valid
1188 */
1189 _findTarget(moduleGraph, validTargetModuleFilter, alreadyVisited) {
1190 if (!this._target || this._target.size === 0) return undefined;
1191 let rawTarget = this._target.values().next().value;
1192 if (!rawTarget) return undefined;
1193 /** @type {{ module: Module, export: string[] | undefined }} */
1194 let target = {
1195 module: rawTarget.connection.module,
1196 export: rawTarget.export
1197 };
1198 for (;;) {
1199 if (validTargetModuleFilter(target.module)) return target;
1200 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1201 const exportInfo = exportsInfo.getExportInfo(target.export[0]);
1202 if (alreadyVisited.has(exportInfo)) return null;
1203 const newTarget = exportInfo._findTarget(
1204 moduleGraph,
1205 validTargetModuleFilter,
1206 alreadyVisited
1207 );
1208 if (!newTarget) return false;
1209 if (target.export.length === 1) {
1210 target = newTarget;
1211 } else {
1212 target = {
1213 module: newTarget.module,
1214 export: newTarget.export
1215 ? newTarget.export.concat(target.export.slice(1))
1216 : target.export.slice(1)
1217 };
1218 }
1219 }
1220 }
1221
1222 /**
1223 * @param {ModuleGraph} moduleGraph the module graph
1224 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1225 * @returns {{ module: Module, export: string[] | undefined } | undefined} the target
1226 */
1227 getTarget(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
1228 const result = this._getTarget(moduleGraph, resolveTargetFilter, undefined);
1229 if (result === CIRCULAR) return undefined;
1230 return result;
1231 }
1232
1233 /**
1234 * @param {ModuleGraph} moduleGraph the module graph
1235 * @param {function({ module: Module, connection: ModuleGraphConnection, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1236 * @param {Set<ExportInfo> | undefined} alreadyVisited set of already visited export info to avoid circular references
1237 * @returns {{ module: Module, connection: ModuleGraphConnection, export: string[] | undefined } | CIRCULAR | undefined} the target
1238 */
1239 _getTarget(moduleGraph, resolveTargetFilter, alreadyVisited) {
1240 /**
1241 * @param {{ connection: ModuleGraphConnection, export: string[] | undefined } | null} inputTarget unresolved target
1242 * @param {Set<ExportInfo>} alreadyVisited set of already visited export info to avoid circular references
1243 * @returns {{ module: Module, connection: ModuleGraphConnection, export: string[] | undefined } | CIRCULAR | null} resolved target
1244 */
1245 const resolveTarget = (inputTarget, alreadyVisited) => {
1246 if (!inputTarget) return null;
1247 if (!inputTarget.export) {
1248 return {
1249 module: inputTarget.connection.module,
1250 connection: inputTarget.connection,
1251 export: undefined
1252 };
1253 }
1254 /** @type {{ module: Module, connection: ModuleGraphConnection, export: string[] | undefined }} */
1255 let target = {
1256 module: inputTarget.connection.module,
1257 connection: inputTarget.connection,
1258 export: inputTarget.export
1259 };
1260 if (!resolveTargetFilter(target)) return target;
1261 let alreadyVisitedOwned = false;
1262 for (;;) {
1263 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1264 const exportInfo = exportsInfo.getExportInfo(target.export[0]);
1265 if (!exportInfo) return target;
1266 if (alreadyVisited.has(exportInfo)) return CIRCULAR;
1267 const newTarget = exportInfo._getTarget(
1268 moduleGraph,
1269 resolveTargetFilter,
1270 alreadyVisited
1271 );
1272 if (newTarget === CIRCULAR) return CIRCULAR;
1273 if (!newTarget) return target;
1274 if (target.export.length === 1) {
1275 target = newTarget;
1276 if (!target.export) return target;
1277 } else {
1278 target = {
1279 module: newTarget.module,
1280 connection: newTarget.connection,
1281 export: newTarget.export
1282 ? newTarget.export.concat(target.export.slice(1))
1283 : target.export.slice(1)
1284 };
1285 }
1286 if (!resolveTargetFilter(target)) return target;
1287 if (!alreadyVisitedOwned) {
1288 alreadyVisited = new Set(alreadyVisited);
1289 alreadyVisitedOwned = true;
1290 }
1291 alreadyVisited.add(exportInfo);
1292 }
1293 };
1294
1295 if (!this._target || this._target.size === 0) return undefined;
1296 if (alreadyVisited && alreadyVisited.has(this)) return CIRCULAR;
1297 const newAlreadyVisited = new Set(alreadyVisited);
1298 newAlreadyVisited.add(this);
1299 const values = this._target.values();
1300 const target = resolveTarget(values.next().value, newAlreadyVisited);
1301 if (target === CIRCULAR) return CIRCULAR;
1302 if (target === null) return undefined;
1303 let result = values.next();
1304 while (!result.done) {
1305 const t = resolveTarget(result.value, newAlreadyVisited);
1306 if (t === CIRCULAR) return CIRCULAR;
1307 if (t === null) return undefined;
1308 if (t.module !== target.module) return undefined;
1309 if (!t.export !== !target.export) return undefined;
1310 if (target.export && !equals(t.export, target.export)) return undefined;
1311 result = values.next();
1312 }
1313 return target;
1314 }
1315
1316 /**
1317 * Move the target forward as long resolveTargetFilter is fulfilled
1318 * @param {ModuleGraph} moduleGraph the module graph
1319 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1320 * @param {function({ module: Module, export: string[] | undefined }): ModuleGraphConnection=} updateOriginalConnection updates the original connection instead of using the target connection
1321 * @returns {{ module: Module, export: string[] | undefined } | undefined} the resolved target when moved
1322 */
1323 moveTarget(moduleGraph, resolveTargetFilter, updateOriginalConnection) {
1324 const target = this._getTarget(moduleGraph, resolveTargetFilter, undefined);
1325 if (target === CIRCULAR) return undefined;
1326 if (!target) return undefined;
1327 const originalTarget = this._target.values().next().value;
1328 if (
1329 originalTarget.connection === target.connection &&
1330 originalTarget.export === target.export
1331 ) {
1332 return undefined;
1333 }
1334 this._target.clear();
1335 this._target.set(undefined, {
1336 connection: updateOriginalConnection
1337 ? updateOriginalConnection(target)
1338 : target.connection,
1339 export: target.export
1340 });
1341 return target;
1342 }
1343
1344 createNestedExportsInfo() {
1345 if (this.exportsInfoOwned) return this.exportsInfo;
1346 this.exportsInfoOwned = true;
1347 const oldExportsInfo = this.exportsInfo;
1348 this.exportsInfo = new ExportsInfo();
1349 this.exportsInfo.setHasProvideInfo();
1350 if (oldExportsInfo) {
1351 this.exportsInfo.setRedirectNamedTo(oldExportsInfo);
1352 }
1353 return this.exportsInfo;
1354 }
1355
1356 getNestedExportsInfo() {
1357 return this.exportsInfo;
1358 }
1359
1360 hasInfo(baseInfo, runtime) {
1361 return (
1362 (this._usedName && this._usedName !== this.name) ||
1363 this.provided ||
1364 this.terminalBinding ||
1365 this.getUsed(runtime) !== baseInfo.getUsed(runtime)
1366 );
1367 }
1368
1369 updateHash(hash, runtime) {
1370 this._updateHash(hash, runtime, new Set());
1371 }
1372
1373 _updateHash(hash, runtime, alreadyVisitedExportsInfo) {
1374 hash.update(`${this._usedName || this.name}`);
1375 hash.update(`${this.getUsed(runtime)}`);
1376 hash.update(`${this.provided}`);
1377 hash.update(`${this.terminalBinding}`);
1378 if (this.exportsInfo && !alreadyVisitedExportsInfo.has(this.exportsInfo)) {
1379 this.exportsInfo._updateHash(hash, runtime, alreadyVisitedExportsInfo);
1380 }
1381 }
1382
1383 getUsedInfo() {
1384 if (this._globalUsed !== undefined) {
1385 switch (this._globalUsed) {
1386 case UsageState.Unused:
1387 return "unused";
1388 case UsageState.NoInfo:
1389 return "no usage info";
1390 case UsageState.Unknown:
1391 return "maybe used (runtime-defined)";
1392 case UsageState.Used:
1393 return "used";
1394 case UsageState.OnlyPropertiesUsed:
1395 return "only properties used";
1396 }
1397 } else if (this._usedInRuntime !== undefined) {
1398 /** @type {Map<RuntimeUsageStateType, string[]>} */
1399 const map = new Map();
1400 for (const [runtime, used] of this._usedInRuntime) {
1401 const list = map.get(used);
1402 if (list !== undefined) list.push(runtime);
1403 else map.set(used, [runtime]);
1404 }
1405 const specificInfo = Array.from(map, ([used, runtimes]) => {
1406 switch (used) {
1407 case UsageState.NoInfo:
1408 return `no usage info in ${runtimes.join(", ")}`;
1409 case UsageState.Unknown:
1410 return `maybe used in ${runtimes.join(", ")} (runtime-defined)`;
1411 case UsageState.Used:
1412 return `used in ${runtimes.join(", ")}`;
1413 case UsageState.OnlyPropertiesUsed:
1414 return `only properties used in ${runtimes.join(", ")}`;
1415 }
1416 });
1417 if (specificInfo.length > 0) {
1418 return specificInfo.join("; ");
1419 }
1420 }
1421 return this._hasUseInRuntimeInfo ? "unused" : "no usage info";
1422 }
1423
1424 getProvidedInfo() {
1425 switch (this.provided) {
1426 case undefined:
1427 return "no provided info";
1428 case null:
1429 return "maybe provided (runtime-defined)";
1430 case true:
1431 return "provided";
1432 case false:
1433 return "not provided";
1434 }
1435 }
1436
1437 getRenameInfo() {
1438 if (this._usedName !== null && this._usedName !== this.name) {
1439 return `renamed to ${JSON.stringify(this._usedName).slice(1, -1)}`;
1440 }
1441 switch (this.canMangleProvide) {
1442 case undefined:
1443 switch (this.canMangleUse) {
1444 case undefined:
1445 return "missing provision and use info prevents renaming";
1446 case false:
1447 return "usage prevents renaming (no provision info)";
1448 case true:
1449 return "missing provision info prevents renaming";
1450 }
1451 break;
1452 case true:
1453 switch (this.canMangleUse) {
1454 case undefined:
1455 return "missing usage info prevents renaming";
1456 case false:
1457 return "usage prevents renaming";
1458 case true:
1459 return "could be renamed";
1460 }
1461 break;
1462 case false:
1463 switch (this.canMangleUse) {
1464 case undefined:
1465 return "provision prevents renaming (no use info)";
1466 case false:
1467 return "usage and provision prevents renaming";
1468 case true:
1469 return "provision prevents renaming";
1470 }
1471 break;
1472 }
1473 throw new Error(
1474 `Unexpected flags for getRenameInfo ${this.canMangleProvide} ${this.canMangleUse}`
1475 );
1476 }
1477}
1478
1479module.exports = ExportsInfo;
1480module.exports.ExportInfo = ExportInfo;
1481module.exports.UsageState = UsageState;