InitFragment.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Florent Cailhol @ooflorent
  4. */
  5. "use strict";
  6. const { ConcatSource } = require("webpack-sources");
  7. const makeSerializable = require("./util/makeSerializable");
  8. /** @import { Source } from "webpack-sources" */
  9. /** @import { GenerateContext } from "./Generator" */
  10. /**
  11. * @import {
  12. * ObjectDeserializerContext,
  13. * ObjectSerializerContext
  14. * } from "./serialization/ObjectMiddleware"
  15. */
  16. /** @typedef {string} InitFragmentKey */
  17. /**
  18. * Defines the maybe mergeable init fragment type used by this module.
  19. * @template GenerateContext
  20. * @typedef {object} MaybeMergeableInitFragment
  21. * @property {InitFragmentKey=} key
  22. * @property {number} stage
  23. * @property {number} position
  24. * @property {(context: GenerateContext) => string | Source | undefined} getContent
  25. * @property {(context: GenerateContext) => string | Source | undefined} getEndContent
  26. * @property {(fragments: MaybeMergeableInitFragment<GenerateContext>) => MaybeMergeableInitFragment<GenerateContext>=} merge
  27. * @property {(fragments: MaybeMergeableInitFragment<GenerateContext>[]) => MaybeMergeableInitFragment<GenerateContext>[]=} mergeAll
  28. */
  29. /**
  30. * Represents InitFragment.
  31. * @template GenerateContext
  32. * @implements {MaybeMergeableInitFragment<GenerateContext>}
  33. */
  34. class InitFragment {
  35. /**
  36. * Creates an instance of InitFragment.
  37. * @param {string | Source | undefined} content the source code that will be included as initialization code
  38. * @param {number} stage category of initialization code (contribute to order)
  39. * @param {number} position position in the category (contribute to order)
  40. * @param {InitFragmentKey=} key unique key to avoid emitting the same initialization code twice
  41. * @param {string | Source=} endContent the source code that will be included at the end of the module
  42. */
  43. constructor(content, stage, position, key, endContent) {
  44. /** @type {string | Source | undefined} */
  45. this.content = content;
  46. /** @type {number} */
  47. this.stage = stage;
  48. /** @type {number} */
  49. this.position = position;
  50. /** @type {string | undefined} */
  51. this.key = key;
  52. /** @type {string | Source | undefined} */
  53. this.endContent = endContent;
  54. }
  55. /**
  56. * Returns the source code that will be included as initialization code.
  57. * @param {GenerateContext} context context
  58. * @returns {string | Source | undefined} the source code that will be included as initialization code
  59. */
  60. getContent(context) {
  61. return this.content;
  62. }
  63. /**
  64. * Returns the source code that will be included at the end of the module.
  65. * @param {GenerateContext} context context
  66. * @returns {string | Source | undefined} the source code that will be included at the end of the module
  67. */
  68. getEndContent(context) {
  69. return this.endContent;
  70. }
  71. /**
  72. * Adds the provided source to the init fragment.
  73. * @template Context
  74. * @param {Source} source sources
  75. * @param {MaybeMergeableInitFragment<Context>[]} initFragments init fragments
  76. * @param {Context} context context
  77. * @returns {Source} source
  78. */
  79. static addToSource(source, initFragments, context) {
  80. if (initFragments.length > 0) {
  81. // Sort fragment indices by (stage, position), falling back to the
  82. // original index — one flat number array instead of N [fragment, index]
  83. // tuples for a stable-by-index order.
  84. const sortedIndices = initFragments.map((_, index) => index);
  85. sortedIndices.sort((a, b) => {
  86. const fragmentA = initFragments[a];
  87. const fragmentB = initFragments[b];
  88. const stageCmp = fragmentA.stage - fragmentB.stage;
  89. if (stageCmp !== 0) return stageCmp;
  90. const positionCmp = fragmentA.position - fragmentB.position;
  91. if (positionCmp !== 0) return positionCmp;
  92. return a - b;
  93. });
  94. // Deduplicate fragments. If a fragment has no key, it is always included.
  95. // Keyless fragments get a unique numeric key; number keys never collide
  96. // with the string `InitFragmentKey`s used for keyed fragments.
  97. /** @type {Map<InitFragmentKey | number, MaybeMergeableInitFragment<Context> | MaybeMergeableInitFragment<Context>[]>} */
  98. const keyedFragments = new Map();
  99. let keylessKey = 0;
  100. for (const index of sortedIndices) {
  101. const fragment = initFragments[index];
  102. if (typeof fragment.mergeAll === "function") {
  103. if (!fragment.key) {
  104. throw new Error(
  105. `InitFragment with mergeAll function must have a valid key: ${fragment.constructor.name}`
  106. );
  107. }
  108. const oldValue = keyedFragments.get(fragment.key);
  109. if (oldValue === undefined) {
  110. keyedFragments.set(fragment.key, fragment);
  111. } else if (Array.isArray(oldValue)) {
  112. oldValue.push(fragment);
  113. } else {
  114. keyedFragments.set(fragment.key, [oldValue, fragment]);
  115. }
  116. continue;
  117. } else if (typeof fragment.merge === "function") {
  118. const key = /** @type {InitFragmentKey} */ (fragment.key);
  119. const oldValue =
  120. /** @type {MaybeMergeableInitFragment<Context>} */
  121. (keyedFragments.get(key));
  122. if (oldValue !== undefined) {
  123. keyedFragments.set(key, fragment.merge(oldValue));
  124. continue;
  125. }
  126. }
  127. keyedFragments.set(fragment.key || keylessKey++, fragment);
  128. }
  129. const concatSource = new ConcatSource();
  130. /** @type {(string | Source)[]} */
  131. const endContents = [];
  132. for (let fragment of keyedFragments.values()) {
  133. if (Array.isArray(fragment)) {
  134. fragment =
  135. /** @type {[MaybeMergeableInitFragment<Context> & { mergeAll: (fragments: MaybeMergeableInitFragment<Context>[]) => MaybeMergeableInitFragment<Context>[] }, ...MaybeMergeableInitFragment<Context>[]]} */
  136. (fragment)[0].mergeAll(fragment);
  137. }
  138. const content =
  139. /** @type {MaybeMergeableInitFragment<Context>} */
  140. (fragment).getContent(context);
  141. if (content) {
  142. concatSource.add(content);
  143. }
  144. const endContent =
  145. /** @type {MaybeMergeableInitFragment<Context>} */
  146. (fragment).getEndContent(context);
  147. if (endContent) {
  148. endContents.push(endContent);
  149. }
  150. }
  151. concatSource.add(source);
  152. for (const content of endContents.reverse()) {
  153. concatSource.add(content);
  154. }
  155. return concatSource;
  156. }
  157. return source;
  158. }
  159. /**
  160. * Serializes this instance into the provided serializer context.
  161. * @param {ObjectSerializerContext} context context
  162. */
  163. serialize(context) {
  164. const { write } = context;
  165. write(this.content);
  166. write(this.stage);
  167. write(this.position);
  168. write(this.key);
  169. write(this.endContent);
  170. }
  171. /**
  172. * Restores this instance from the provided deserializer context.
  173. * @param {ObjectDeserializerContext} context context
  174. */
  175. deserialize(context) {
  176. const { read } = context;
  177. this.content = read();
  178. this.stage = read();
  179. this.position = read();
  180. this.key = read();
  181. this.endContent = read();
  182. }
  183. }
  184. makeSerializable(InitFragment, "webpack/lib/InitFragment");
  185. InitFragment.STAGE_CONSTANTS = 10;
  186. InitFragment.STAGE_ASYNC_BOUNDARY = 20;
  187. InitFragment.STAGE_HARMONY_EXPORTS = 30;
  188. InitFragment.STAGE_HARMONY_IMPORTS = 40;
  189. InitFragment.STAGE_PROVIDES = 50;
  190. InitFragment.STAGE_ASYNC_DEPENDENCIES = 60;
  191. InitFragment.STAGE_ASYNC_HARMONY_IMPORTS = 70;
  192. module.exports = InitFragment;