MangleExportsPlugin.js 5.4 KB

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