MangleExportsPlugin.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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 | undefined} 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(
  86. deterministic,
  87. /** @type {ExportsInfo} */ (exportInfo.exportsInfo),
  88. false
  89. );
  90. }
  91. }
  92. }
  93. if (deterministic) {
  94. assignDeterministicIds(
  95. mangleableExports,
  96. e => e.name,
  97. comparator,
  98. (e, id) => {
  99. const name = numberToIdentifier(id);
  100. const size = usedNames.size;
  101. usedNames.add(name);
  102. if (size === usedNames.size) return false;
  103. e.setUsedName(name);
  104. return true;
  105. },
  106. [
  107. NUMBER_OF_IDENTIFIER_START_CHARS,
  108. NUMBER_OF_IDENTIFIER_START_CHARS *
  109. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
  110. ],
  111. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
  112. usedNames.size
  113. );
  114. } else {
  115. const usedExports = [];
  116. const unusedExports = [];
  117. for (const exportInfo of mangleableExports) {
  118. if (exportInfo.getUsed(undefined) === UsageState.Unused) {
  119. unusedExports.push(exportInfo);
  120. } else {
  121. usedExports.push(exportInfo);
  122. }
  123. }
  124. usedExports.sort(comparator);
  125. unusedExports.sort(comparator);
  126. let i = 0;
  127. for (const list of [usedExports, unusedExports]) {
  128. for (const exportInfo of list) {
  129. let name;
  130. do {
  131. name = numberToIdentifier(i++);
  132. } while (usedNames.has(name));
  133. exportInfo.setUsedName(name);
  134. }
  135. }
  136. }
  137. };
  138. class MangleExportsPlugin {
  139. /**
  140. * @param {boolean} deterministic use deterministic names
  141. */
  142. constructor(deterministic) {
  143. this._deterministic = deterministic;
  144. }
  145. /**
  146. * Apply the plugin
  147. * @param {Compiler} compiler the compiler instance
  148. * @returns {void}
  149. */
  150. apply(compiler) {
  151. const { _deterministic: deterministic } = this;
  152. compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => {
  153. const moduleGraph = compilation.moduleGraph;
  154. compilation.hooks.optimizeCodeGeneration.tap(
  155. "MangleExportsPlugin",
  156. modules => {
  157. if (compilation.moduleMemCaches) {
  158. throw new Error(
  159. "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
  160. );
  161. }
  162. for (const module of modules) {
  163. const isNamespace =
  164. module.buildMeta && module.buildMeta.exportsType === "namespace";
  165. const exportsInfo = moduleGraph.getExportsInfo(module);
  166. mangleExportsInfo(deterministic, exportsInfo, isNamespace);
  167. }
  168. }
  169. );
  170. });
  171. }
  172. }
  173. module.exports = MangleExportsPlugin;