HarmonyExportInitFragment.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. /** @import { Source } from "webpack-sources" */
  11. /** @import { GenerateContext } from "../Generator" */
  12. /** @import { UsedName } from "../ExportsInfo" */
  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. /** @import { OnDemandGeneration } from "./ExportBindingInitFragment" */
  36. /** @type {ExportMap} */
  37. const EMPTY_MAP = new Map();
  38. /** @type {UnusedExports} */
  39. const EMPTY_SET = new Set();
  40. /**
  41. * Represents HarmonyExportInitFragment.
  42. * @extends {InitFragment<GenerateContext>} Context
  43. */
  44. class HarmonyExportInitFragment extends InitFragment {
  45. /**
  46. * Creates an instance of HarmonyExportInitFragment.
  47. * @param {string} exportsArgument the exports identifier
  48. * @param {ExportMap} exportMap mapping from used name to exposed variable name
  49. * @param {UnusedExports} unusedExports list of unused export names
  50. * @param {OnDemandGeneration=} onDemand offered the call instead of emitting it
  51. */
  52. constructor(
  53. exportsArgument,
  54. exportMap = EMPTY_MAP,
  55. unusedExports = EMPTY_SET,
  56. onDemand = undefined
  57. ) {
  58. super(undefined, InitFragment.STAGE_HARMONY_EXPORTS, 1, "harmony-exports");
  59. /** @type {string} */
  60. this.exportsArgument = exportsArgument;
  61. /** @type {ExportMap} */
  62. this.exportMap = exportMap;
  63. /** @type {UnusedExports} */
  64. this.unusedExports = unusedExports;
  65. /** @type {OnDemandGeneration | undefined} */
  66. this.onDemand = onDemand;
  67. }
  68. /**
  69. * Merges the provided values into a single result.
  70. * @param {HarmonyExportInitFragment[]} fragments all fragments to merge
  71. * @returns {HarmonyExportInitFragment} merged fragment
  72. */
  73. mergeAll(fragments) {
  74. /** @type {undefined | ExportMap} */
  75. let exportMap;
  76. let exportMapOwned = false;
  77. /** @type {undefined | UnusedExports} */
  78. let unusedExports;
  79. let unusedExportsOwned = false;
  80. for (const fragment of fragments) {
  81. if (fragment.exportMap.size !== 0) {
  82. if (exportMap === undefined) {
  83. exportMap = fragment.exportMap;
  84. exportMapOwned = false;
  85. } else {
  86. if (!exportMapOwned) {
  87. exportMap = new Map(exportMap);
  88. exportMapOwned = true;
  89. }
  90. for (const [key, value] of fragment.exportMap) {
  91. if (!exportMap.has(key)) exportMap.set(key, value);
  92. }
  93. }
  94. }
  95. if (fragment.unusedExports.size !== 0) {
  96. if (unusedExports === undefined) {
  97. unusedExports = fragment.unusedExports;
  98. unusedExportsOwned = false;
  99. } else {
  100. if (!unusedExportsOwned) {
  101. unusedExports = new Set(unusedExports);
  102. unusedExportsOwned = true;
  103. }
  104. for (const value of fragment.unusedExports) {
  105. unusedExports.add(value);
  106. }
  107. }
  108. }
  109. }
  110. return new HarmonyExportInitFragment(
  111. this.exportsArgument,
  112. exportMap,
  113. unusedExports,
  114. this.onDemand
  115. );
  116. }
  117. /**
  118. * Returns merged result.
  119. * @param {HarmonyExportInitFragment} other other
  120. * @returns {HarmonyExportInitFragment} merged result
  121. */
  122. merge(other) {
  123. /** @type {ExportMap} */
  124. let exportMap;
  125. if (this.exportMap.size === 0) {
  126. exportMap = other.exportMap;
  127. } else if (other.exportMap.size === 0) {
  128. exportMap = this.exportMap;
  129. } else {
  130. exportMap = new Map(other.exportMap);
  131. for (const [key, value] of this.exportMap) {
  132. if (!exportMap.has(key)) exportMap.set(key, value);
  133. }
  134. }
  135. /** @type {UnusedExports} */
  136. let unusedExports;
  137. if (this.unusedExports.size === 0) {
  138. unusedExports = other.unusedExports;
  139. } else if (other.unusedExports.size === 0) {
  140. unusedExports = this.unusedExports;
  141. } else {
  142. unusedExports = new Set(other.unusedExports);
  143. for (const value of this.unusedExports) {
  144. unusedExports.add(value);
  145. }
  146. }
  147. return new HarmonyExportInitFragment(
  148. this.exportsArgument,
  149. exportMap,
  150. unusedExports,
  151. this.onDemand || other.onDemand
  152. );
  153. }
  154. /**
  155. * Returns the source code that will be included as initialization code.
  156. * @param {GenerateContext} context context
  157. * @returns {string | Source | undefined} the source code that will be included as initialization code
  158. */
  159. getContent({ runtimeTemplate, runtimeRequirements }) {
  160. const unusedPart =
  161. this.unusedExports.size > 1
  162. ? `/* unused harmony exports ${joinIterableWithComma(
  163. this.unusedExports
  164. )} */\n`
  165. : this.unusedExports.size > 0
  166. ? `/* unused harmony export ${first(this.unusedExports)} */\n`
  167. : "";
  168. let definePart = "";
  169. if (this.exportMap.size > 0) {
  170. /** @type {string[]} */
  171. const definitions = [];
  172. const orderedExportMap =
  173. this.exportMap.size > 1
  174. ? [...this.exportMap].sort(([a], [b]) => (a < b ? -1 : 1))
  175. : this.exportMap;
  176. for (const [key, value] of orderedExportMap) {
  177. definitions.push(
  178. `\n/* harmony export */ ${propertyName(
  179. /** @type {string} */ (key)
  180. )}: ${runtimeTemplate.returningFunction(value)}`
  181. );
  182. }
  183. // Requested even when the call is taken over below: whoever took it may still
  184. // emit it, and by then the requirement set is closed.
  185. runtimeRequirements.add(RuntimeGlobals.exports);
  186. runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
  187. definePart = `/* harmony export */ ${
  188. RuntimeGlobals.definePropertyGetters
  189. }(${this.exportsArgument}, {${definitions.join(
  190. ","
  191. )}\n/* harmony export */ });\n`;
  192. if (this.onDemand !== undefined && this.onDemand(definePart, false)) {
  193. definePart = "";
  194. }
  195. }
  196. return `${definePart}${unusedPart}`;
  197. }
  198. }
  199. module.exports = HarmonyExportInitFragment;