MangleExportsPlugin.js 5.6 KB

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