source: frontend/node_modules/webpack/lib/ChunkGroup.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 20.0 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const util = require("util");
9const SortableSet = require("./util/SortableSet");
10const {
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
38let 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 */
47const 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 */
55const 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 */
68const 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 */
80class 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
689ChunkGroup.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
695ChunkGroup.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
701module.exports = ChunkGroup;
Note: See TracBrowser for help on using the repository browser.