UNPKG

39 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.setUsed(UsageState.Used, runtime)) {
399 changed = true;
400 }
401 }
402 return changed;
403 }
404
405 /**
406 * @param {RuntimeSpec} runtime the runtime
407 * @returns {boolean} true, when something changed
408 */
409 setUsedForSideEffectsOnly(runtime) {
410 return this._sideEffectsOnlyInfo.setUsedConditionally(
411 used => used === UsageState.Unused,
412 UsageState.Used,
413 runtime
414 );
415 }
416
417 /**
418 * @param {RuntimeSpec} runtime the runtime
419 * @returns {boolean} true, when the module exports are used in any way
420 */
421 isUsed(runtime) {
422 if (this._redirectTo !== undefined) {
423 if (this._redirectTo.isUsed(runtime)) {
424 return true;
425 }
426 } else {
427 if (this._otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
428 return true;
429 }
430 }
431 for (const exportInfo of this._exports.values()) {
432 if (exportInfo.getUsed(runtime) !== UsageState.Unused) {
433 return true;
434 }
435 }
436 return false;
437 }
438
439 /**
440 * @param {RuntimeSpec} runtime the runtime
441 * @returns {boolean} true, when the module is used in any way
442 */
443 isModuleUsed(runtime) {
444 if (this.isUsed(runtime)) return true;
445 if (this._sideEffectsOnlyInfo.getUsed(runtime) !== UsageState.Unused)
446 return true;
447 return false;
448 }
449
450 /**
451 * @param {RuntimeSpec} runtime the runtime
452 * @returns {SortableSet<string> | boolean | null} set of used exports, or true (when namespace object is used), or false (when unused), or null (when unknown)
453 */
454 getUsedExports(runtime) {
455 if (!this._redirectTo !== undefined) {
456 switch (this._otherExportsInfo.getUsed(runtime)) {
457 case UsageState.NoInfo:
458 return null;
459 case UsageState.Unknown:
460 return true;
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 for (const exportInfo of this.orderedExports) {
689 exportInfo.updateHash(hash, runtime);
690 }
691 this._sideEffectsOnlyInfo.updateHash(hash, runtime);
692 this._otherExportsInfo.updateHash(hash, runtime);
693 if (this._redirectTo !== undefined) {
694 this._redirectTo.updateHash(hash, runtime);
695 }
696 }
697
698 getRestoreProvidedData() {
699 const otherProvided = this._otherExportsInfo.provided;
700 const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide;
701 const otherTerminalBinding = this._otherExportsInfo.terminalBinding;
702 const exports = [];
703 for (const exportInfo of this._exports.values()) {
704 if (
705 exportInfo.provided !== otherProvided ||
706 exportInfo.canMangleProvide !== otherCanMangleProvide ||
707 exportInfo.terminalBinding !== otherTerminalBinding ||
708 exportInfo.exportsInfoOwned
709 ) {
710 exports.push({
711 name: exportInfo.name,
712 provided: exportInfo.provided,
713 canMangleProvide: exportInfo.canMangleProvide,
714 terminalBinding: exportInfo.terminalBinding,
715 exportsInfo: exportInfo.exportsInfoOwned
716 ? exportInfo.exportsInfo.getRestoreProvidedData()
717 : undefined
718 });
719 }
720 }
721 return new RestoreProvidedData(
722 exports,
723 otherProvided,
724 otherCanMangleProvide,
725 otherTerminalBinding
726 );
727 }
728
729 restoreProvided({
730 otherProvided,
731 otherCanMangleProvide,
732 otherTerminalBinding,
733 exports
734 }) {
735 for (const exportInfo of this._exports.values()) {
736 exportInfo.provided = otherProvided;
737 exportInfo.canMangleProvide = otherCanMangleProvide;
738 exportInfo.terminalBinding = otherTerminalBinding;
739 }
740 this._otherExportsInfo.provided = otherProvided;
741 this._otherExportsInfo.canMangleProvide = otherCanMangleProvide;
742 this._otherExportsInfo.terminalBinding = otherTerminalBinding;
743 for (const exp of exports) {
744 const exportInfo = this.getExportInfo(exp.name);
745 exportInfo.provided = exp.provided;
746 exportInfo.canMangleProvide = exp.canMangleProvide;
747 exportInfo.terminalBinding = exp.terminalBinding;
748 if (exp.exportsInfo) {
749 const exportsInfo = exportInfo.createNestedExportsInfo();
750 exportsInfo.restoreProvided(exp.exportsInfo);
751 }
752 }
753 }
754}
755
756class ExportInfo {
757 /**
758 * @param {string} name the original name of the export
759 * @param {ExportInfo=} initFrom init values from this ExportInfo
760 */
761 constructor(name, initFrom) {
762 /** @type {string} */
763 this.name = name;
764 /** @private @type {string | null} */
765 this._usedName = initFrom ? initFrom._usedName : null;
766 /** @private @type {Map<string, RuntimeUsageStateType>} */
767 this._usedInRuntime =
768 initFrom && initFrom._usedInRuntime
769 ? new Map(initFrom._usedInRuntime)
770 : undefined;
771 /** @private @type {boolean} */
772 this._hasUseInRuntimeInfo = initFrom
773 ? initFrom._hasUseInRuntimeInfo
774 : false;
775 /**
776 * true: it is provided
777 * false: it is not provided
778 * null: only the runtime knows if it is provided
779 * undefined: it was not determined if it is provided
780 * @type {boolean | null | undefined}
781 */
782 this.provided = initFrom ? initFrom.provided : undefined;
783 /**
784 * is the export a terminal binding that should be checked for export star conflicts
785 * @type {boolean}
786 */
787 this.terminalBinding = initFrom ? initFrom.terminalBinding : false;
788 /**
789 * true: it can be mangled
790 * false: is can not be mangled
791 * undefined: it was not determined if it can be mangled
792 * @type {boolean | undefined}
793 */
794 this.canMangleProvide = initFrom ? initFrom.canMangleProvide : undefined;
795 /**
796 * true: it can be mangled
797 * false: is can not be mangled
798 * undefined: it was not determined if it can be mangled
799 * @type {boolean | undefined}
800 */
801 this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
802 /** @type {boolean} */
803 this.exportsInfoOwned = false;
804 /** @type {ExportsInfo=} */
805 this.exportsInfo = undefined;
806 /** @type {Map<any, { connection: ModuleGraphConnection, export: string[] } | null>=} */
807 this._target = undefined;
808 if (initFrom && initFrom._target) {
809 this._target = new Map();
810 for (const [key, value] of initFrom._target) {
811 this._target.set(
812 key,
813 value ? { connection: value.connection, export: [name] } : null
814 );
815 }
816 }
817 }
818
819 // TODO webpack 5 remove
820 /** @private */
821 get used() {
822 throw new Error("REMOVED");
823 }
824 /** @private */
825 get usedName() {
826 throw new Error("REMOVED");
827 }
828 /**
829 * @private
830 * @param {*} v v
831 */
832 set used(v) {
833 throw new Error("REMOVED");
834 }
835 /**
836 * @private
837 * @param {*} v v
838 */
839 set usedName(v) {
840 throw new Error("REMOVED");
841 }
842
843 get canMangle() {
844 switch (this.canMangleProvide) {
845 case undefined:
846 return this.canMangleUse === false ? false : undefined;
847 case false:
848 return false;
849 case true:
850 switch (this.canMangleUse) {
851 case undefined:
852 return undefined;
853 case false:
854 return false;
855 case true:
856 return true;
857 }
858 }
859 throw new Error(
860 `Unexpected flags for canMangle ${this.canMangleProvide} ${this.canMangleUse}`
861 );
862 }
863
864 /**
865 * @param {RuntimeSpec} runtime only apply to this runtime
866 * @returns {boolean} true, when something changed
867 */
868 setUsedInUnknownWay(runtime) {
869 let changed = false;
870 if (
871 this.setUsedConditionally(
872 used => used < UsageState.Unknown,
873 UsageState.Unknown,
874 runtime
875 )
876 ) {
877 changed = true;
878 }
879 if (this.canMangleUse !== false) {
880 this.canMangleUse = false;
881 changed = true;
882 }
883 return changed;
884 }
885
886 /**
887 * @param {RuntimeSpec} runtime only apply to this runtime
888 * @returns {boolean} true, when something changed
889 */
890 setUsedWithoutInfo(runtime) {
891 let changed = false;
892 if (this.setUsed(UsageState.NoInfo, runtime)) {
893 changed = true;
894 }
895 if (this.canMangleUse !== false) {
896 this.canMangleUse = false;
897 changed = true;
898 }
899 return changed;
900 }
901
902 setHasUseInfo() {
903 if (!this._hasUseInRuntimeInfo) {
904 this._hasUseInRuntimeInfo = true;
905 }
906 if (this.canMangleUse === undefined) {
907 this.canMangleUse = true;
908 }
909 if (this.exportsInfoOwned) {
910 this.exportsInfo.setHasUseInfo();
911 }
912 }
913
914 /**
915 * @param {function(UsageStateType): boolean} condition compare with old value
916 * @param {UsageStateType} newValue set when condition is true
917 * @param {RuntimeSpec} runtime only apply to this runtime
918 * @returns {boolean} true when something has changed
919 */
920 setUsedConditionally(condition, newValue, runtime) {
921 if (this._usedInRuntime === undefined) {
922 if (newValue !== UsageState.Unused && condition(UsageState.Unused)) {
923 this._usedInRuntime = new Map();
924 forEachRuntime(runtime, runtime =>
925 this._usedInRuntime.set(runtime, newValue)
926 );
927 return true;
928 }
929 } else {
930 let changed = false;
931 forEachRuntime(runtime, runtime => {
932 /** @type {UsageStateType} */
933 let oldValue = this._usedInRuntime.get(runtime);
934 if (oldValue === undefined) oldValue = UsageState.Unused;
935 if (newValue !== oldValue && condition(oldValue)) {
936 if (newValue === UsageState.Unused) {
937 this._usedInRuntime.delete(runtime);
938 } else {
939 this._usedInRuntime.set(runtime, newValue);
940 }
941 changed = true;
942 }
943 });
944 if (changed) {
945 if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
946 return true;
947 }
948 }
949 return false;
950 }
951
952 /**
953 * @param {UsageStateType} newValue new value of the used state
954 * @param {RuntimeSpec} runtime only apply to this runtime
955 * @returns {boolean} true when something has changed
956 */
957 setUsed(newValue, runtime) {
958 if (this._usedInRuntime === undefined) {
959 if (newValue !== UsageState.Unused) {
960 this._usedInRuntime = new Map();
961 forEachRuntime(runtime, runtime =>
962 this._usedInRuntime.set(runtime, newValue)
963 );
964 return true;
965 }
966 } else {
967 let changed = false;
968 forEachRuntime(runtime, runtime => {
969 /** @type {UsageStateType} */
970 let oldValue = this._usedInRuntime.get(runtime);
971 if (oldValue === undefined) oldValue = UsageState.Unused;
972 if (newValue !== oldValue) {
973 if (newValue === UsageState.Unused) {
974 this._usedInRuntime.delete(runtime);
975 } else {
976 this._usedInRuntime.set(runtime, newValue);
977 }
978 changed = true;
979 }
980 });
981 if (changed) {
982 if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
983 return true;
984 }
985 }
986 return false;
987 }
988
989 /**
990 * @param {any} key the key
991 * @param {ModuleGraphConnection=} connection the target module if a single one
992 * @param {string[]=} exportName the exported name
993 * @returns {boolean} true, if something has changed
994 */
995 setTarget(key, connection, exportName) {
996 if (exportName) exportName = [...exportName];
997 if (!this._target) {
998 this._target = new Map();
999 this._target.set(
1000 key,
1001 connection ? { connection, export: exportName } : null
1002 );
1003 return true;
1004 }
1005 const oldTarget = this._target.get(key);
1006 if (!oldTarget) {
1007 if (oldTarget === null && !connection) return false;
1008 this._target.set(
1009 key,
1010 connection ? { connection, export: exportName } : null
1011 );
1012 return true;
1013 }
1014 if (!connection) {
1015 this._target.set(key, null);
1016 return true;
1017 }
1018 if (
1019 oldTarget.connection !== connection ||
1020 (exportName
1021 ? !oldTarget.export || !equals(oldTarget.export, exportName)
1022 : oldTarget.export)
1023 ) {
1024 oldTarget.connection = connection;
1025 oldTarget.export = exportName;
1026 return true;
1027 }
1028 return false;
1029 }
1030
1031 /**
1032 * @param {RuntimeSpec} runtime for this runtime
1033 * @returns {UsageStateType} usage state
1034 */
1035 getUsed(runtime) {
1036 if (!this._hasUseInRuntimeInfo) return UsageState.NoInfo;
1037 if (this._usedInRuntime === undefined) {
1038 return UsageState.Unused;
1039 } else if (typeof runtime === "string") {
1040 const value = this._usedInRuntime.get(runtime);
1041 return value === undefined ? UsageState.Unused : value;
1042 } else if (runtime === undefined) {
1043 /** @type {UsageStateType} */
1044 let max = UsageState.Unused;
1045 for (const value of this._usedInRuntime.values()) {
1046 if (value === UsageState.Used) {
1047 return UsageState.Used;
1048 }
1049 if (max < value) max = value;
1050 }
1051 return max;
1052 } else {
1053 /** @type {UsageStateType} */
1054 let max = UsageState.Unused;
1055 for (const item of runtime) {
1056 const value = this._usedInRuntime.get(item);
1057 if (value !== undefined) {
1058 if (value === UsageState.Used) {
1059 return UsageState.Used;
1060 }
1061 if (max < value) max = value;
1062 }
1063 }
1064 return max;
1065 }
1066 }
1067
1068 /**
1069 * get used name
1070 * @param {string=} fallbackName fallback name for used exports with no name
1071 * @param {RuntimeSpec} runtime check usage for this runtime only
1072 * @returns {string | false} used name
1073 */
1074 getUsedName(fallbackName, runtime) {
1075 if (this._hasUseInRuntimeInfo) {
1076 if (this._usedInRuntime === undefined) return false;
1077 if (typeof runtime === "string") {
1078 if (!this._usedInRuntime.has(runtime)) {
1079 return false;
1080 }
1081 } else if (runtime !== undefined) {
1082 if (
1083 Array.from(runtime).every(
1084 runtime => !this._usedInRuntime.has(runtime)
1085 )
1086 ) {
1087 return false;
1088 }
1089 }
1090 }
1091 if (this._usedName !== null) return this._usedName;
1092 return this.name || fallbackName;
1093 }
1094
1095 /**
1096 * @returns {boolean} true, when a mangled name of this export is set
1097 */
1098 hasUsedName() {
1099 return this._usedName !== null;
1100 }
1101
1102 /**
1103 * Sets the mangled name of this export
1104 * @param {string} name the new name
1105 * @returns {void}
1106 */
1107 setUsedName(name) {
1108 this._usedName = name;
1109 }
1110
1111 /**
1112 * @param {ModuleGraph} moduleGraph the module graph
1113 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1114 * @returns {ExportInfo | ExportsInfo | undefined} the terminal binding export(s) info if known
1115 */
1116 getTerminalBinding(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
1117 if (this.terminalBinding) return this;
1118 const target = this.getTarget(moduleGraph, resolveTargetFilter);
1119 if (!target) return undefined;
1120 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1121 if (!target.export) return exportsInfo;
1122 return exportsInfo.getReadOnlyExportInfoRecursive(target.export);
1123 }
1124
1125 isReexport() {
1126 return !this.terminalBinding && this._target && this._target.size > 0;
1127 }
1128
1129 /**
1130 * @param {ModuleGraph} moduleGraph the module graph
1131 * @param {function(Module): boolean} validTargetModuleFilter a valid target module
1132 * @returns {{ module: Module, export: string[] | undefined } | undefined | false} the target, undefined when there is no target, false when no target is valid
1133 */
1134 findTarget(moduleGraph, validTargetModuleFilter) {
1135 return this._findTarget(moduleGraph, validTargetModuleFilter, new Set());
1136 }
1137
1138 /**
1139 * @param {ModuleGraph} moduleGraph the module graph
1140 * @param {function(Module): boolean} validTargetModuleFilter a valid target module
1141 * @param {Set<ExportInfo> | undefined} alreadyVisited set of already visited export info to avoid circular references
1142 * @returns {{ module: Module, export: string[] | undefined } | undefined | false} the target, undefined when there is no target, false when no target is valid
1143 */
1144 _findTarget(moduleGraph, validTargetModuleFilter, alreadyVisited) {
1145 if (!this._target || this._target.size === 0) return undefined;
1146 let rawTarget = this._target.values().next().value;
1147 if (!rawTarget) return undefined;
1148 /** @type {{ module: Module, export: string[] | undefined }} */
1149 let target = {
1150 module: rawTarget.connection.module,
1151 export: rawTarget.export
1152 };
1153 for (;;) {
1154 if (validTargetModuleFilter(target.module)) return target;
1155 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1156 const exportInfo = exportsInfo.getExportInfo(target.export[0]);
1157 if (alreadyVisited.has(exportInfo)) return null;
1158 const newTarget = exportInfo._findTarget(
1159 moduleGraph,
1160 validTargetModuleFilter,
1161 alreadyVisited
1162 );
1163 if (!newTarget) return false;
1164 if (target.export.length === 1) {
1165 target = newTarget;
1166 } else {
1167 target = {
1168 module: newTarget.module,
1169 export: newTarget.export
1170 ? newTarget.export.concat(target.export.slice(1))
1171 : target.export.slice(1)
1172 };
1173 }
1174 }
1175 }
1176
1177 /**
1178 * @param {ModuleGraph} moduleGraph the module graph
1179 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1180 * @returns {{ module: Module, export: string[] | undefined } | undefined} the target
1181 */
1182 getTarget(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
1183 const result = this._getTarget(moduleGraph, resolveTargetFilter, undefined);
1184 if (result === CIRCULAR) return undefined;
1185 return result;
1186 }
1187
1188 /**
1189 * @param {ModuleGraph} moduleGraph the module graph
1190 * @param {function({ module: Module, export: string[] | undefined }): boolean} resolveTargetFilter filter function to further resolve target
1191 * @param {Set<ExportInfo> | undefined} alreadyVisited set of already visited export info to avoid circular references
1192 * @returns {{ module: Module, export: string[] | undefined } | CIRCULAR | undefined} the target
1193 */
1194 _getTarget(moduleGraph, resolveTargetFilter, alreadyVisited) {
1195 /**
1196 * @param {{ connection: ModuleGraphConnection, export: string[] | undefined } | null} inputTarget unresolved target
1197 * @param {Set<ExportInfo>} alreadyVisited set of already visited export info to avoid circular references
1198 * @returns {{ module: Module, export: string[] | undefined } | CIRCULAR | null} resolved target
1199 */
1200 const resolveTarget = (inputTarget, alreadyVisited) => {
1201 if (!inputTarget) return null;
1202 if (!inputTarget.export) {
1203 return {
1204 module: inputTarget.connection.module,
1205 export: undefined
1206 };
1207 }
1208 /** @type {{ module: Module, export: string[] | undefined }} */
1209 let target = {
1210 module: inputTarget.connection.module,
1211 export: inputTarget.export
1212 };
1213 if (!resolveTargetFilter(target)) return target;
1214 let alreadyVisitedOwned = false;
1215 for (;;) {
1216 const exportsInfo = moduleGraph.getExportsInfo(target.module);
1217 const exportInfo = exportsInfo.getExportInfo(target.export[0]);
1218 if (!exportInfo) return target;
1219 if (alreadyVisited.has(exportInfo)) return CIRCULAR;
1220 const newTarget = exportInfo._getTarget(
1221 moduleGraph,
1222 resolveTargetFilter,
1223 alreadyVisited
1224 );
1225 if (newTarget === CIRCULAR) return CIRCULAR;
1226 if (!newTarget) return target;
1227 if (target.export.length === 1) {
1228 target = newTarget;
1229 if (!target.export) return target;
1230 } else {
1231 target = {
1232 module: newTarget.module,
1233 export: newTarget.export
1234 ? newTarget.export.concat(target.export.slice(1))
1235 : target.export.slice(1)
1236 };
1237 }
1238 if (!resolveTargetFilter(target)) return target;
1239 if (!alreadyVisitedOwned) {
1240 alreadyVisited = new Set(alreadyVisited);
1241 alreadyVisitedOwned = true;
1242 }
1243 alreadyVisited.add(exportInfo);
1244 }
1245 };
1246
1247 if (!this._target || this._target.size === 0) return undefined;
1248 if (alreadyVisited && alreadyVisited.has(this)) return CIRCULAR;
1249 const newAlreadyVisited = new Set(alreadyVisited);
1250 newAlreadyVisited.add(this);
1251 const values = this._target.values();
1252 const target = resolveTarget(values.next().value, newAlreadyVisited);
1253 if (target === CIRCULAR) return CIRCULAR;
1254 if (target === null) return undefined;
1255 if (this._target.size === 1) {
1256 return target;
1257 }
1258 let result = values.next();
1259 while (!result.done) {
1260 const t = resolveTarget(result.value, newAlreadyVisited);
1261 if (t === CIRCULAR) return CIRCULAR;
1262 if (t === null) return undefined;
1263 if (t.module !== target.module) return undefined;
1264 if (!t.export !== !target.export) return undefined;
1265 if (target.export && !equals(t.export, target.export)) return undefined;
1266 result = values.next();
1267 }
1268 return target;
1269 }
1270
1271 createNestedExportsInfo() {
1272 if (this.exportsInfoOwned) return this.exportsInfo;
1273 this.exportsInfoOwned = true;
1274 const oldExportsInfo = this.exportsInfo;
1275 this.exportsInfo = new ExportsInfo();
1276 this.exportsInfo.setHasProvideInfo();
1277 if (oldExportsInfo) {
1278 this.exportsInfo.setRedirectNamedTo(oldExportsInfo);
1279 }
1280 return this.exportsInfo;
1281 }
1282
1283 getNestedExportsInfo() {
1284 return this.exportsInfo;
1285 }
1286
1287 updateHash(hash, runtime) {
1288 hash.update(`${this._usedName || this.name}`);
1289 hash.update(`${this.getUsed(runtime)}`);
1290 hash.update(`${this.provided}`);
1291 hash.update(`${this.canMangle}`);
1292 hash.update(`${this.terminalBinding}`);
1293 }
1294
1295 getUsedInfo() {
1296 if (this._usedInRuntime !== undefined) {
1297 /** @type {Map<RuntimeUsageStateType, string[]>} */
1298 const map = new Map();
1299 for (const [runtime, used] of this._usedInRuntime) {
1300 const list = map.get(used);
1301 if (list !== undefined) list.push(runtime);
1302 else map.set(used, [runtime]);
1303 }
1304 const specificInfo = Array.from(map, ([used, runtimes]) => {
1305 switch (used) {
1306 case UsageState.NoInfo:
1307 return `no usage info in ${runtimes.join(", ")}`;
1308 case UsageState.Unknown:
1309 return `maybe used in ${runtimes.join(", ")} (runtime-defined)`;
1310 case UsageState.Used:
1311 return `used in ${runtimes.join(", ")}`;
1312 case UsageState.OnlyPropertiesUsed:
1313 return `only properties used in ${runtimes.join(", ")}`;
1314 }
1315 });
1316 if (specificInfo.length > 0) {
1317 return specificInfo.join("; ");
1318 }
1319 }
1320 return this._hasUseInRuntimeInfo ? "unused" : "no usage info";
1321 }
1322
1323 getProvidedInfo() {
1324 switch (this.provided) {
1325 case undefined:
1326 return "no provided info";
1327 case null:
1328 return "maybe provided (runtime-defined)";
1329 case true:
1330 return "provided";
1331 case false:
1332 return "not provided";
1333 }
1334 }
1335
1336 getRenameInfo() {
1337 if (this._usedName !== null && this._usedName !== this.name) {
1338 return `renamed to ${JSON.stringify(this._usedName).slice(1, -1)}`;
1339 }
1340 switch (this.canMangleProvide) {
1341 case undefined:
1342 switch (this.canMangleUse) {
1343 case undefined:
1344 return "missing provision and use info prevents renaming";
1345 case false:
1346 return "usage prevents renaming (no provision info)";
1347 case true:
1348 return "missing provision info prevents renaming";
1349 }
1350 break;
1351 case true:
1352 switch (this.canMangleUse) {
1353 case undefined:
1354 return "missing usage info prevents renaming";
1355 case false:
1356 return "usage prevents renaming";
1357 case true:
1358 return "could be renamed";
1359 }
1360 break;
1361 case false:
1362 switch (this.canMangleUse) {
1363 case undefined:
1364 return "provision prevents renaming (no use info)";
1365 case false:
1366 return "usage and provision prevents renaming";
1367 case true:
1368 return "provision prevents renaming";
1369 }
1370 break;
1371 }
1372 throw new Error(
1373 `Unexpected flags for getRenameInfo ${this.canMangleProvide} ${this.canMangleUse}`
1374 );
1375 }
1376}
1377
1378module.exports = ExportsInfo;
1379module.exports.ExportInfo = ExportInfo;
1380module.exports.UsageState = UsageState;