package.lib.stats.StatsFactory.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of webpack Show documentation
Show all versions of webpack Show documentation
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.
The newest version!
/*
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("../Compilation")} Compilation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("../util/smartGrouping").GroupConfig} GroupConfig */
/**
* @typedef {object} KnownStatsFactoryContext
* @property {string} type
* @property {function(string): string=} makePathsRelative
* @property {Compilation=} compilation
* @property {Set=} rootModules
* @property {Map=} compilationFileToChunks
* @property {Map=} compilationAuxiliaryFileToChunks
* @property {RuntimeSpec=} runtime
* @property {function(Compilation): WebpackError[]=} cachedGetErrors
* @property {function(Compilation): WebpackError[]=} cachedGetWarnings
*/
/** @typedef {KnownStatsFactoryContext & Record} StatsFactoryContext */
class StatsFactory {
constructor() {
this.hooks = Object.freeze({
/** @type {HookMap>} */
extract: new HookMap(
() => new SyncBailHook(["object", "data", "context"])
),
/** @type {HookMap>} */
filter: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap>} */
sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
/** @type {HookMap>} */
filterSorted: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap>} */
groupResults: new HookMap(
() => new SyncBailHook(["groupConfigs", "context"])
),
/** @type {HookMap>} */
sortResults: new HookMap(
() => new SyncBailHook(["comparators", "context"])
),
/** @type {HookMap>} */
filterResults: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap>} */
merge: new HookMap(() => new SyncBailHook(["items", "context"])),
/** @type {HookMap>} */
result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
/** @type {HookMap>} */
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
/** @type {HookMap>} */
getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
});
const hooks = this.hooks;
this._caches =
/** @type {Record[]>>} */ ({});
for (const key of Object.keys(hooks)) {
this._caches[key] = new Map();
}
this._inCreate = false;
}
_getAllLevelHooks(hookMap, cache, type) {
const cacheEntry = cache.get(type);
if (cacheEntry !== undefined) {
return cacheEntry;
}
const hooks = [];
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
if (hook) {
hooks.push(hook);
}
}
cache.set(type, hooks);
return hooks;
}
_forEachLevel(hookMap, cache, type, fn) {
for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
const result = fn(hook);
if (result !== undefined) return result;
}
}
_forEachLevelWaterfall(hookMap, cache, type, data, fn) {
for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
data = fn(hook, data);
}
return data;
}
_forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) {
const hooks = this._getAllLevelHooks(hookMap, cache, type);
if (hooks.length === 0) return forceClone ? items.slice() : items;
let i = 0;
return items.filter((item, idx) => {
for (const hook of hooks) {
const r = fn(hook, item, idx, i);
if (r !== undefined) {
if (r) i++;
return r;
}
}
i++;
return true;
});
}
/**
* @param {string} type type
* @param {any} data factory data
* @param {Omit} baseContext context used as base
* @returns {any} created object
*/
create(type, data, baseContext) {
if (this._inCreate) {
return this._create(type, data, baseContext);
} else {
try {
this._inCreate = true;
return this._create(type, data, baseContext);
} finally {
for (const key of Object.keys(this._caches)) this._caches[key].clear();
this._inCreate = false;
}
}
}
_create(type, data, baseContext) {
const context = {
...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
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) => {
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
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
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)
);
} else {
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;