| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
- const smartGrouping = require("../util/smartGrouping");
- /** @import Chunk from "../Chunk" */
- /** @import { OriginRecord } from "../ChunkGroup" */
- /** @import Compilation, { Asset } from "../Compilation" */
- /** @import Dependency from "../Dependency" */
- /** @import Module from "../Module" */
- /** @import ModuleGraphConnection from "../ModuleGraphConnection" */
- /** @typedef {import("../util/comparators").Comparator<EXPECTED_ANY>} Comparator */
- /** @import ModuleProfile from "../ModuleProfile" */
- /** @import { RuntimeSpec } from "../util/runtime" */
- /**
- * Defines the group config type used by this module.
- * @template T, R
- * @typedef {import("../util/smartGrouping").GroupConfig<T, R>} GroupConfig
- */
- /**
- * @import {
- * ChunkGroupInfoWithName,
- * ModuleTrace,
- * StatsAsset,
- * StatsChunk,
- * StatsChunkGroup,
- * StatsChunkOrigin,
- * StatsCompilation,
- * StatsError,
- * StatsModule,
- * StatsModuleReason,
- * StatsModuleTraceDependency,
- * StatsModuleTraceItem,
- * StatsProfile
- * } from "./DefaultStatsFactoryPlugin"
- */
- /**
- * Defines the known stats factory context type used by this module.
- * @typedef {object} KnownStatsFactoryContext
- * @property {string} type
- * @property {Compilation} compilation
- * @property {(path: string) => string} makePathsRelative
- * @property {Set<Module>} rootModules
- * @property {Map<string, Chunk[]>} compilationFileToChunks
- * @property {Map<string, Chunk[]>} compilationAuxiliaryFileToChunks
- * @property {RuntimeSpec} runtime
- * @property {(compilation: Compilation) => Error[]} cachedGetErrors
- * @property {(compilation: Compilation) => Error[]} cachedGetWarnings
- */
- /** @typedef {KnownStatsFactoryContext & Record<string, EXPECTED_ANY>} StatsFactoryContext */
- // StatsLogging StatsLoggingEntry
- /**
- * Defines the stats object type used by this module.
- * @template T
- * @template F
- * @typedef {T extends Compilation ? StatsCompilation : T extends ChunkGroupInfoWithName ? StatsChunkGroup : T extends Chunk ? StatsChunk : T extends OriginRecord ? StatsChunkOrigin : T extends Module ? StatsModule : T extends ModuleGraphConnection ? StatsModuleReason : T extends Asset ? StatsAsset : T extends ModuleTrace ? StatsModuleTraceItem : T extends Dependency ? StatsModuleTraceDependency : T extends Error ? StatsError : T extends ModuleProfile ? StatsProfile : F} StatsObject
- */
- /**
- * Defines the created object type used by this module.
- * @template T
- * @template F
- * @typedef {T extends ChunkGroupInfoWithName[] ? Record<string, StatsObject<ChunkGroupInfoWithName, F>> : T extends (infer V)[] ? StatsObject<V, F>[] : StatsObject<T, F>} CreatedObject
- */
- /** @typedef {EXPECTED_ANY} ObjectForExtract */
- /** @typedef {EXPECTED_ANY} FactoryData */
- /** @typedef {EXPECTED_ANY} FactoryDataItem */
- /** @typedef {EXPECTED_ANY} Result */
- /**
- * Defines the stats factory hooks type used by this module.
- * @typedef {object} StatsFactoryHooks
- * @property {HookMap<SyncBailHook<[ObjectForExtract, FactoryData, StatsFactoryContext], void>>} extract
- * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filter
- * @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sort
- * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterSorted
- * @property {HookMap<SyncBailHook<[GroupConfig<EXPECTED_ANY, EXPECTED_ANY>[], StatsFactoryContext], void>>} groupResults
- * @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sortResults
- * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterResults
- * @property {HookMap<SyncBailHook<[FactoryDataItem[], StatsFactoryContext], Result | void>>} merge
- * @property {HookMap<SyncBailHook<[Result, StatsFactoryContext], Result>>} result
- * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], string | void>>} getItemName
- * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], StatsFactory | void>>} getItemFactory
- */
- /**
- * Represents the stats factory runtime component.
- * @template T
- * @typedef {Map<string, T[]>} Caches
- */
- class StatsFactory {
- constructor() {
- /** @type {StatsFactoryHooks} */
- this.hooks = Object.freeze({
- extract: new HookMap(
- () => new SyncBailHook(["object", "data", "context"])
- ),
- filter: new HookMap(
- () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
- ),
- sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
- filterSorted: new HookMap(
- () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
- ),
- groupResults: new HookMap(
- () => new SyncBailHook(["groupConfigs", "context"])
- ),
- sortResults: new HookMap(
- () => new SyncBailHook(["comparators", "context"])
- ),
- filterResults: new HookMap(
- () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
- ),
- merge: new HookMap(() => new SyncBailHook(["items", "context"])),
- result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
- getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
- getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
- });
- const hooks = this.hooks;
- this._caches =
- /** @type {{ [Key in keyof StatsFactoryHooks]: StatsFactoryHooks[Key] extends HookMap<infer H> ? Map<string, H[]> : never }} */ ({});
- for (const key of Object.keys(hooks)) {
- this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map();
- }
- /** @type {boolean} */
- this._inCreate = false;
- }
- /**
- * Get all level hooks.
- * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
- * @template {HM extends HookMap<infer H> ? H : never} H
- * @param {HM} hookMap hook map
- * @param {Caches<H>} cache cache
- * @param {string} type type
- * @returns {H[]} hooks
- * @private
- */
- _getAllLevelHooks(hookMap, cache, type) {
- const cacheEntry = cache.get(type);
- if (cacheEntry !== undefined) {
- return cacheEntry;
- }
- const hooks = /** @type {H[]} */ ([]);
- const typeParts = type.split(".");
- for (let i = 0; i < typeParts.length; i++) {
- const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
- if (hook) {
- hooks.push(hook);
- }
- }
- cache.set(type, hooks);
- return hooks;
- }
- /**
- * Returns hook.
- * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
- * @template {HM extends HookMap<infer H> ? H : never} H
- * @template {H extends import("tapable").Hook<infer A, infer R> ? R : never} R
- * @param {HM} hookMap hook map
- * @param {Caches<H>} cache cache
- * @param {string} type type
- * @param {(hook: H) => R | void} fn fn
- * @returns {R | void} hook
- * @private
- */
- _forEachLevel(hookMap, cache, type, fn) {
- for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
- const result = fn(/** @type {H} */ (hook));
- if (result !== undefined) return result;
- }
- }
- /**
- * For each level waterfall.
- * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
- * @template {HM extends HookMap<infer H> ? H : never} H
- * @template [D=EXPECTED_ANY]
- * @param {HM} hookMap hook map
- * @param {Caches<H>} cache cache
- * @param {string} type type
- * @param {D} data data
- * @param {(hook: H, data: D) => D} fn fn
- * @returns {D} data
- * @private
- */
- _forEachLevelWaterfall(hookMap, cache, type, data, fn) {
- for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
- data = fn(/** @type {H} */ (hook), data);
- }
- return data;
- }
- /**
- * For each level filter.
- * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} T
- * @template {T extends HookMap<infer H> ? H : never} H
- * @template [D=EXPECTED_ANY]
- * @param {T} hookMap hook map
- * @param {Caches<H>} cache cache
- * @param {string} type type
- * @param {D[]} items items
- * @param {(hook: H, item: D, idx: number, i: number) => boolean | void} fn fn
- * @param {boolean} forceClone force clone
- * @returns {D[]} result for each level
- * @private
- */
- _forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) {
- const hooks = this._getAllLevelHooks(hookMap, cache, type);
- if (hooks.length === 0) return forceClone ? [...items] : items;
- let i = 0;
- return items.filter((item, idx) => {
- for (const hook of hooks) {
- const r = fn(/** @type {H} */ (hook), item, idx, i);
- if (r !== undefined) {
- if (r) i++;
- return r;
- }
- }
- i++;
- return true;
- });
- }
- /**
- * Returns created object.
- * @template FactoryData
- * @template FallbackCreatedObject
- * @param {string} type type
- * @param {FactoryData} data factory data
- * @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
- * @returns {CreatedObject<FactoryData, FallbackCreatedObject>} created object
- */
- create(type, data, baseContext) {
- if (this._inCreate) {
- return this._create(type, data, baseContext);
- }
- try {
- this._inCreate = true;
- return this._create(type, data, baseContext);
- } finally {
- for (const key of Object.keys(this._caches)) {
- this._caches[/** @type {keyof StatsFactoryHooks} */ (key)].clear();
- }
- this._inCreate = false;
- }
- }
- /**
- * Returns created object.
- * @private
- * @template FactoryData
- * @template FallbackCreatedObject
- * @param {string} type type
- * @param {FactoryData} data factory data
- * @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
- * @returns {CreatedObject<FactoryData, FallbackCreatedObject>} created object
- */
- _create(type, data, baseContext) {
- const context = /** @type {StatsFactoryContext} */ ({
- ...baseContext,
- type,
- [type]: data
- });
- if (Array.isArray(data)) {
- // run filter on unsorted items
- const items = this._forEachLevelFilter(
- this.hooks.filter,
- this._caches.filter,
- type,
- data,
- (h, r, idx, i) => h.call(r, context, idx, i),
- true
- );
- // sort items
- /** @type {Comparator[]} */
- const comparators = [];
- this._forEachLevel(this.hooks.sort, this._caches.sort, type, (h) =>
- h.call(comparators, context)
- );
- if (comparators.length > 0) {
- sortWithOriginalOrder(items, comparators);
- }
- // run filter on sorted items
- const items2 = this._forEachLevelFilter(
- this.hooks.filterSorted,
- this._caches.filterSorted,
- type,
- items,
- (h, r, idx, i) => h.call(r, context, idx, i),
- false
- );
- // reuse one item context; create() spreads it synchronously, so mutating
- // `_index`/the name key between items is safe and skips a per-item spread
- /** @type {StatsFactoryContext} */
- const itemContext = { ...context };
- const itemNameType = `${type}[]`;
- /** @type {string | void} */
- let prevItemName;
- let resultItems = items2.map((item, i) => {
- itemContext._index = i;
- // run getItemName
- const itemName = this._forEachLevel(
- this.hooks.getItemName,
- this._caches.getItemName,
- itemNameType,
- (h) => h.call(item, itemContext)
- );
- // drop a previous item's name key before adding this one's
- if (prevItemName !== undefined && prevItemName !== itemName) {
- delete itemContext[prevItemName];
- }
- if (itemName) itemContext[itemName] = item;
- prevItemName = itemName;
- const innerType = itemName ? `${type}[].${itemName}` : itemNameType;
- // run getItemFactory
- const itemFactory =
- this._forEachLevel(
- this.hooks.getItemFactory,
- this._caches.getItemFactory,
- innerType,
- (h) => h.call(item, itemContext)
- ) || this;
- // run item factory
- return itemFactory.create(innerType, item, itemContext);
- });
- // sort result items
- /** @type {Comparator[]} */
- const comparators2 = [];
- this._forEachLevel(
- this.hooks.sortResults,
- this._caches.sortResults,
- type,
- (h) => h.call(comparators2, context)
- );
- if (comparators2.length > 0) {
- sortWithOriginalOrder(resultItems, comparators2);
- }
- // group result items
- /** @type {GroupConfig<EXPECTED_ANY, EXPECTED_ANY>[]} */
- const groupConfigs = [];
- this._forEachLevel(
- this.hooks.groupResults,
- this._caches.groupResults,
- type,
- (h) => h.call(groupConfigs, context)
- );
- if (groupConfigs.length > 0) {
- resultItems = smartGrouping(resultItems, groupConfigs);
- }
- // run filter on sorted result items
- const finalResultItems = this._forEachLevelFilter(
- this.hooks.filterResults,
- this._caches.filterResults,
- type,
- resultItems,
- (h, r, idx, i) => h.call(r, context, idx, i),
- false
- );
- // run merge on mapped items
- let result = this._forEachLevel(
- this.hooks.merge,
- this._caches.merge,
- type,
- (h) => h.call(finalResultItems, context)
- );
- if (result === undefined) result = finalResultItems;
- // run result on merged items
- return this._forEachLevelWaterfall(
- this.hooks.result,
- this._caches.result,
- type,
- result,
- (h, r) => h.call(r, context)
- );
- }
- /** @type {ObjectForExtract} */
- const object = {};
- // run extract on value
- this._forEachLevel(this.hooks.extract, this._caches.extract, type, (h) =>
- h.call(object, data, context)
- );
- // run result on extracted object
- return this._forEachLevelWaterfall(
- this.hooks.result,
- this._caches.result,
- type,
- object,
- (h, r) => h.call(r, context)
- );
- }
- }
- /**
- * Stable in-place sort applying comparators in order, keeping the original order
- * for equal items. Inlined instead of `concatComparators(...c, keepOriginalOrder())`
- * because that combination is single-use per sort and only thrashes the comparator
- * caches (a fresh tiebreaker closure every call allocates a new cache entry).
- * @param {EXPECTED_ANY[]} items items to sort in place
- * @param {Comparator[]} comparators comparators applied in order
- * @returns {void}
- */
- const sortWithOriginalOrder = (items, comparators) => {
- // original-index tiebreaker keeps the sort stable on engines without a stable Array.sort
- /** @type {Map<EXPECTED_ANY, number>} */
- const originalOrder = new Map();
- for (let i = 0; i < items.length; i++) {
- originalOrder.set(items[i], i);
- }
- const count = comparators.length;
- items.sort((a, b) => {
- for (let i = 0; i < count; i++) {
- const res = comparators[i](a, b);
- if (res !== 0) return res;
- }
- return (
- /** @type {number} */ (originalOrder.get(a)) -
- /** @type {number} */ (originalOrder.get(b))
- );
- });
- };
- module.exports = StatsFactory;
|