Projekt

Obecné

Profil

Stáhnout (12.1 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
const SortableSet = require("./util/SortableSet");
8
const compareLocations = require("./compareLocations");
9

    
10
/** @typedef {import("./Chunk")} Chunk */
11
/** @typedef {import("./Module")} Module */
12
/** @typedef {import("./ModuleReason")} ModuleReason */
13

    
14
/** @typedef {{module: Module, loc: TODO, request: string}} OriginRecord */
15
/** @typedef {string|{name: string}} ChunkGroupOptions */
16

    
17
let debugId = 5000;
18

    
19
/**
20
 * @template T
21
 * @param {SortableSet<T>} set set to convert to array.
22
 * @returns {T[]} the array format of existing set
23
 */
24
const getArray = set => Array.from(set);
25

    
26
/**
27
 * A convenience method used to sort chunks based on their id's
28
 * @param {ChunkGroup} a first sorting comparator
29
 * @param {ChunkGroup} b second sorting comparator
30
 * @returns {1|0|-1} a sorting index to determine order
31
 */
32
const sortById = (a, b) => {
33
	if (a.id < b.id) return -1;
34
	if (b.id < a.id) return 1;
35
	return 0;
36
};
37

    
38
/**
39
 * @param {OriginRecord} a the first comparator in sort
40
 * @param {OriginRecord} b the second comparator in sort
41
 * @returns {1|-1|0} returns sorting order as index
42
 */
43
const sortOrigin = (a, b) => {
44
	const aIdent = a.module ? a.module.identifier() : "";
45
	const bIdent = b.module ? b.module.identifier() : "";
46
	if (aIdent < bIdent) return -1;
47
	if (aIdent > bIdent) return 1;
48
	return compareLocations(a.loc, b.loc);
49
};
50

    
51
class ChunkGroup {
52
	/**
53
	 * Creates an instance of ChunkGroup.
54
	 * @param {ChunkGroupOptions=} options chunk group options passed to chunkGroup
55
	 */
56
	constructor(options) {
57
		if (typeof options === "string") {
58
			options = { name: options };
59
		} else if (!options) {
60
			options = { name: undefined };
61
		}
62
		/** @type {number} */
63
		this.groupDebugId = debugId++;
64
		this.options = options;
65
		/** @type {SortableSet<ChunkGroup>} */
66
		this._children = new SortableSet(undefined, sortById);
67
		this._parents = new SortableSet(undefined, sortById);
68
		this._blocks = new SortableSet();
69
		/** @type {Chunk[]} */
70
		this.chunks = [];
71
		/** @type {OriginRecord[]} */
72
		this.origins = [];
73
		/** Indices in top-down order */
74
		/** @private @type {Map<Module, number>} */
75
		this._moduleIndices = new Map();
76
		/** Indices in bottom-up order */
77
		/** @private @type {Map<Module, number>} */
78
		this._moduleIndices2 = new Map();
79
	}
80

    
81
	/**
82
	 * when a new chunk is added to a chunkGroup, addingOptions will occur.
83
	 * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions
84
	 * @returns {void}
85
	 */
86
	addOptions(options) {
87
		for (const key of Object.keys(options)) {
88
			if (this.options[key] === undefined) {
89
				this.options[key] = options[key];
90
			} else if (this.options[key] !== options[key]) {
91
				if (key.endsWith("Order")) {
92
					this.options[key] = Math.max(this.options[key], options[key]);
93
				} else {
94
					throw new Error(
95
						`ChunkGroup.addOptions: No option merge strategy for ${key}`
96
					);
97
				}
98
			}
99
		}
100
	}
101

    
102
	/**
103
	 * returns the name of current ChunkGroup
104
	 * @returns {string|undefined} returns the ChunkGroup name
105
	 */
106
	get name() {
107
		return this.options.name;
108
	}
109

    
110
	/**
111
	 * sets a new name for current ChunkGroup
112
	 * @param {string} value the new name for ChunkGroup
113
	 * @returns {void}
114
	 */
115
	set name(value) {
116
		this.options.name = value;
117
	}
118

    
119
	/**
120
	 * get a uniqueId for ChunkGroup, made up of its member Chunk debugId's
121
	 * @returns {string} a unique concatenation of chunk debugId's
122
	 */
123
	get debugId() {
124
		return Array.from(this.chunks, x => x.debugId).join("+");
125
	}
126

    
127
	/**
128
	 * get a unique id for ChunkGroup, made up of its member Chunk id's
129
	 * @returns {string} a unique concatenation of chunk ids
130
	 */
131
	get id() {
132
		return Array.from(this.chunks, x => x.id).join("+");
133
	}
134

    
135
	/**
136
	 * Performs an unshift of a specific chunk
137
	 * @param {Chunk} chunk chunk being unshifted
138
	 * @returns {boolean} returns true if attempted chunk shift is accepted
139
	 */
140
	unshiftChunk(chunk) {
141
		const oldIdx = this.chunks.indexOf(chunk);
142
		if (oldIdx > 0) {
143
			this.chunks.splice(oldIdx, 1);
144
			this.chunks.unshift(chunk);
145
		} else if (oldIdx < 0) {
146
			this.chunks.unshift(chunk);
147
			return true;
148
		}
149
		return false;
150
	}
151

    
152
	/**
153
	 * inserts a chunk before another existing chunk in group
154
	 * @param {Chunk} chunk Chunk being inserted
155
	 * @param {Chunk} before Placeholder/target chunk marking new chunk insertion point
156
	 * @returns {boolean} return true if insertion was successful
157
	 */
158
	insertChunk(chunk, before) {
159
		const oldIdx = this.chunks.indexOf(chunk);
160
		const idx = this.chunks.indexOf(before);
161
		if (idx < 0) {
162
			throw new Error("before chunk not found");
163
		}
164
		if (oldIdx >= 0 && oldIdx > idx) {
165
			this.chunks.splice(oldIdx, 1);
166
			this.chunks.splice(idx, 0, chunk);
167
		} else if (oldIdx < 0) {
168
			this.chunks.splice(idx, 0, chunk);
169
			return true;
170
		}
171
		return false;
172
	}
173

    
174
	/**
175
	 * add a chunk into ChunkGroup. Is pushed on or prepended
176
	 * @param {Chunk} chunk chunk being pushed into ChunkGroupS
177
	 * @returns {boolean} returns true if chunk addition was successful.
178
	 */
179
	pushChunk(chunk) {
180
		const oldIdx = this.chunks.indexOf(chunk);
181
		if (oldIdx >= 0) {
182
			return false;
183
		}
184
		this.chunks.push(chunk);
185
		return true;
186
	}
187

    
188
	/**
189
	 * @param {Chunk} oldChunk chunk to be replaced
190
	 * @param {Chunk} newChunk New chunk that will be replaced with
191
	 * @returns {boolean} returns true if the replacement was successful
192
	 */
193
	replaceChunk(oldChunk, newChunk) {
194
		const oldIdx = this.chunks.indexOf(oldChunk);
195
		if (oldIdx < 0) return false;
196
		const newIdx = this.chunks.indexOf(newChunk);
197
		if (newIdx < 0) {
198
			this.chunks[oldIdx] = newChunk;
199
			return true;
200
		}
201
		if (newIdx < oldIdx) {
202
			this.chunks.splice(oldIdx, 1);
203
			return true;
204
		} else if (newIdx !== oldIdx) {
205
			this.chunks[oldIdx] = newChunk;
206
			this.chunks.splice(newIdx, 1);
207
			return true;
208
		}
209
	}
210

    
211
	removeChunk(chunk) {
212
		const idx = this.chunks.indexOf(chunk);
213
		if (idx >= 0) {
214
			this.chunks.splice(idx, 1);
215
			return true;
216
		}
217
		return false;
218
	}
219

    
220
	isInitial() {
221
		return false;
222
	}
223

    
224
	addChild(chunk) {
225
		if (this._children.has(chunk)) {
226
			return false;
227
		}
228
		this._children.add(chunk);
229
		return true;
230
	}
231

    
232
	getChildren() {
233
		return this._children.getFromCache(getArray);
234
	}
235

    
236
	getNumberOfChildren() {
237
		return this._children.size;
238
	}
239

    
240
	get childrenIterable() {
241
		return this._children;
242
	}
243

    
244
	removeChild(chunk) {
245
		if (!this._children.has(chunk)) {
246
			return false;
247
		}
248

    
249
		this._children.delete(chunk);
250
		chunk.removeParent(this);
251
		return true;
252
	}
253

    
254
	addParent(parentChunk) {
255
		if (!this._parents.has(parentChunk)) {
256
			this._parents.add(parentChunk);
257
			return true;
258
		}
259
		return false;
260
	}
261

    
262
	getParents() {
263
		return this._parents.getFromCache(getArray);
264
	}
265

    
266
	setParents(newParents) {
267
		this._parents.clear();
268
		for (const p of newParents) {
269
			this._parents.add(p);
270
		}
271
	}
272

    
273
	getNumberOfParents() {
274
		return this._parents.size;
275
	}
276

    
277
	hasParent(parent) {
278
		return this._parents.has(parent);
279
	}
280

    
281
	get parentsIterable() {
282
		return this._parents;
283
	}
284

    
285
	removeParent(chunk) {
286
		if (this._parents.delete(chunk)) {
287
			chunk.removeChunk(this);
288
			return true;
289
		}
290
		return false;
291
	}
292

    
293
	/**
294
	 * @returns {Array} - an array containing the blocks
295
	 */
296
	getBlocks() {
297
		return this._blocks.getFromCache(getArray);
298
	}
299

    
300
	getNumberOfBlocks() {
301
		return this._blocks.size;
302
	}
303

    
304
	hasBlock(block) {
305
		return this._blocks.has(block);
306
	}
307

    
308
	get blocksIterable() {
309
		return this._blocks;
310
	}
311

    
312
	addBlock(block) {
313
		if (!this._blocks.has(block)) {
314
			this._blocks.add(block);
315
			return true;
316
		}
317
		return false;
318
	}
319

    
320
	addOrigin(module, loc, request) {
321
		this.origins.push({
322
			module,
323
			loc,
324
			request
325
		});
326
	}
327

    
328
	containsModule(module) {
329
		for (const chunk of this.chunks) {
330
			if (chunk.containsModule(module)) return true;
331
		}
332
		return false;
333
	}
334

    
335
	getFiles() {
336
		const files = new Set();
337

    
338
		for (const chunk of this.chunks) {
339
			for (const file of chunk.files) {
340
				files.add(file);
341
			}
342
		}
343

    
344
		return Array.from(files);
345
	}
346

    
347
	/**
348
	 * @param {string=} reason reason for removing ChunkGroup
349
	 * @returns {void}
350
	 */
351
	remove(reason) {
352
		// cleanup parents
353
		for (const parentChunkGroup of this._parents) {
354
			// remove this chunk from its parents
355
			parentChunkGroup._children.delete(this);
356

    
357
			// cleanup "sub chunks"
358
			for (const chunkGroup of this._children) {
359
				/**
360
				 * remove this chunk as "intermediary" and connect
361
				 * it "sub chunks" and parents directly
362
				 */
363
				// add parent to each "sub chunk"
364
				chunkGroup.addParent(parentChunkGroup);
365
				// add "sub chunk" to parent
366
				parentChunkGroup.addChild(chunkGroup);
367
			}
368
		}
369

    
370
		/**
371
		 * we need to iterate again over the children
372
		 * to remove this from the child's parents.
373
		 * This can not be done in the above loop
374
		 * as it is not guaranteed that `this._parents` contains anything.
375
		 */
376
		for (const chunkGroup of this._children) {
377
			// remove this as parent of every "sub chunk"
378
			chunkGroup._parents.delete(this);
379
		}
380

    
381
		// cleanup blocks
382
		for (const block of this._blocks) {
383
			block.chunkGroup = null;
384
		}
385

    
386
		// remove chunks
387
		for (const chunk of this.chunks) {
388
			chunk.removeGroup(this);
389
		}
390
	}
391

    
392
	sortItems() {
393
		this.origins.sort(sortOrigin);
394
		this._parents.sort();
395
		this._children.sort();
396
	}
397

    
398
	/**
399
	 * Sorting predicate which allows current ChunkGroup to be compared against another.
400
	 * Sorting values are based off of number of chunks in ChunkGroup.
401
	 *
402
	 * @param {ChunkGroup} otherGroup the chunkGroup to compare this against
403
	 * @returns {-1|0|1} sort position for comparison
404
	 */
405
	compareTo(otherGroup) {
406
		if (this.chunks.length > otherGroup.chunks.length) return -1;
407
		if (this.chunks.length < otherGroup.chunks.length) return 1;
408
		const a = this.chunks[Symbol.iterator]();
409
		const b = otherGroup.chunks[Symbol.iterator]();
410
		// eslint-disable-next-line no-constant-condition
411
		while (true) {
412
			const aItem = a.next();
413
			const bItem = b.next();
414
			if (aItem.done) return 0;
415
			const cmp = aItem.value.compareTo(bItem.value);
416
			if (cmp !== 0) return cmp;
417
		}
418
	}
419

    
420
	getChildrenByOrders() {
421
		const lists = new Map();
422
		for (const childGroup of this._children) {
423
			// TODO webpack 5 remove this check for options
424
			if (typeof childGroup.options === "object") {
425
				for (const key of Object.keys(childGroup.options)) {
426
					if (key.endsWith("Order")) {
427
						const name = key.substr(0, key.length - "Order".length);
428
						let list = lists.get(name);
429
						if (list === undefined) {
430
							lists.set(name, (list = []));
431
						}
432
						list.push({
433
							order: childGroup.options[key],
434
							group: childGroup
435
						});
436
					}
437
				}
438
			}
439
		}
440
		const result = Object.create(null);
441
		for (const [name, list] of lists) {
442
			list.sort((a, b) => {
443
				const cmp = b.order - a.order;
444
				if (cmp !== 0) return cmp;
445
				// TODO webpack 5 remove this check of compareTo
446
				if (a.group.compareTo) {
447
					return a.group.compareTo(b.group);
448
				}
449
				return 0;
450
			});
451
			result[name] = list.map(i => i.group);
452
		}
453
		return result;
454
	}
455

    
456
	/**
457
	 * Sets the top-down index of a module in this ChunkGroup
458
	 * @param {Module} module module for which the index should be set
459
	 * @param {number} index the index of the module
460
	 * @returns {void}
461
	 */
462
	setModuleIndex(module, index) {
463
		this._moduleIndices.set(module, index);
464
	}
465

    
466
	/**
467
	 * Gets the top-down index of a module in this ChunkGroup
468
	 * @param {Module} module the module
469
	 * @returns {number} index
470
	 */
471
	getModuleIndex(module) {
472
		return this._moduleIndices.get(module);
473
	}
474

    
475
	/**
476
	 * Sets the bottom-up index of a module in this ChunkGroup
477
	 * @param {Module} module module for which the index should be set
478
	 * @param {number} index the index of the module
479
	 * @returns {void}
480
	 */
481
	setModuleIndex2(module, index) {
482
		this._moduleIndices2.set(module, index);
483
	}
484

    
485
	/**
486
	 * Gets the bottom-up index of a module in this ChunkGroup
487
	 * @param {Module} module the module
488
	 * @returns {number} index
489
	 */
490
	getModuleIndex2(module) {
491
		return this._moduleIndices2.get(module);
492
	}
493

    
494
	checkConstraints() {
495
		const chunk = this;
496
		for (const child of chunk._children) {
497
			if (!child._parents.has(chunk)) {
498
				throw new Error(
499
					`checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`
500
				);
501
			}
502
		}
503
		for (const parentChunk of chunk._parents) {
504
			if (!parentChunk._children.has(chunk)) {
505
				throw new Error(
506
					`checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`
507
				);
508
			}
509
		}
510
	}
511
}
512

    
513
module.exports = ChunkGroup;
(12-12/145)