ExportBindingInitFragment.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Natsu @xiaoxiaojx
  4. */
  5. "use strict";
  6. const InitFragment = require("../InitFragment");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. /** @import { Source } from "webpack-sources" */
  9. /** @import { GenerateContext } from "../Generator" */
  10. /** @import { UsedName } from "../ExportsInfo" */
  11. /**
  12. * @typedef {object} ExportBinding
  13. * @property {UsedName} name the used export name
  14. * @property {string} value the variable expression
  15. * @property {"getter" | "value"=} bindingType "value" for value binding, defaults to "getter"
  16. */
  17. /**
  18. * Offered the rendered `d()` call and where it sits, and answers whether it took it
  19. * over. A consumer that does emits the call itself where it is actually read — or
  20. * leaves it out. Value bindings read the declarations above them, so one placed at
  21. * the end cannot be moved before the module body.
  22. * @typedef {(source: string, placeAtEnd: boolean) => boolean} OnDemandGeneration
  23. */
  24. /**
  25. * Init fragment for const export bindings.
  26. * Generates a flat-array __webpack_require__.d() call.
  27. * Getter bindings: "key", () => expr (2 items).
  28. * Value bindings: "key", 0, expr (3 items, 0 is sentinel).
  29. *
  30. * Uses a separate key ("harmony-export-bindings") from HarmonyExportInitFragment
  31. * so the two fragment types do not merge and cannot interfere with each
  32. * other's placement (top vs bottom).
  33. * @extends {InitFragment<GenerateContext>} Context
  34. */
  35. class ExportBindingInitFragment extends InitFragment {
  36. /**
  37. * @param {string} exportsArgument the exports identifier
  38. * @param {ExportBinding[]} bindings list of export bindings
  39. * @param {boolean=} placeAtEnd when true, place the d() call at the end of the module
  40. * @param {OnDemandGeneration=} onDemand offered the call instead of emitting it
  41. */
  42. constructor(
  43. exportsArgument,
  44. bindings,
  45. placeAtEnd = false,
  46. onDemand = undefined
  47. ) {
  48. super(
  49. undefined,
  50. InitFragment.STAGE_HARMONY_EXPORTS,
  51. 1,
  52. "harmony-export-bindings"
  53. );
  54. /** @type {string} */
  55. this.exportsArgument = exportsArgument;
  56. /** @type {ExportBinding[]} */
  57. this.bindings = bindings;
  58. /** @type {boolean} */
  59. this.placeAtEnd = placeAtEnd;
  60. /** @type {OnDemandGeneration | undefined} */
  61. this.onDemand = onDemand;
  62. }
  63. /**
  64. * @param {ExportBindingInitFragment[]} fragments all fragments to merge
  65. * @returns {ExportBindingInitFragment} merged fragment
  66. */
  67. mergeAll(fragments) {
  68. /** @type {ExportBinding[]} */
  69. const bindings = [];
  70. /** @type {Set<string>} */
  71. const seenNames = new Set();
  72. let placeAtEnd = false;
  73. for (const fragment of fragments) {
  74. for (const binding of fragment.bindings) {
  75. const key = /** @type {string} */ (binding.name);
  76. if (!seenNames.has(key)) {
  77. seenNames.add(key);
  78. bindings.push(binding);
  79. }
  80. }
  81. if (fragment.placeAtEnd) placeAtEnd = true;
  82. }
  83. return new ExportBindingInitFragment(
  84. this.exportsArgument,
  85. bindings,
  86. placeAtEnd,
  87. this.onDemand
  88. );
  89. }
  90. /**
  91. * @param {ExportBindingInitFragment} other other
  92. * @returns {ExportBindingInitFragment} merged result
  93. */
  94. merge(other) {
  95. /** @type {ExportBinding[]} */
  96. const bindings = [];
  97. /** @type {Set<string>} */
  98. const seenNames = new Set();
  99. for (const binding of other.bindings) {
  100. const key = /** @type {string} */ (binding.name);
  101. seenNames.add(key);
  102. bindings.push(binding);
  103. }
  104. for (const binding of this.bindings) {
  105. const key = /** @type {string} */ (binding.name);
  106. if (!seenNames.has(key)) {
  107. seenNames.add(key);
  108. bindings.push(binding);
  109. }
  110. }
  111. return new ExportBindingInitFragment(
  112. this.exportsArgument,
  113. bindings,
  114. this.placeAtEnd || other.placeAtEnd,
  115. this.onDemand || other.onDemand
  116. );
  117. }
  118. /**
  119. * @param {GenerateContext} context context
  120. * @returns {string} the d() call or empty string
  121. */
  122. _generateDefinition({ runtimeTemplate, runtimeRequirements }) {
  123. if (this.bindings.length === 0) return "";
  124. const sorted = [...this.bindings].sort((a, b) =>
  125. /** @type {string} */ (a.name) < /** @type {string} */ (b.name) ? -1 : 1
  126. );
  127. /** @type {string[]} */
  128. const items = [];
  129. for (const binding of sorted) {
  130. const key = JSON.stringify(/** @type {string} */ (binding.name));
  131. if (binding.bindingType === "value") {
  132. items.push(`\n/* harmony export */ ${key}, 0, ${binding.value}`);
  133. } else {
  134. items.push(
  135. `\n/* harmony export */ ${key}, ${runtimeTemplate.returningFunction(binding.value)}`
  136. );
  137. }
  138. }
  139. // Requested even when the call is taken over below: whoever took it may still
  140. // emit it, and by then the requirement set is closed.
  141. runtimeRequirements.add(RuntimeGlobals.exports);
  142. runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
  143. // Only this fragment emits the array form, so the runtime helper drops the
  144. // branch reading it unless this asks for it.
  145. runtimeRequirements.add(RuntimeGlobals.definePropertyGettersFromArray);
  146. const definition = `/* harmony export */ ${
  147. RuntimeGlobals.definePropertyGetters
  148. }(${this.exportsArgument}, [${items.join(",")}\n/* harmony export */ ]);\n`;
  149. return this.onDemand !== undefined &&
  150. this.onDemand(definition, this.placeAtEnd)
  151. ? ""
  152. : definition;
  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(context) {
  160. if (this.placeAtEnd) return undefined;
  161. return this._generateDefinition(context);
  162. }
  163. /**
  164. * Returns the source code that will be included at the end of the module.
  165. * @param {GenerateContext} context context
  166. * @returns {string | Source | undefined} the source code that will be included at the end of the module
  167. */
  168. getEndContent(context) {
  169. if (!this.placeAtEnd) return undefined;
  170. const definePart = this._generateDefinition(context);
  171. if (!definePart) return undefined;
  172. return `\n${definePart}`;
  173. }
  174. }
  175. module.exports = ExportBindingInitFragment;