UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

419 lines (386 loc) 14.7 kB
/* 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 smartGrouping = require("../util/smartGrouping"); /** @typedef {import("../Chunk")} Chunk */ /** @typedef {import("../ChunkGroup").OriginRecord} OriginRecord */ /** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../Compilation").Asset} Asset */ /** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ /** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../ModuleGraph").ModuleProfile} ModuleProfile */ /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ /** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../util/comparators").Comparator<EXPECTED_ANY>} Comparator */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ /** * Defines the group config type used by this module. * @template T, R * @typedef {import("../util/smartGrouping").GroupConfig<T, R>} GroupConfig */ /** @typedef {import("./DefaultStatsFactoryPlugin").ChunkGroupInfoWithName} ChunkGroupInfoWithName */ /** @typedef {import("./DefaultStatsFactoryPlugin").ModuleIssuerPath} ModuleIssuerPath */ /** @typedef {import("./DefaultStatsFactoryPlugin").ModuleTrace} ModuleTrace */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkOrigin} StatsChunkOrigin */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */ /** @typedef {import("./DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */ /** * 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]: Map<string, SyncBailHook<EXPECTED_ANY, EXPECTED_ANY>[]> }} */ ({}); for (const key of Object.keys(hooks)) { this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map(); } 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<EXPECTED_ANY, 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 * @param {HM} hookMap hook map * @param {Caches<H>} cache cache * @param {string} type type * @param {FactoryData} data data * @param {(hook: H, factoryData: FactoryData) => FactoryData} fn fn * @returns {FactoryData} 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 {H extends import("tapable").Hook<EXPECTED_ANY, infer R> ? R : never} R * @param {T} hookMap hook map * @param {Caches<H>} cache cache * @param {string} type type * @param {FactoryData[]} items items * @param {(hook: H, item: R, idx: number, i: number) => R | undefined} fn fn * @param {boolean} forceClone force clone * @returns {R[]} 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) { items.sort( // @ts-expect-error number of arguments is correct concatComparators(...comparators, keepOriginalOrder(items)) ); } // 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 ); // for each item let resultItems = items2.map((item, i) => { /** @type {StatsFactoryContext} */ const itemContext = { ...context, _index: i }; // run getItemName const itemName = this._forEachLevel( this.hooks.getItemName, this._caches.getItemName, `${type}[]`, (h) => h.call(item, itemContext) ); if (itemName) itemContext[itemName] = item; const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`; // 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) { resultItems.sort( // @ts-expect-error number of arguments is correct concatComparators(...comparators2, keepOriginalOrder(resultItems)) ); } // 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) ); } } module.exports = StatsFactory;