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