HarmonyExportInitFragment.js 5.2 KB

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