MangleExportsPlugin.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
  9. NUMBER_OF_IDENTIFIER_START_CHARS,
  10. numberToIdentifier
  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. }
  25. let hasSomethingToMangle = false;
  26. for (const exportInfo of exportsInfo.exports) {
  27. if (exportInfo.canMangle === true) {
  28. hasSomethingToMangle = true;
  29. }
  30. }
  31. return hasSomethingToMangle;
  32. };
  33. // Sort by name
  34. const comparator = compareSelect(e => e.name, compareStringsNumeric);
  35. /**
  36. * @param {boolean} deterministic use deterministic names
  37. * @param {ExportsInfo} exportsInfo exports info
  38. * @param {boolean | undefined} isNamespace is namespace object
  39. * @returns {void}
  40. */
  41. const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => {
  42. if (!canMangle(exportsInfo)) return;
  43. const usedNames = new Set();
  44. /** @type {ExportInfo[]} */
  45. const mangleableExports = [];
  46. // Avoid to renamed exports that are not provided when
  47. // 1. it's not a namespace export: non-provided exports can be found in prototype chain
  48. // 2. there are other provided exports and deterministic mode is chosen:
  49. // non-provided exports would break the determinism
  50. let avoidMangleNonProvided = !isNamespace;
  51. if (!avoidMangleNonProvided && deterministic) {
  52. for (const exportInfo of exportsInfo.ownedExports) {
  53. if (exportInfo.provided !== false) {
  54. avoidMangleNonProvided = true;
  55. break;
  56. }
  57. }
  58. }
  59. for (const exportInfo of exportsInfo.ownedExports) {
  60. const name = exportInfo.name;
  61. if (!exportInfo.hasUsedName()) {
  62. if (
  63. // Can the export be mangled?
  64. exportInfo.canMangle !== true ||
  65. // Never rename 1 char exports
  66. (name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
  67. // Don't rename 2 char exports in deterministic mode
  68. (deterministic &&
  69. name.length === 2 &&
  70. /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
  71. // Don't rename exports that are not provided
  72. (avoidMangleNonProvided && exportInfo.provided !== true)
  73. ) {
  74. exportInfo.setUsedName(name);
  75. usedNames.add(name);
  76. } else {
  77. mangleableExports.push(exportInfo);
  78. }
  79. }
  80. if (exportInfo.exportsInfoOwned) {
  81. const used = exportInfo.getUsed(undefined);
  82. if (
  83. used === UsageState.OnlyPropertiesUsed ||
  84. used === UsageState.Unused
  85. ) {
  86. mangleExportsInfo(
  87. deterministic,
  88. /** @type {ExportsInfo} */ (exportInfo.exportsInfo),
  89. false
  90. );
  91. }
  92. }
  93. }
  94. if (deterministic) {
  95. assignDeterministicIds(
  96. mangleableExports,
  97. e => e.name,
  98. comparator,
  99. (e, id) => {
  100. const name = numberToIdentifier(id);
  101. const size = usedNames.size;
  102. usedNames.add(name);
  103. if (size === usedNames.size) return false;
  104. e.setUsedName(name);
  105. return true;
  106. },
  107. [
  108. NUMBER_OF_IDENTIFIER_START_CHARS,
  109. NUMBER_OF_IDENTIFIER_START_CHARS *
  110. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
  111. ],
  112. NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
  113. usedNames.size
  114. );
  115. } else {
  116. const usedExports = [];
  117. const unusedExports = [];
  118. for (const exportInfo of mangleableExports) {
  119. if (exportInfo.getUsed(undefined) === UsageState.Unused) {
  120. unusedExports.push(exportInfo);
  121. } else {
  122. usedExports.push(exportInfo);
  123. }
  124. }
  125. usedExports.sort(comparator);
  126. unusedExports.sort(comparator);
  127. let i = 0;
  128. for (const list of [usedExports, unusedExports]) {
  129. for (const exportInfo of list) {
  130. let name;
  131. do {
  132. name = numberToIdentifier(i++);
  133. } while (usedNames.has(name));
  134. exportInfo.setUsedName(name);
  135. }
  136. }
  137. }
  138. };
  139. const PLUGIN_NAME = "MangleExportsPlugin";
  140. class MangleExportsPlugin {
  141. /**
  142. * @param {boolean} deterministic use deterministic names
  143. */
  144. constructor(deterministic) {
  145. this._deterministic = deterministic;
  146. }
  147. /**
  148. * Apply the plugin
  149. * @param {Compiler} compiler the compiler instance
  150. * @returns {void}
  151. */
  152. apply(compiler) {
  153. const { _deterministic: deterministic } = this;
  154. compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
  155. const moduleGraph = compilation.moduleGraph;
  156. compilation.hooks.optimizeCodeGeneration.tap(PLUGIN_NAME, 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. module.exports = MangleExportsPlugin;