mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			171 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Tobias Koppers @sokra
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef {Object} GroupOptions
 | 
						|
 * @property {boolean=} groupChildren
 | 
						|
 * @property {boolean=} force
 | 
						|
 * @property {number=} targetGroupCount
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @template T
 | 
						|
 * @template R
 | 
						|
 * @typedef {Object} GroupConfig
 | 
						|
 * @property {function(T): string[]} getKeys
 | 
						|
 * @property {function(string, (R | T)[], T[]): R} createGroup
 | 
						|
 * @property {function(string, T[]): GroupOptions=} getOptions
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @template T
 | 
						|
 * @typedef {Object} ItemWithGroups
 | 
						|
 * @property {T} item
 | 
						|
 * @property {Set<string>} groups
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @template T
 | 
						|
 * @template R
 | 
						|
 * @param {T[]} items the list of items
 | 
						|
 * @param {GroupConfig<T, R>[]} groupConfigs configuration
 | 
						|
 * @returns {(R | T)[]} grouped items
 | 
						|
 */
 | 
						|
const smartGrouping = (items, groupConfigs) => {
 | 
						|
	/** @type {Set<ItemWithGroups<T>>} */
 | 
						|
	const itemsWithGroups = new Set();
 | 
						|
	/** @type {Map<string, [GroupConfig<T, R>, string]>} */
 | 
						|
	const groupConfigMap = new Map();
 | 
						|
	for (const item of items) {
 | 
						|
		const groups = new Set();
 | 
						|
		for (let i = 0; i < groupConfigs.length; i++) {
 | 
						|
			const groupConfig = groupConfigs[i];
 | 
						|
			const keys = groupConfig.getKeys(item);
 | 
						|
			if (keys) {
 | 
						|
				for (const group of keys) {
 | 
						|
					const fullGroup = `${i}:${group}`;
 | 
						|
					if (!groupConfigMap.has(fullGroup)) {
 | 
						|
						groupConfigMap.set(fullGroup, [groupConfig, group]);
 | 
						|
					}
 | 
						|
					groups.add(fullGroup);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		itemsWithGroups.add({
 | 
						|
			item,
 | 
						|
			groups
 | 
						|
		});
 | 
						|
	}
 | 
						|
	const alreadyGrouped = new Set();
 | 
						|
	/**
 | 
						|
	 * @param {Set<ItemWithGroups<T>>} itemsWithGroups input items with groups
 | 
						|
	 * @returns {(T | R)[]} groups items
 | 
						|
	 */
 | 
						|
	const runGrouping = itemsWithGroups => {
 | 
						|
		const totalSize = itemsWithGroups.size;
 | 
						|
		/** @type {Map<string, Set<ItemWithGroups<T>>>} */
 | 
						|
		const groupMap = new Map();
 | 
						|
		for (const entry of itemsWithGroups) {
 | 
						|
			for (const group of entry.groups) {
 | 
						|
				if (alreadyGrouped.has(group)) continue;
 | 
						|
				const list = groupMap.get(group);
 | 
						|
				if (list === undefined) {
 | 
						|
					groupMap.set(group, new Set([entry]));
 | 
						|
				} else {
 | 
						|
					list.add(entry);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		/** @type {Set<string>} */
 | 
						|
		const usedGroups = new Set();
 | 
						|
		/** @type {(T | R)[]} */
 | 
						|
		const results = [];
 | 
						|
		for (;;) {
 | 
						|
			let bestGroup = undefined;
 | 
						|
			let bestGroupSize = -1;
 | 
						|
			let bestGroupItems = undefined;
 | 
						|
			let bestGroupOptions = undefined;
 | 
						|
			for (const [group, items] of groupMap) {
 | 
						|
				if (items.size === 0) continue;
 | 
						|
				const [groupConfig, groupKey] = groupConfigMap.get(group);
 | 
						|
				const options =
 | 
						|
					groupConfig.getOptions &&
 | 
						|
					groupConfig.getOptions(
 | 
						|
						groupKey,
 | 
						|
						Array.from(items, ({ item }) => item)
 | 
						|
					);
 | 
						|
				const force = options && options.force;
 | 
						|
				if (!force) {
 | 
						|
					if (bestGroupOptions && bestGroupOptions.force) continue;
 | 
						|
					if (usedGroups.has(group)) continue;
 | 
						|
					if (items.size <= 1 || totalSize - items.size <= 1) {
 | 
						|
						continue;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				const targetGroupCount = (options && options.targetGroupCount) || 4;
 | 
						|
				let sizeValue = force
 | 
						|
					? items.size
 | 
						|
					: Math.min(
 | 
						|
							items.size,
 | 
						|
							(totalSize * 2) / targetGroupCount +
 | 
						|
								itemsWithGroups.size -
 | 
						|
								items.size
 | 
						|
					  );
 | 
						|
				if (
 | 
						|
					sizeValue > bestGroupSize ||
 | 
						|
					(force && (!bestGroupOptions || !bestGroupOptions.force))
 | 
						|
				) {
 | 
						|
					bestGroup = group;
 | 
						|
					bestGroupSize = sizeValue;
 | 
						|
					bestGroupItems = items;
 | 
						|
					bestGroupOptions = options;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (bestGroup === undefined) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			const items = new Set(bestGroupItems);
 | 
						|
			const options = bestGroupOptions;
 | 
						|
 | 
						|
			const groupChildren = !options || options.groupChildren !== false;
 | 
						|
 | 
						|
			for (const item of items) {
 | 
						|
				itemsWithGroups.delete(item);
 | 
						|
				// Remove all groups that items have from the map to not select them again
 | 
						|
				for (const group of item.groups) {
 | 
						|
					const list = groupMap.get(group);
 | 
						|
					if (list !== undefined) list.delete(item);
 | 
						|
					if (groupChildren) {
 | 
						|
						usedGroups.add(group);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			groupMap.delete(bestGroup);
 | 
						|
 | 
						|
			const idx = bestGroup.indexOf(":");
 | 
						|
			const configKey = bestGroup.slice(0, idx);
 | 
						|
			const key = bestGroup.slice(idx + 1);
 | 
						|
			const groupConfig = groupConfigs[+configKey];
 | 
						|
 | 
						|
			const allItems = Array.from(items, ({ item }) => item);
 | 
						|
 | 
						|
			alreadyGrouped.add(bestGroup);
 | 
						|
			const children = groupChildren ? runGrouping(items) : allItems;
 | 
						|
			alreadyGrouped.delete(bestGroup);
 | 
						|
 | 
						|
			results.push(groupConfig.createGroup(key, children, allItems));
 | 
						|
		}
 | 
						|
		for (const { item } of itemsWithGroups) {
 | 
						|
			results.push(item);
 | 
						|
		}
 | 
						|
		return results;
 | 
						|
	};
 | 
						|
	return runGrouping(itemsWithGroups);
 | 
						|
};
 | 
						|
 | 
						|
module.exports = smartGrouping;
 |