MangleExportsPlugin.js 5.6 KB

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