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