InitFragment.js 7.0 KB

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