提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
6.5 KiB

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