| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const util = require("util");
|
|---|
| 9 | const SortableSet = require("./util/SortableSet");
|
|---|
| 10 | const {
|
|---|
| 11 | compareChunks,
|
|---|
| 12 | compareIterables,
|
|---|
| 13 | compareLocations
|
|---|
| 14 | } = require("./util/comparators");
|
|---|
| 15 |
|
|---|
| 16 | /** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
|
|---|
| 17 | /** @typedef {import("./Chunk")} Chunk */
|
|---|
| 18 | /** @typedef {import("./ChunkGraph")} ChunkGraph */
|
|---|
| 19 | /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
|
|---|
| 20 | /** @typedef {import("./Entrypoint")} Entrypoint */
|
|---|
| 21 | /** @typedef {import("./Module")} Module */
|
|---|
| 22 | /** @typedef {import("./ModuleGraph")} ModuleGraph */
|
|---|
| 23 |
|
|---|
| 24 | /** @typedef {{ module: Module | null, loc: DependencyLocation, request: string }} OriginRecord */
|
|---|
| 25 |
|
|---|
| 26 | /**
|
|---|
| 27 | * Describes the scheduling hints that can be attached to a chunk group.
|
|---|
| 28 | * These values influence how child groups are ordered for preload/prefetch
|
|---|
| 29 | * and how their fetch priority is exposed to runtime code.
|
|---|
| 30 | * @typedef {object} RawChunkGroupOptions
|
|---|
| 31 | * @property {number=} preloadOrder
|
|---|
| 32 | * @property {number=} prefetchOrder
|
|---|
| 33 | * @property {("low" | "high" | "auto")=} fetchPriority
|
|---|
| 34 | */
|
|---|
| 35 |
|
|---|
| 36 | /** @typedef {RawChunkGroupOptions & { name?: string | null }} ChunkGroupOptions */
|
|---|
| 37 |
|
|---|
| 38 | let debugId = 5000;
|
|---|
| 39 |
|
|---|
| 40 | /**
|
|---|
| 41 | * Materializes a sortable set as an array without changing its current order.
|
|---|
| 42 | * Used with `SortableSet` caches that expect a stable array result.
|
|---|
| 43 | * @template T
|
|---|
| 44 | * @param {SortableSet<T>} set set to convert to array.
|
|---|
| 45 | * @returns {T[]} the array format of existing set
|
|---|
| 46 | */
|
|---|
| 47 | const getArray = (set) => [...set];
|
|---|
| 48 |
|
|---|
| 49 | /**
|
|---|
| 50 | * A convenience method used to sort chunks based on their id's
|
|---|
| 51 | * @param {ChunkGroup} a first sorting comparator
|
|---|
| 52 | * @param {ChunkGroup} b second sorting comparator
|
|---|
| 53 | * @returns {1 | 0 | -1} a sorting index to determine order
|
|---|
| 54 | */
|
|---|
| 55 | const sortById = (a, b) => {
|
|---|
| 56 | if (a.id < b.id) return -1;
|
|---|
| 57 | if (b.id < a.id) return 1;
|
|---|
| 58 | return 0;
|
|---|
| 59 | };
|
|---|
| 60 |
|
|---|
| 61 | /**
|
|---|
| 62 | * Orders origin records by referencing module and then by source location.
|
|---|
| 63 | * This keeps origin metadata deterministic for hashing and diagnostics.
|
|---|
| 64 | * @param {OriginRecord} a the first comparator in sort
|
|---|
| 65 | * @param {OriginRecord} b the second comparator in sort
|
|---|
| 66 | * @returns {1 | -1 | 0} returns sorting order as index
|
|---|
| 67 | */
|
|---|
| 68 | const sortOrigin = (a, b) => {
|
|---|
| 69 | const aIdent = a.module ? a.module.identifier() : "";
|
|---|
| 70 | const bIdent = b.module ? b.module.identifier() : "";
|
|---|
| 71 | if (aIdent < bIdent) return -1;
|
|---|
| 72 | if (aIdent > bIdent) return 1;
|
|---|
| 73 | return compareLocations(a.loc, b.loc);
|
|---|
| 74 | };
|
|---|
| 75 |
|
|---|
| 76 | /**
|
|---|
| 77 | * Represents a connected group of chunks along with the parent/child
|
|---|
| 78 | * relationships, async blocks, and traversal metadata webpack tracks for it.
|
|---|
| 79 | */
|
|---|
| 80 | class ChunkGroup {
|
|---|
| 81 | /**
|
|---|
| 82 | * Creates a chunk group and initializes the relationship sets and ordering
|
|---|
| 83 | * metadata used while building and optimizing the chunk graph.
|
|---|
| 84 | * @param {string | ChunkGroupOptions=} options chunk group options passed to chunkGroup
|
|---|
| 85 | */
|
|---|
| 86 | constructor(options) {
|
|---|
| 87 | if (typeof options === "string") {
|
|---|
| 88 | options = { name: options };
|
|---|
| 89 | } else if (!options) {
|
|---|
| 90 | options = { name: undefined };
|
|---|
| 91 | }
|
|---|
| 92 | /** @type {number} */
|
|---|
| 93 | this.groupDebugId = debugId++;
|
|---|
| 94 | /** @type {ChunkGroupOptions} */
|
|---|
| 95 | this.options = options;
|
|---|
| 96 | /** @type {SortableSet<ChunkGroup>} */
|
|---|
| 97 | this._children = new SortableSet(undefined, sortById);
|
|---|
| 98 | /** @type {SortableSet<ChunkGroup>} */
|
|---|
| 99 | this._parents = new SortableSet(undefined, sortById);
|
|---|
| 100 | /** @type {SortableSet<ChunkGroup>} */
|
|---|
| 101 | this._asyncEntrypoints = new SortableSet(undefined, sortById);
|
|---|
| 102 | /** @type {SortableSet<AsyncDependenciesBlock>} */
|
|---|
| 103 | this._blocks = new SortableSet();
|
|---|
| 104 | /** @type {Chunk[]} */
|
|---|
| 105 | this.chunks = [];
|
|---|
| 106 | /** @type {OriginRecord[]} */
|
|---|
| 107 | this.origins = [];
|
|---|
| 108 |
|
|---|
| 109 | /** @typedef {Map<Module, number>} OrderIndices */
|
|---|
| 110 |
|
|---|
| 111 | /** Indices in top-down order */
|
|---|
| 112 | /**
|
|---|
| 113 | * @private
|
|---|
| 114 | * @type {OrderIndices}
|
|---|
| 115 | */
|
|---|
| 116 | this._modulePreOrderIndices = new Map();
|
|---|
| 117 | /** Indices in bottom-up order */
|
|---|
| 118 | /**
|
|---|
| 119 | * @private
|
|---|
| 120 | * @type {OrderIndices}
|
|---|
| 121 | */
|
|---|
| 122 | this._modulePostOrderIndices = new Map();
|
|---|
| 123 | /** @type {number | undefined} */
|
|---|
| 124 | this.index = undefined;
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | /**
|
|---|
| 128 | * Merges additional options into the chunk group.
|
|---|
| 129 | * Order-based options are combined by taking the higher priority, while
|
|---|
| 130 | * unsupported conflicts surface as an explicit error.
|
|---|
| 131 | * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions
|
|---|
| 132 | * @returns {void}
|
|---|
| 133 | */
|
|---|
| 134 | addOptions(options) {
|
|---|
| 135 | for (const key of /** @type {(keyof ChunkGroupOptions)[]} */ (
|
|---|
| 136 | Object.keys(options)
|
|---|
| 137 | )) {
|
|---|
| 138 | if (this.options[key] === undefined) {
|
|---|
| 139 | /** @type {ChunkGroupOptions[keyof ChunkGroupOptions]} */
|
|---|
| 140 | (this.options[key]) = options[key];
|
|---|
| 141 | } else if (this.options[key] !== options[key]) {
|
|---|
| 142 | if (key.endsWith("Order")) {
|
|---|
| 143 | const orderKey =
|
|---|
| 144 | /** @type {Exclude<keyof ChunkGroupOptions, "name" | "fetchPriority">} */
|
|---|
| 145 | (key);
|
|---|
| 146 |
|
|---|
| 147 | this.options[orderKey] = Math.max(
|
|---|
| 148 | /** @type {number} */
|
|---|
| 149 | (this.options[orderKey]),
|
|---|
| 150 | /** @type {number} */
|
|---|
| 151 | (options[orderKey])
|
|---|
| 152 | );
|
|---|
| 153 | } else {
|
|---|
| 154 | throw new Error(
|
|---|
| 155 | `ChunkGroup.addOptions: No option merge strategy for ${key}`
|
|---|
| 156 | );
|
|---|
| 157 | }
|
|---|
| 158 | }
|
|---|
| 159 | }
|
|---|
| 160 | }
|
|---|
| 161 |
|
|---|
| 162 | /**
|
|---|
| 163 | * Returns the configured name of the chunk group, if one was assigned.
|
|---|
| 164 | * @returns {ChunkGroupOptions["name"]} returns the ChunkGroup name
|
|---|
| 165 | */
|
|---|
| 166 | get name() {
|
|---|
| 167 | return this.options.name;
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | /**
|
|---|
| 171 | * Updates the configured name of the chunk group.
|
|---|
| 172 | * @param {string | undefined} value the new name for ChunkGroup
|
|---|
| 173 | * @returns {void}
|
|---|
| 174 | */
|
|---|
| 175 | set name(value) {
|
|---|
| 176 | this.options.name = value;
|
|---|
| 177 | }
|
|---|
| 178 |
|
|---|
| 179 | /* istanbul ignore next */
|
|---|
| 180 | /**
|
|---|
| 181 | * Returns a debug-only identifier derived from the group's member chunk
|
|---|
| 182 | * debug ids. This is primarily useful in diagnostics and assertions.
|
|---|
| 183 | * @returns {string} a unique concatenation of chunk debugId's
|
|---|
| 184 | */
|
|---|
| 185 | get debugId() {
|
|---|
| 186 | return Array.from(this.chunks, (x) => x.debugId).join("+");
|
|---|
| 187 | }
|
|---|
| 188 |
|
|---|
| 189 | /**
|
|---|
| 190 | * Returns an identifier derived from the ids of the chunks currently in
|
|---|
| 191 | * the group.
|
|---|
| 192 | * @returns {string} a unique concatenation of chunk ids
|
|---|
| 193 | */
|
|---|
| 194 | get id() {
|
|---|
| 195 | return Array.from(this.chunks, (x) => x.id).join("+");
|
|---|
| 196 | }
|
|---|
| 197 |
|
|---|
| 198 | /**
|
|---|
| 199 | * Moves a chunk to the front of the group or inserts it when it is not
|
|---|
| 200 | * already present.
|
|---|
| 201 | * @param {Chunk} chunk chunk being unshifted
|
|---|
| 202 | * @returns {boolean} returns true if attempted chunk shift is accepted
|
|---|
| 203 | */
|
|---|
| 204 | unshiftChunk(chunk) {
|
|---|
| 205 | const oldIdx = this.chunks.indexOf(chunk);
|
|---|
| 206 | if (oldIdx > 0) {
|
|---|
| 207 | this.chunks.splice(oldIdx, 1);
|
|---|
| 208 | this.chunks.unshift(chunk);
|
|---|
| 209 | } else if (oldIdx < 0) {
|
|---|
| 210 | this.chunks.unshift(chunk);
|
|---|
| 211 | return true;
|
|---|
| 212 | }
|
|---|
| 213 | return false;
|
|---|
| 214 | }
|
|---|
| 215 |
|
|---|
| 216 | /**
|
|---|
| 217 | * Inserts a chunk directly before another chunk that already belongs to the
|
|---|
| 218 | * group, preserving the rest of the ordering.
|
|---|
| 219 | * @param {Chunk} chunk Chunk being inserted
|
|---|
| 220 | * @param {Chunk} before Placeholder/target chunk marking new chunk insertion point
|
|---|
| 221 | * @returns {boolean} return true if insertion was successful
|
|---|
| 222 | */
|
|---|
| 223 | insertChunk(chunk, before) {
|
|---|
| 224 | const oldIdx = this.chunks.indexOf(chunk);
|
|---|
| 225 | const idx = this.chunks.indexOf(before);
|
|---|
| 226 | if (idx < 0) {
|
|---|
| 227 | throw new Error("before chunk not found");
|
|---|
| 228 | }
|
|---|
| 229 | if (oldIdx >= 0 && oldIdx > idx) {
|
|---|
| 230 | this.chunks.splice(oldIdx, 1);
|
|---|
| 231 | this.chunks.splice(idx, 0, chunk);
|
|---|
| 232 | } else if (oldIdx < 0) {
|
|---|
| 233 | this.chunks.splice(idx, 0, chunk);
|
|---|
| 234 | return true;
|
|---|
| 235 | }
|
|---|
| 236 | return false;
|
|---|
| 237 | }
|
|---|
| 238 |
|
|---|
| 239 | /**
|
|---|
| 240 | * Appends a chunk to the group when it is not already a member.
|
|---|
| 241 | * @param {Chunk} chunk chunk being pushed into ChunkGroupS
|
|---|
| 242 | * @returns {boolean} returns true if chunk addition was successful.
|
|---|
| 243 | */
|
|---|
| 244 | pushChunk(chunk) {
|
|---|
| 245 | const oldIdx = this.chunks.indexOf(chunk);
|
|---|
| 246 | if (oldIdx >= 0) {
|
|---|
| 247 | return false;
|
|---|
| 248 | }
|
|---|
| 249 | this.chunks.push(chunk);
|
|---|
| 250 | return true;
|
|---|
| 251 | }
|
|---|
| 252 |
|
|---|
| 253 | /**
|
|---|
| 254 | * Replaces one member chunk with another while preserving the group's
|
|---|
| 255 | * ordering and avoiding duplicates.
|
|---|
| 256 | * @param {Chunk} oldChunk chunk to be replaced
|
|---|
| 257 | * @param {Chunk} newChunk New chunk that will be replaced with
|
|---|
| 258 | * @returns {boolean | undefined} returns true if the replacement was successful
|
|---|
| 259 | */
|
|---|
| 260 | replaceChunk(oldChunk, newChunk) {
|
|---|
| 261 | const oldIdx = this.chunks.indexOf(oldChunk);
|
|---|
| 262 | if (oldIdx < 0) return false;
|
|---|
| 263 | const newIdx = this.chunks.indexOf(newChunk);
|
|---|
| 264 | if (newIdx < 0) {
|
|---|
| 265 | this.chunks[oldIdx] = newChunk;
|
|---|
| 266 | return true;
|
|---|
| 267 | }
|
|---|
| 268 | if (newIdx < oldIdx) {
|
|---|
| 269 | this.chunks.splice(oldIdx, 1);
|
|---|
| 270 | return true;
|
|---|
| 271 | } else if (newIdx !== oldIdx) {
|
|---|
| 272 | this.chunks[oldIdx] = newChunk;
|
|---|
| 273 | this.chunks.splice(newIdx, 1);
|
|---|
| 274 | return true;
|
|---|
| 275 | }
|
|---|
| 276 | }
|
|---|
| 277 |
|
|---|
| 278 | /**
|
|---|
| 279 | * Removes a chunk from this group.
|
|---|
| 280 | * @param {Chunk} chunk chunk to remove
|
|---|
| 281 | * @returns {boolean} returns true if chunk was removed
|
|---|
| 282 | */
|
|---|
| 283 | removeChunk(chunk) {
|
|---|
| 284 | const idx = this.chunks.indexOf(chunk);
|
|---|
| 285 | if (idx >= 0) {
|
|---|
| 286 | this.chunks.splice(idx, 1);
|
|---|
| 287 | return true;
|
|---|
| 288 | }
|
|---|
| 289 | return false;
|
|---|
| 290 | }
|
|---|
| 291 |
|
|---|
| 292 | /**
|
|---|
| 293 | * Indicates whether this chunk group is loaded as part of the initial page
|
|---|
| 294 | * load instead of being created lazily.
|
|---|
| 295 | * @returns {boolean} true, when this chunk group will be loaded on initial page load
|
|---|
| 296 | */
|
|---|
| 297 | isInitial() {
|
|---|
| 298 | return false;
|
|---|
| 299 | }
|
|---|
| 300 |
|
|---|
| 301 | /**
|
|---|
| 302 | * Adds a child chunk group to the current group.
|
|---|
| 303 | * @param {ChunkGroup} group chunk group to add
|
|---|
| 304 | * @returns {boolean} returns true if chunk group was added
|
|---|
| 305 | */
|
|---|
| 306 | addChild(group) {
|
|---|
| 307 | const size = this._children.size;
|
|---|
| 308 | this._children.add(group);
|
|---|
| 309 | return size !== this._children.size;
|
|---|
| 310 | }
|
|---|
| 311 |
|
|---|
| 312 | /**
|
|---|
| 313 | * Returns the child chunk groups reachable from this group.
|
|---|
| 314 | * @returns {ChunkGroup[]} returns the children of this group
|
|---|
| 315 | */
|
|---|
| 316 | getChildren() {
|
|---|
| 317 | return this._children.getFromCache(getArray);
|
|---|
| 318 | }
|
|---|
| 319 |
|
|---|
| 320 | getNumberOfChildren() {
|
|---|
| 321 | return this._children.size;
|
|---|
| 322 | }
|
|---|
| 323 |
|
|---|
| 324 | get childrenIterable() {
|
|---|
| 325 | return this._children;
|
|---|
| 326 | }
|
|---|
| 327 |
|
|---|
| 328 | /**
|
|---|
| 329 | * Removes a child chunk group and clears the corresponding parent link on
|
|---|
| 330 | * the removed child.
|
|---|
| 331 | * @param {ChunkGroup} group the chunk group to remove
|
|---|
| 332 | * @returns {boolean} returns true if the chunk group was removed
|
|---|
| 333 | */
|
|---|
| 334 | removeChild(group) {
|
|---|
| 335 | if (!this._children.has(group)) {
|
|---|
| 336 | return false;
|
|---|
| 337 | }
|
|---|
| 338 |
|
|---|
| 339 | this._children.delete(group);
|
|---|
| 340 | group.removeParent(this);
|
|---|
| 341 | return true;
|
|---|
| 342 | }
|
|---|
| 343 |
|
|---|
| 344 | /**
|
|---|
| 345 | * Records a parent chunk group relationship.
|
|---|
| 346 | * @param {ChunkGroup} parentChunk the parent group to be added into
|
|---|
| 347 | * @returns {boolean} returns true if this chunk group was added to the parent group
|
|---|
| 348 | */
|
|---|
| 349 | addParent(parentChunk) {
|
|---|
| 350 | if (!this._parents.has(parentChunk)) {
|
|---|
| 351 | this._parents.add(parentChunk);
|
|---|
| 352 | return true;
|
|---|
| 353 | }
|
|---|
| 354 | return false;
|
|---|
| 355 | }
|
|---|
| 356 |
|
|---|
| 357 | /**
|
|---|
| 358 | * Returns the parent chunk groups that can lead to this group.
|
|---|
| 359 | * @returns {ChunkGroup[]} returns the parents of this group
|
|---|
| 360 | */
|
|---|
| 361 | getParents() {
|
|---|
| 362 | return this._parents.getFromCache(getArray);
|
|---|
| 363 | }
|
|---|
| 364 |
|
|---|
| 365 | getNumberOfParents() {
|
|---|
| 366 | return this._parents.size;
|
|---|
| 367 | }
|
|---|
| 368 |
|
|---|
| 369 | /**
|
|---|
| 370 | * Checks whether the provided group is registered as a parent.
|
|---|
| 371 | * @param {ChunkGroup} parent the parent group
|
|---|
| 372 | * @returns {boolean} returns true if the parent group contains this group
|
|---|
| 373 | */
|
|---|
| 374 | hasParent(parent) {
|
|---|
| 375 | return this._parents.has(parent);
|
|---|
| 376 | }
|
|---|
| 377 |
|
|---|
| 378 | get parentsIterable() {
|
|---|
| 379 | return this._parents;
|
|---|
| 380 | }
|
|---|
| 381 |
|
|---|
| 382 | /**
|
|---|
| 383 | * Removes a parent chunk group and clears the reverse child relationship.
|
|---|
| 384 | * @param {ChunkGroup} chunkGroup the parent group
|
|---|
| 385 | * @returns {boolean} returns true if this group has been removed from the parent
|
|---|
| 386 | */
|
|---|
| 387 | removeParent(chunkGroup) {
|
|---|
| 388 | if (this._parents.delete(chunkGroup)) {
|
|---|
| 389 | chunkGroup.removeChild(this);
|
|---|
| 390 | return true;
|
|---|
| 391 | }
|
|---|
| 392 | return false;
|
|---|
| 393 | }
|
|---|
| 394 |
|
|---|
| 395 | /**
|
|---|
| 396 | * Registers an async entrypoint that is rooted in this chunk group.
|
|---|
| 397 | * @param {Entrypoint} entrypoint entrypoint to add
|
|---|
| 398 | * @returns {boolean} returns true if entrypoint was added
|
|---|
| 399 | */
|
|---|
| 400 | addAsyncEntrypoint(entrypoint) {
|
|---|
| 401 | const size = this._asyncEntrypoints.size;
|
|---|
| 402 | this._asyncEntrypoints.add(entrypoint);
|
|---|
| 403 | return size !== this._asyncEntrypoints.size;
|
|---|
| 404 | }
|
|---|
| 405 |
|
|---|
| 406 | get asyncEntrypointsIterable() {
|
|---|
| 407 | return this._asyncEntrypoints;
|
|---|
| 408 | }
|
|---|
| 409 |
|
|---|
| 410 | /**
|
|---|
| 411 | * Returns the async dependency blocks that create or reference this group.
|
|---|
| 412 | * @returns {AsyncDependenciesBlock[]} an array containing the blocks
|
|---|
| 413 | */
|
|---|
| 414 | getBlocks() {
|
|---|
| 415 | return this._blocks.getFromCache(getArray);
|
|---|
| 416 | }
|
|---|
| 417 |
|
|---|
| 418 | getNumberOfBlocks() {
|
|---|
| 419 | return this._blocks.size;
|
|---|
| 420 | }
|
|---|
| 421 |
|
|---|
| 422 | /**
|
|---|
| 423 | * Checks whether an async dependency block is associated with this group.
|
|---|
| 424 | * @param {AsyncDependenciesBlock} block block
|
|---|
| 425 | * @returns {boolean} true, if block exists
|
|---|
| 426 | */
|
|---|
| 427 | hasBlock(block) {
|
|---|
| 428 | return this._blocks.has(block);
|
|---|
| 429 | }
|
|---|
| 430 |
|
|---|
| 431 | /**
|
|---|
| 432 | * Exposes the group's async dependency blocks as an iterable.
|
|---|
| 433 | * @returns {Iterable<AsyncDependenciesBlock>} blocks
|
|---|
| 434 | */
|
|---|
| 435 | get blocksIterable() {
|
|---|
| 436 | return this._blocks;
|
|---|
| 437 | }
|
|---|
| 438 |
|
|---|
| 439 | /**
|
|---|
| 440 | * Associates an async dependency block with this chunk group.
|
|---|
| 441 | * @param {AsyncDependenciesBlock} block a block
|
|---|
| 442 | * @returns {boolean} false, if block was already added
|
|---|
| 443 | */
|
|---|
| 444 | addBlock(block) {
|
|---|
| 445 | if (!this._blocks.has(block)) {
|
|---|
| 446 | this._blocks.add(block);
|
|---|
| 447 | return true;
|
|---|
| 448 | }
|
|---|
| 449 | return false;
|
|---|
| 450 | }
|
|---|
| 451 |
|
|---|
| 452 | /**
|
|---|
| 453 | * Records where this chunk group originated from in user code.
|
|---|
| 454 | * The origin is used for diagnostics, ordering, and reporting.
|
|---|
| 455 | * @param {Module | null} module origin module
|
|---|
| 456 | * @param {DependencyLocation} loc location of the reference in the origin module
|
|---|
| 457 | * @param {string} request request name of the reference
|
|---|
| 458 | * @returns {void}
|
|---|
| 459 | */
|
|---|
| 460 | addOrigin(module, loc, request) {
|
|---|
| 461 | this.origins.push({
|
|---|
| 462 | module,
|
|---|
| 463 | loc,
|
|---|
| 464 | request
|
|---|
| 465 | });
|
|---|
| 466 | }
|
|---|
| 467 |
|
|---|
| 468 | /**
|
|---|
| 469 | * Collects the emitted files produced by every chunk in the group.
|
|---|
| 470 | * @returns {string[]} the files contained this chunk group
|
|---|
| 471 | */
|
|---|
| 472 | getFiles() {
|
|---|
| 473 | /** @type {Set<string>} */
|
|---|
| 474 | const files = new Set();
|
|---|
| 475 |
|
|---|
| 476 | for (const chunk of this.chunks) {
|
|---|
| 477 | for (const file of chunk.files) {
|
|---|
| 478 | files.add(file);
|
|---|
| 479 | }
|
|---|
| 480 | }
|
|---|
| 481 |
|
|---|
| 482 | return [...files];
|
|---|
| 483 | }
|
|---|
| 484 |
|
|---|
| 485 | /**
|
|---|
| 486 | * Disconnects this group from its parents, children, and chunks.
|
|---|
| 487 | * Child groups are reconnected to this group's parents so the surrounding
|
|---|
| 488 | * graph remains intact after removal.
|
|---|
| 489 | * @returns {void}
|
|---|
| 490 | */
|
|---|
| 491 | remove() {
|
|---|
| 492 | // cleanup parents
|
|---|
| 493 | for (const parentChunkGroup of this._parents) {
|
|---|
| 494 | // remove this chunk from its parents
|
|---|
| 495 | parentChunkGroup._children.delete(this);
|
|---|
| 496 |
|
|---|
| 497 | // cleanup "sub chunks"
|
|---|
| 498 | for (const chunkGroup of this._children) {
|
|---|
| 499 | /**
|
|---|
| 500 | * remove this chunk as "intermediary" and connect
|
|---|
| 501 | * it "sub chunks" and parents directly
|
|---|
| 502 | */
|
|---|
| 503 | // add parent to each "sub chunk"
|
|---|
| 504 | chunkGroup.addParent(parentChunkGroup);
|
|---|
| 505 | // add "sub chunk" to parent
|
|---|
| 506 | parentChunkGroup.addChild(chunkGroup);
|
|---|
| 507 | }
|
|---|
| 508 | }
|
|---|
| 509 |
|
|---|
| 510 | /**
|
|---|
| 511 | * we need to iterate again over the children
|
|---|
| 512 | * to remove this from the child's parents.
|
|---|
| 513 | * This can not be done in the above loop
|
|---|
| 514 | * as it is not guaranteed that `this._parents` contains anything.
|
|---|
| 515 | */
|
|---|
| 516 | for (const chunkGroup of this._children) {
|
|---|
| 517 | // remove this as parent of every "sub chunk"
|
|---|
| 518 | chunkGroup._parents.delete(this);
|
|---|
| 519 | }
|
|---|
| 520 |
|
|---|
| 521 | // remove chunks
|
|---|
| 522 | for (const chunk of this.chunks) {
|
|---|
| 523 | chunk.removeGroup(this);
|
|---|
| 524 | }
|
|---|
| 525 | }
|
|---|
| 526 |
|
|---|
| 527 | sortItems() {
|
|---|
| 528 | this.origins.sort(sortOrigin);
|
|---|
| 529 | }
|
|---|
| 530 |
|
|---|
| 531 | /**
|
|---|
| 532 | * Sorting predicate which allows current ChunkGroup to be compared against another.
|
|---|
| 533 | * Sorting values are based off of number of chunks in ChunkGroup.
|
|---|
| 534 | * @param {ChunkGraph} chunkGraph the chunk graph
|
|---|
| 535 | * @param {ChunkGroup} otherGroup the chunkGroup to compare this against
|
|---|
| 536 | * @returns {-1 | 0 | 1} sort position for comparison
|
|---|
| 537 | */
|
|---|
| 538 | compareTo(chunkGraph, otherGroup) {
|
|---|
| 539 | if (this.chunks.length > otherGroup.chunks.length) return -1;
|
|---|
| 540 | if (this.chunks.length < otherGroup.chunks.length) return 1;
|
|---|
| 541 | return compareIterables(compareChunks(chunkGraph))(
|
|---|
| 542 | this.chunks,
|
|---|
| 543 | otherGroup.chunks
|
|---|
| 544 | );
|
|---|
| 545 | }
|
|---|
| 546 |
|
|---|
| 547 | /**
|
|---|
| 548 | * Aggregates per-block `*Order` options for the blocks that bridge this
|
|---|
| 549 | * chunk group to the given child chunk group. `*Order` options are tied to
|
|---|
| 550 | * the originating `import()` call and must not be sourced from the child's
|
|---|
| 551 | * shared options, otherwise a webpackPrefetch/Preload directive from one
|
|---|
| 552 | * parent would leak into other parents that share the child by name.
|
|---|
| 553 | * @param {ChunkGroup} childGroup the child chunk group
|
|---|
| 554 | * @param {ChunkGraph} chunkGraph the chunk graph
|
|---|
| 555 | * @returns {Record<string, number>} merged `*Order` options for the edge from this group to `childGroup`
|
|---|
| 556 | */
|
|---|
| 557 | getChildOrderOptions(childGroup, chunkGraph) {
|
|---|
| 558 | /** @type {Record<string, number>} */
|
|---|
| 559 | const result = Object.create(null);
|
|---|
| 560 | let bridged = false;
|
|---|
| 561 | for (const block of childGroup.blocksIterable) {
|
|---|
| 562 | const rootModule = /** @type {Module} */ (block.getRootBlock());
|
|---|
| 563 | if (!chunkGraph.isModuleInChunkGroup(rootModule, this)) continue;
|
|---|
| 564 | bridged = true;
|
|---|
| 565 | const opts = block.groupOptions;
|
|---|
| 566 | if (!opts) continue;
|
|---|
| 567 | for (const key of Object.keys(opts)) {
|
|---|
| 568 | if (!key.endsWith("Order")) continue;
|
|---|
| 569 | const value =
|
|---|
| 570 | /** @type {number} */
|
|---|
| 571 | (opts[/** @type {keyof ChunkGroupOptions} */ (key)]);
|
|---|
| 572 | if (typeof value !== "number") continue;
|
|---|
| 573 | if (result[key] === undefined || value > result[key]) {
|
|---|
| 574 | result[key] = value;
|
|---|
| 575 | }
|
|---|
| 576 | }
|
|---|
| 577 | }
|
|---|
| 578 | // Fall back to the child's own options only when no block bridges
|
|---|
| 579 | // this edge (e.g. a chunk group created by APIs that don't go through
|
|---|
| 580 | // an AsyncDependenciesBlock). Otherwise we'd reintroduce the leak.
|
|---|
| 581 | if (!bridged) {
|
|---|
| 582 | for (const key of Object.keys(childGroup.options)) {
|
|---|
| 583 | if (!key.endsWith("Order")) continue;
|
|---|
| 584 | const value =
|
|---|
| 585 | childGroup.options[/** @type {keyof ChunkGroupOptions} */ (key)];
|
|---|
| 586 | if (typeof value === "number") {
|
|---|
| 587 | result[key] = value;
|
|---|
| 588 | }
|
|---|
| 589 | }
|
|---|
| 590 | }
|
|---|
| 591 | return result;
|
|---|
| 592 | }
|
|---|
| 593 |
|
|---|
| 594 | /**
|
|---|
| 595 | * Groups child chunk groups by their `*Order` options and sorts each group
|
|---|
| 596 | * by descending order and deterministic chunk-group comparison.
|
|---|
| 597 | * @param {ModuleGraph} moduleGraph the module graph
|
|---|
| 598 | * @param {ChunkGraph} chunkGraph the chunk graph
|
|---|
| 599 | * @returns {Record<string, ChunkGroup[]>} mapping from children type to ordered list of ChunkGroups
|
|---|
| 600 | */
|
|---|
| 601 | getChildrenByOrders(moduleGraph, chunkGraph) {
|
|---|
| 602 | /** @type {Map<string, { order: number, group: ChunkGroup }[]>} */
|
|---|
| 603 | const lists = new Map();
|
|---|
| 604 | for (const childGroup of this._children) {
|
|---|
| 605 | const edgeOptions = this.getChildOrderOptions(childGroup, chunkGraph);
|
|---|
| 606 | for (const key of Object.keys(edgeOptions)) {
|
|---|
| 607 | const name = key.slice(0, key.length - "Order".length);
|
|---|
| 608 | let list = lists.get(name);
|
|---|
| 609 | if (list === undefined) {
|
|---|
| 610 | lists.set(name, (list = []));
|
|---|
| 611 | }
|
|---|
| 612 | list.push({
|
|---|
| 613 | order: edgeOptions[key],
|
|---|
| 614 | group: childGroup
|
|---|
| 615 | });
|
|---|
| 616 | }
|
|---|
| 617 | }
|
|---|
| 618 | /** @type {Record<string, ChunkGroup[]>} */
|
|---|
| 619 | const result = Object.create(null);
|
|---|
| 620 | for (const [name, list] of lists) {
|
|---|
| 621 | list.sort((a, b) => {
|
|---|
| 622 | const cmp = b.order - a.order;
|
|---|
| 623 | if (cmp !== 0) return cmp;
|
|---|
| 624 | return a.group.compareTo(chunkGraph, b.group);
|
|---|
| 625 | });
|
|---|
| 626 | result[name] = list.map((i) => i.group);
|
|---|
| 627 | }
|
|---|
| 628 | return result;
|
|---|
| 629 | }
|
|---|
| 630 |
|
|---|
| 631 | /**
|
|---|
| 632 | * Stores the module's top-down traversal index within this group.
|
|---|
| 633 | * @param {Module} module module for which the index should be set
|
|---|
| 634 | * @param {number} index the index of the module
|
|---|
| 635 | * @returns {void}
|
|---|
| 636 | */
|
|---|
| 637 | setModulePreOrderIndex(module, index) {
|
|---|
| 638 | this._modulePreOrderIndices.set(module, index);
|
|---|
| 639 | }
|
|---|
| 640 |
|
|---|
| 641 | /**
|
|---|
| 642 | * Returns the module's top-down traversal index within this group.
|
|---|
| 643 | * @param {Module} module the module
|
|---|
| 644 | * @returns {number | undefined} index
|
|---|
| 645 | */
|
|---|
| 646 | getModulePreOrderIndex(module) {
|
|---|
| 647 | return this._modulePreOrderIndices.get(module);
|
|---|
| 648 | }
|
|---|
| 649 |
|
|---|
| 650 | /**
|
|---|
| 651 | * Stores the module's bottom-up traversal index within this group.
|
|---|
| 652 | * @param {Module} module module for which the index should be set
|
|---|
| 653 | * @param {number} index the index of the module
|
|---|
| 654 | * @returns {void}
|
|---|
| 655 | */
|
|---|
| 656 | setModulePostOrderIndex(module, index) {
|
|---|
| 657 | this._modulePostOrderIndices.set(module, index);
|
|---|
| 658 | }
|
|---|
| 659 |
|
|---|
| 660 | /**
|
|---|
| 661 | * Returns the module's bottom-up traversal index within this group.
|
|---|
| 662 | * @param {Module} module the module
|
|---|
| 663 | * @returns {number | undefined} index
|
|---|
| 664 | */
|
|---|
| 665 | getModulePostOrderIndex(module) {
|
|---|
| 666 | return this._modulePostOrderIndices.get(module);
|
|---|
| 667 | }
|
|---|
| 668 |
|
|---|
| 669 | /* istanbul ignore next */
|
|---|
| 670 | checkConstraints() {
|
|---|
| 671 | const chunk = this;
|
|---|
| 672 | for (const child of chunk._children) {
|
|---|
| 673 | if (!child._parents.has(chunk)) {
|
|---|
| 674 | throw new Error(
|
|---|
| 675 | `checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`
|
|---|
| 676 | );
|
|---|
| 677 | }
|
|---|
| 678 | }
|
|---|
| 679 | for (const parentChunk of chunk._parents) {
|
|---|
| 680 | if (!parentChunk._children.has(chunk)) {
|
|---|
| 681 | throw new Error(
|
|---|
| 682 | `checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`
|
|---|
| 683 | );
|
|---|
| 684 | }
|
|---|
| 685 | }
|
|---|
| 686 | }
|
|---|
| 687 | }
|
|---|
| 688 |
|
|---|
| 689 | ChunkGroup.prototype.getModuleIndex = util.deprecate(
|
|---|
| 690 | ChunkGroup.prototype.getModulePreOrderIndex,
|
|---|
| 691 | "ChunkGroup.getModuleIndex was renamed to getModulePreOrderIndex",
|
|---|
| 692 | "DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX"
|
|---|
| 693 | );
|
|---|
| 694 |
|
|---|
| 695 | ChunkGroup.prototype.getModuleIndex2 = util.deprecate(
|
|---|
| 696 | ChunkGroup.prototype.getModulePostOrderIndex,
|
|---|
| 697 | "ChunkGroup.getModuleIndex2 was renamed to getModulePostOrderIndex",
|
|---|
| 698 | "DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX_2"
|
|---|
| 699 | );
|
|---|
| 700 |
|
|---|
| 701 | module.exports = ChunkGroup;
|
|---|