MangleExportsPlugin.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { UsageState } = require("../ExportsInfo");
  7. const {
  8. numberToIdentifier,
  9. NUMBER_OF_IDENTIFIER_START_CHARS,
  10. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
  11. } = require("../Template");
  12. const { assignDeterministicIds } = require("../ids/IdHelpers");
  13. const { compareSelect, compareStringsNumeric } = require("../util/comparators");
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../ExportsInfo")} ExportsInfo */
  16. /** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
  17. /**
  18. * @param {ExportsInfo} exportsInfo exports info
  19. * @returns {boolean} mangle is possible
  20. */
  21. const canMangle = exportsInfo => {
  22. if (exportsInfo.otherExportsInfo.getUsed(undefined) !== UsageState.Unused)
  23. return false;
  24. let hasSomethingToMangle = false;
  25. for (const exportInfo of exportsInfo.exports) {
  26. if (exportInfo.canMangle === true) {
  27. hasSomethingToMangle = true;
  28. }
  29. }
  30. return hasSomethingToMangle;
  31. };
  32. // Sort by name
  33. const comparator = compareSelect(e => e.name, compareStringsNumeric);
  34. /**
  35. * @param {boolean} deterministic use deterministic names
  36. * @param {ExportsInfo} exportsInfo exports info
  37. * @param {boolean} isNamespace is namespace object
  38. * @returns {void}
  39. */
  40. const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => {
  41. if (!canMangle(exportsInfo)) return;
  42. const usedNames = new Set();
  43. /** @type {ExportInfo[]} */
  44. const mangleableExports = [];
  45. // Avoid to renamed exports that are not provided when
  46. // 1. it's not a namespace export: non-provided exports can be found in prototype chain
  47. // 2. there are other provided exports and deterministic mode is chosen:
  48. // non-provided exports would break the determinism
  49. let avoidMangleNonProvided = !isNamespace;
  50. if (!avoidMangleNonProvided && deterministic) {
  51. for (const exportInfo of exportsInfo.ownedExports) {
  52. if (exportInfo.provided !== false) {
  53. avoidMangleNonProvided = true;
  54. break;
  55. }
  56. }
  57. }
  58. for (const exportInfo of exportsInfo.ownedExports) {
  59. const name = exportInfo.name;
  60. if (!exportInfo.hasUsedName()) {
  61. if (
  62. // Can the export be mangled?
  63. exportInfo.canMangle !== true ||
  64. // Never rename 1 char exports
  65. (name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
  66. // Don't rename 2 char exports in deterministic mode
  67. (deterministic &&
  68. name.length === 2 &&
  69. /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
  70. // Don't rename exports that are not provided
  71. (avoidMangleNonProvided && exportInfo.provided !== true)
  72. ) {
  73. exportInfo.setUsedName(name);
  74. usedNames.add(name);
  75. } else {
  76. mangleableExports.push(exportInfo);
  77. }
  78. }
  79. if (exportInfo.exportsInfoOwned) {
  80. const used = exportInfo.getUsed(undefined);
  81. if (
  82. used === UsageState.OnlyPropertiesUsed ||
  83. used === UsageState.Unused
  84. ) {
  85. mangleExportsInfo(deterministic, exportInfo.exportsInfo, false);
  86. }
  87. }
  88. }
  89. if (deterministic) {
  90. assignDeterministicIds(
  91. mangleableExports,
  92. e => e.name,
  93. comparator,
  94. (e, id) => {
  95. const name = numberToIdentifier(id);
  96. const size = usedNames.size;
  97. usedNames.add(name);
  98. if (size === usedNames.size) return false;
  99. e.setUsedName(name);
  100. return true;
  101. },
  102. [
  103. NUMBER_OF_IDENTIFIER_START_CHARS,
  104. NUMBER_OF_IDENTIFIER_START_CHARS *
  105. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
  106. ],
  107. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
  108. usedNames.size
  109. );
  110. } else {
  111. const usedExports = [];
  112. const unusedExports = [];
  113. for (const exportInfo of mangleableExports) {
  114. if (exportInfo.getUsed(undefined) === UsageState.Unused) {
  115. unusedExports.push(exportInfo);
  116. } else {
  117. usedExports.push(exportInfo);
  118. }
  119. }
  120. usedExports.sort(comparator);
  121. unusedExports.sort(comparator);
  122. let i = 0;
  123. for (const list of [usedExports, unusedExports]) {
  124. for (const exportInfo of list) {
  125. let name;
  126. do {
  127. name = numberToIdentifier(i++);
  128. } while (usedNames.has(name));
  129. exportInfo.setUsedName(name);
  130. }
  131. }
  132. }
  133. };
  134. class MangleExportsPlugin {
  135. /**
  136. * @param {boolean} deterministic use deterministic names
  137. */
  138. constructor(deterministic) {
  139. this._deterministic = deterministic;
  140. }
  141. /**
  142. * Apply the plugin
  143. * @param {Compiler} compiler the compiler instance
  144. * @returns {void}
  145. */
  146. apply(compiler) {
  147. const { _deterministic: deterministic } = this;
  148. compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => {
  149. const moduleGraph = compilation.moduleGraph;
  150. compilation.hooks.optimizeCodeGeneration.tap(
  151. "MangleExportsPlugin",
  152. modules => {
  153. if (compilation.moduleMemCaches) {
  154. throw new Error(
  155. "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
  156. );
  157. }
  158. for (const module of modules) {
  159. const isNamespace =
  160. module.buildMeta && module.buildMeta.exportsType === "namespace";
  161. const exportsInfo = moduleGraph.getExportsInfo(module);
  162. mangleExportsInfo(deterministic, exportsInfo, isNamespace);
  163. }
  164. }
  165. );
  166. });
  167. }
  168. }
  169. module.exports = MangleExportsPlugin;