HarmonyExportInitFragment.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const InitFragment = require("../InitFragment");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const { first } = require("../util/SetHelpers");
  9. const { propertyName } = require("../util/propertyName");
  10. /** @typedef {import("webpack-sources").Source} Source */
  11. /** @typedef {import("../Generator").GenerateContext} GenerateContext */
  12. const joinIterableWithComma = iterable => {
  13. // This is more performant than Array.from().join(", ")
  14. // as it doesn't create an array
  15. let str = "";
  16. let first = true;
  17. for (const item of iterable) {
  18. if (first) {
  19. first = false;
  20. } else {
  21. str += ", ";
  22. }
  23. str += item;
  24. }
  25. return str;
  26. };
  27. const EMPTY_MAP = new Map();
  28. const EMPTY_SET = new Set();
  29. /**
  30. * @typedef {GenerateContext} Context
  31. */
  32. class HarmonyExportInitFragment extends InitFragment {
  33. /**
  34. * @param {string} exportsArgument the exports identifier
  35. * @param {Map<string, string>} exportMap mapping from used name to exposed variable name
  36. * @param {Set<string>} unusedExports list of unused export names
  37. */
  38. constructor(
  39. exportsArgument,
  40. exportMap = EMPTY_MAP,
  41. unusedExports = EMPTY_SET
  42. ) {
  43. super(undefined, InitFragment.STAGE_HARMONY_EXPORTS, 1, "harmony-exports");
  44. this.exportsArgument = exportsArgument;
  45. this.exportMap = exportMap;
  46. this.unusedExports = unusedExports;
  47. }
  48. /**
  49. * @param {HarmonyExportInitFragment[]} fragments all fragments to merge
  50. * @returns {HarmonyExportInitFragment} merged fragment
  51. */
  52. mergeAll(fragments) {
  53. let exportMap;
  54. let exportMapOwned = false;
  55. let unusedExports;
  56. let unusedExportsOwned = false;
  57. for (const fragment of fragments) {
  58. if (fragment.exportMap.size !== 0) {
  59. if (exportMap === undefined) {
  60. exportMap = fragment.exportMap;
  61. exportMapOwned = false;
  62. } else {
  63. if (!exportMapOwned) {
  64. exportMap = new Map(exportMap);
  65. exportMapOwned = true;
  66. }
  67. for (const [key, value] of fragment.exportMap) {
  68. if (!exportMap.has(key)) exportMap.set(key, value);
  69. }
  70. }
  71. }
  72. if (fragment.unusedExports.size !== 0) {
  73. if (unusedExports === undefined) {
  74. unusedExports = fragment.unusedExports;
  75. unusedExportsOwned = false;
  76. } else {
  77. if (!unusedExportsOwned) {
  78. unusedExports = new Set(unusedExports);
  79. unusedExportsOwned = true;
  80. }
  81. for (const value of fragment.unusedExports) {
  82. unusedExports.add(value);
  83. }
  84. }
  85. }
  86. }
  87. return new HarmonyExportInitFragment(
  88. this.exportsArgument,
  89. exportMap,
  90. unusedExports
  91. );
  92. }
  93. merge(other) {
  94. let exportMap;
  95. if (this.exportMap.size === 0) {
  96. exportMap = other.exportMap;
  97. } else if (other.exportMap.size === 0) {
  98. exportMap = this.exportMap;
  99. } else {
  100. exportMap = new Map(other.exportMap);
  101. for (const [key, value] of this.exportMap) {
  102. if (!exportMap.has(key)) exportMap.set(key, value);
  103. }
  104. }
  105. let unusedExports;
  106. if (this.unusedExports.size === 0) {
  107. unusedExports = other.unusedExports;
  108. } else if (other.unusedExports.size === 0) {
  109. unusedExports = this.unusedExports;
  110. } else {
  111. unusedExports = new Set(other.unusedExports);
  112. for (const value of this.unusedExports) {
  113. unusedExports.add(value);
  114. }
  115. }
  116. return new HarmonyExportInitFragment(
  117. this.exportsArgument,
  118. exportMap,
  119. unusedExports
  120. );
  121. }
  122. /**
  123. * @param {Context} context context
  124. * @returns {string|Source} the source code that will be included as initialization code
  125. */
  126. getContent({ runtimeTemplate, runtimeRequirements }) {
  127. runtimeRequirements.add(RuntimeGlobals.exports);
  128. runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
  129. const unusedPart =
  130. this.unusedExports.size > 1
  131. ? `/* unused harmony exports ${joinIterableWithComma(
  132. this.unusedExports
  133. )} */\n`
  134. : this.unusedExports.size > 0
  135. ? `/* unused harmony export ${first(this.unusedExports)} */\n`
  136. : "";
  137. const definitions = [];
  138. const orderedExportMap = Array.from(this.exportMap).sort(([a], [b]) =>
  139. a < b ? -1 : 1
  140. );
  141. for (const [key, value] of orderedExportMap) {
  142. definitions.push(
  143. `\n/* harmony export */ ${propertyName(
  144. key
  145. )}: ${runtimeTemplate.returningFunction(value)}`
  146. );
  147. }
  148. const definePart =
  149. this.exportMap.size > 0
  150. ? `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${
  151. this.exportsArgument
  152. }, {${definitions.join(",")}\n/* harmony export */ });\n`
  153. : "";
  154. return `${definePart}${unusedPart}`;
  155. }
  156. }
  157. module.exports = HarmonyExportInitFragment;