HarmonyExportInitFragment.js 4.7 KB

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