mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			196 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
 | |
| const { concatComparators, keepOriginalOrder } = require("../util/comparators");
 | |
| 
 | |
| const forEachLevel = (hookMap, type, fn) => {
 | |
| 	const typeParts = type.split(".");
 | |
| 	for (let i = 0; i < typeParts.length; i++) {
 | |
| 		const hook = hookMap.get(typeParts.slice(i).join("."));
 | |
| 		if (hook) {
 | |
| 			const result = fn(hook);
 | |
| 			if (result !== undefined) return result;
 | |
| 		}
 | |
| 	}
 | |
| };
 | |
| 
 | |
| const forEachLevelWaterfall = (hookMap, type, data, fn) => {
 | |
| 	const typeParts = type.split(".");
 | |
| 	for (let i = 0; i < typeParts.length; i++) {
 | |
| 		const hook = hookMap.get(typeParts.slice(i).join("."));
 | |
| 		if (hook) {
 | |
| 			data = fn(hook, data);
 | |
| 		}
 | |
| 	}
 | |
| 	return data;
 | |
| };
 | |
| 
 | |
| const forEachLevelFilter = (hookMap, type, items, fn, forceClone) => {
 | |
| 	const typeParts = type.split(".");
 | |
| 	const hooks = [];
 | |
| 	for (let i = 0; i < typeParts.length; i++) {
 | |
| 		const hook = hookMap.get(typeParts.slice(i).join("."));
 | |
| 		if (hook) {
 | |
| 			hooks.push((item, idx, i) => fn(hook, item, idx, i));
 | |
| 		}
 | |
| 	}
 | |
| 	if (hooks.length === 0) return forceClone ? items.slice() : items;
 | |
| 	let i = 0;
 | |
| 	return items.filter((item, idx) => {
 | |
| 		for (const h of hooks) {
 | |
| 			const r = h(item, idx, i);
 | |
| 			if (r !== undefined) {
 | |
| 				if (r) i++;
 | |
| 				return r;
 | |
| 			}
 | |
| 		}
 | |
| 		i++;
 | |
| 		return true;
 | |
| 	});
 | |
| };
 | |
| 
 | |
| class StatsFactory {
 | |
| 	constructor() {
 | |
| 		this.hooks = Object.freeze({
 | |
| 			/** @type {HookMap<Object, any, Object>} */
 | |
| 			extract: new HookMap(
 | |
| 				() => new SyncBailHook(["object", "data", "context"])
 | |
| 			),
 | |
| 			/** @type {HookMap<any, Object, number, number>} */
 | |
| 			filter: new HookMap(
 | |
| 				() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
 | |
| 			),
 | |
| 			/** @type {HookMap<(function(any, any): number)[], Object>} */
 | |
| 			sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
 | |
| 			/** @type {HookMap<any, Object, number, number>} */
 | |
| 			filterSorted: new HookMap(
 | |
| 				() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
 | |
| 			),
 | |
| 			/** @type {HookMap<(function(any, any): number)[], Object>} */
 | |
| 			sortResults: new HookMap(
 | |
| 				() => new SyncBailHook(["comparators", "context"])
 | |
| 			),
 | |
| 			filterResults: new HookMap(
 | |
| 				() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
 | |
| 			),
 | |
| 			/** @type {HookMap<any[], Object>} */
 | |
| 			merge: new HookMap(() => new SyncBailHook(["items", "context"])),
 | |
| 			/** @type {HookMap<any[], Object>} */
 | |
| 			result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
 | |
| 			/** @type {HookMap<any, Object>} */
 | |
| 			getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
 | |
| 			/** @type {HookMap<any, Object>} */
 | |
| 			getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	create(type, data, baseContext) {
 | |
| 		const context = Object.assign({}, baseContext, {
 | |
| 			type,
 | |
| 			[type]: data
 | |
| 		});
 | |
| 		if (Array.isArray(data)) {
 | |
| 			// run filter on unsorted items
 | |
| 			const items = forEachLevelFilter(
 | |
| 				this.hooks.filter,
 | |
| 				type,
 | |
| 				data,
 | |
| 				(h, r, idx, i) => h.call(r, context, idx, i),
 | |
| 				true
 | |
| 			);
 | |
| 
 | |
| 			// sort items
 | |
| 			const comparators = [];
 | |
| 			forEachLevel(this.hooks.sort, type, h => h.call(comparators, context));
 | |
| 			if (comparators.length > 0) {
 | |
| 				items.sort(
 | |
| 					// @ts-ignore number of arguments is correct
 | |
| 					concatComparators(...comparators, keepOriginalOrder(items))
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			// run filter on sorted items
 | |
| 			const items2 = forEachLevelFilter(
 | |
| 				this.hooks.filterSorted,
 | |
| 				type,
 | |
| 				items,
 | |
| 				(h, r, idx, i) => h.call(r, context, idx, i),
 | |
| 				false
 | |
| 			);
 | |
| 
 | |
| 			// for each item
 | |
| 			const resultItems = items2.map((item, i) => {
 | |
| 				const itemContext = Object.assign({}, context, {
 | |
| 					_index: i
 | |
| 				});
 | |
| 
 | |
| 				// run getItemName
 | |
| 				const itemName = forEachLevel(this.hooks.getItemName, `${type}[]`, h =>
 | |
| 					h.call(item, itemContext)
 | |
| 				);
 | |
| 				if (itemName) itemContext[itemName] = item;
 | |
| 				const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;
 | |
| 
 | |
| 				// run getItemFactory
 | |
| 				const itemFactory =
 | |
| 					forEachLevel(this.hooks.getItemFactory, innerType, h =>
 | |
| 						h.call(item, itemContext)
 | |
| 					) || this;
 | |
| 
 | |
| 				// run item factory
 | |
| 				return itemFactory.create(innerType, item, itemContext);
 | |
| 			});
 | |
| 
 | |
| 			// sort result items
 | |
| 			const comparators2 = [];
 | |
| 			forEachLevel(this.hooks.sortResults, type, h =>
 | |
| 				h.call(comparators2, context)
 | |
| 			);
 | |
| 			if (comparators2.length > 0) {
 | |
| 				resultItems.sort(
 | |
| 					// @ts-ignore number of arguments is correct
 | |
| 					concatComparators(...comparators2, keepOriginalOrder(resultItems))
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			// run filter on sorted result items
 | |
| 			const finalResultItems = forEachLevelFilter(
 | |
| 				this.hooks.filterResults,
 | |
| 				type,
 | |
| 				resultItems,
 | |
| 				(h, r, idx, i) => h.call(r, context, idx, i),
 | |
| 				false
 | |
| 			);
 | |
| 
 | |
| 			// run merge on mapped items
 | |
| 			let result = forEachLevel(this.hooks.merge, type, h =>
 | |
| 				h.call(finalResultItems, context)
 | |
| 			);
 | |
| 			if (result === undefined) result = finalResultItems;
 | |
| 
 | |
| 			// run result on merged items
 | |
| 			return forEachLevelWaterfall(this.hooks.result, type, result, (h, r) =>
 | |
| 				h.call(r, context)
 | |
| 			);
 | |
| 		} else {
 | |
| 			const object = {};
 | |
| 
 | |
| 			// run extract on value
 | |
| 			forEachLevel(this.hooks.extract, type, h =>
 | |
| 				h.call(object, data, context)
 | |
| 			);
 | |
| 
 | |
| 			// run result on extracted object
 | |
| 			return forEachLevelWaterfall(this.hooks.result, type, object, (h, r) =>
 | |
| 				h.call(r, context)
 | |
| 			);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| module.exports = StatsFactory;
 |