HarmonyExportInitFragment.js 4.4 KB

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