`third_party/node/node_modules` is generated by `./mach vendor node`. Differential Revision: https://phabricator.services.mozilla.com/D320322
390 lines
13 KiB
JavaScript
390 lines
13 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Haijie Xie @hai-x
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const Dependency = require("./Dependency");
|
|
|
|
/** @typedef {import("./Compilation")} Compilation */
|
|
/** @typedef {import("./Compilation").DependencyConstructor} DependencyConstructor */
|
|
/** @typedef {import("./Module")} Module */
|
|
/** @typedef {import("./ModuleFactory")} ModuleFactory */
|
|
|
|
/**
|
|
* Defines the deferred request group type used by this module.
|
|
* @typedef {object} DependencyGroup
|
|
* @property {ModuleFactory} factory factory for the request
|
|
* @property {Dependency[]} dependencies deferred dependencies of the request
|
|
* @property {string | undefined} context request context
|
|
*/
|
|
|
|
/**
|
|
* Defines the lazy barrel state type used by this module.
|
|
* @typedef {object} LazyBarrelState
|
|
* @property {Set<string> | true} forwardedIds export names requested so far, true for all
|
|
* @property {LazyBarrelInfo | undefined} lazyBarrelInfo deferred dependencies, undefined until classified
|
|
*/
|
|
|
|
/**
|
|
* Defines the lazy barrel unlazy item type used by this module.
|
|
* @typedef {object} UnlazyDependencyInfo
|
|
* @property {ModuleFactory} factory factory for the request
|
|
* @property {Dependency[]} dependencies deferred dependencies of the request
|
|
* @property {string | undefined} context request context
|
|
* @property {Module} originModule the lazy barrel module
|
|
*/
|
|
|
|
/**
|
|
* Collects the export names a dependency group requests from its target.
|
|
* @param {Dependency[]} dependencies dependency group resolving to one module
|
|
* @returns {Set<string> | true} requested export names, true for all
|
|
*/
|
|
function getForwardedIds(dependencies) {
|
|
/** @type {Set<string>} */
|
|
const ids = new Set();
|
|
for (const dependency of dependencies) {
|
|
// TODO remove in webpack 6
|
|
// It may be missing on custom dependency types not extending the base Dependency
|
|
if (!("getForwardId" in dependency)) continue;
|
|
|
|
const id = dependency.getForwardId();
|
|
if (id === true) return true;
|
|
if (id !== null) ids.add(id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/**
|
|
* Tracks the deferred (not yet factorized/built) dependencies of a
|
|
* side-effect-free barrel module and which export names resolve to them.
|
|
*/
|
|
class LazyBarrelInfo {
|
|
constructor() {
|
|
/** @type {Map<string, string>} forward id -> request key */
|
|
this._forwardIdToRequest = new Map();
|
|
/** @type {Map<string, DependencyGroup>} request key -> still-deferred group */
|
|
this._requestToDepGroup = new Map();
|
|
/** @type {Set<string> | undefined} locally provided export names */
|
|
this._terminalIds = undefined;
|
|
/** @type {Set<string> | undefined} requests of star re-exports */
|
|
this._fallbackRequests = undefined;
|
|
}
|
|
|
|
/**
|
|
* Records a deferred dependency under its request key.
|
|
* @param {string} requestKey request key
|
|
* @param {Dependency} dependency dependency to defer
|
|
* @param {ModuleFactory} factory factory for the request
|
|
* @param {string | undefined} context request context
|
|
*/
|
|
addLazy(requestKey, dependency, factory, context) {
|
|
let group = this._requestToDepGroup.get(requestKey);
|
|
if (group === undefined) {
|
|
group = { factory, dependencies: [], context };
|
|
this._requestToDepGroup.set(requestKey, group);
|
|
}
|
|
group.dependencies.push(dependency);
|
|
}
|
|
|
|
/**
|
|
* Maps an export name to the request providing it.
|
|
* @param {string} id export name
|
|
* @param {string} requestKey request key
|
|
*/
|
|
addForwardId(id, requestKey) {
|
|
this._forwardIdToRequest.set(id, requestKey);
|
|
}
|
|
|
|
/**
|
|
* Registers a locally provided export name.
|
|
* @param {string} id export name
|
|
*/
|
|
addTerminal(id) {
|
|
if (this._terminalIds === undefined) this._terminalIds = new Set();
|
|
this._terminalIds.add(id);
|
|
}
|
|
|
|
/**
|
|
* Registers a star re-export request key.
|
|
* @param {string} requestKey request key
|
|
*/
|
|
addFallback(requestKey) {
|
|
if (this._fallbackRequests === undefined) {
|
|
this._fallbackRequests = new Set();
|
|
}
|
|
this._fallbackRequests.add(requestKey);
|
|
}
|
|
|
|
/**
|
|
* Returns whether nothing is deferred.
|
|
* @returns {boolean} true, when no dependency is deferred
|
|
*/
|
|
isEmpty() {
|
|
return this._requestToDepGroup.size === 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the requests of the still-lazy groups.
|
|
* @returns {Iterable<string>} still-lazy requests
|
|
*/
|
|
getLazyRequests() {
|
|
return this._requestToDepGroup.keys();
|
|
}
|
|
|
|
/**
|
|
* Drops the export-name lookups once no group stays deferred; they only serve
|
|
* to find still-deferred groups, so keeping them per module wastes memory.
|
|
*/
|
|
_release() {
|
|
this._forwardIdToRequest.clear();
|
|
this._terminalIds = undefined;
|
|
this._fallbackRequests = undefined;
|
|
}
|
|
|
|
/**
|
|
* Removes and returns the groups needed to provide the requested export names.
|
|
* @param {Set<string> | true} forwardedIds requested export names, true for all
|
|
* @returns {DependencyGroup[]} groups that must be processed now
|
|
*/
|
|
request(forwardedIds) {
|
|
/** @type {DependencyGroup[]} */
|
|
const result = [];
|
|
/**
|
|
* @param {DependencyGroup} group taken group
|
|
*/
|
|
const unlazy = (group) => {
|
|
for (const dependency of group.dependencies) {
|
|
dependency.setLazy(false);
|
|
}
|
|
result.push(group);
|
|
};
|
|
if (forwardedIds === true) {
|
|
for (const group of this._requestToDepGroup.values()) unlazy(group);
|
|
this._requestToDepGroup.clear();
|
|
this._release();
|
|
return result;
|
|
}
|
|
/**
|
|
* @param {string} requestKey request key to take
|
|
*/
|
|
const take = (requestKey) => {
|
|
const group = this._requestToDepGroup.get(requestKey);
|
|
if (group === undefined) return;
|
|
this._requestToDepGroup.delete(requestKey);
|
|
unlazy(group);
|
|
};
|
|
for (const id of forwardedIds) {
|
|
if (this._terminalIds !== undefined && this._terminalIds.has(id)) {
|
|
continue;
|
|
}
|
|
const requestKey = this._forwardIdToRequest.get(id);
|
|
if (requestKey !== undefined) {
|
|
take(requestKey);
|
|
} else if (this._fallbackRequests !== undefined) {
|
|
// unknown name: any star re-export may provide it
|
|
for (const fallbackKey of this._fallbackRequests) take(fallbackKey);
|
|
}
|
|
}
|
|
if (this._requestToDepGroup.size === 0) this._release();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
const esmDependencyCategory = "esm";
|
|
|
|
/**
|
|
* Owns the per side-effect-free module lazy barrel state for a `Compilation`,
|
|
* keeping the deferral bookkeeping out of `Compilation` itself.
|
|
*/
|
|
class LazyBarrelController {
|
|
/**
|
|
* @param {Compilation} compilation the owning compilation
|
|
*/
|
|
constructor(compilation) {
|
|
/** @type {Compilation} */
|
|
this._compilation = compilation;
|
|
/** @type {WeakMap<Module, LazyBarrelState>} */
|
|
this._modules = new WeakMap();
|
|
}
|
|
|
|
/**
|
|
* Releases all per-module deferral state. The last consumer is
|
|
* `FlagDependencyExportsPlugin`'s cache key during `finishModules`, so the
|
|
* bookkeeping is dead weight once sealing starts.
|
|
*/
|
|
clear() {
|
|
this._modules = new WeakMap();
|
|
}
|
|
|
|
/**
|
|
* requests of the re-export targets `module` still keeps lazy.
|
|
* `FlagDependencyExportsPlugin` folds them into its provided-exports cache
|
|
* key, so a barrel whose built targets differ from a cached run does not
|
|
* reuse that run's stale exports.
|
|
* @param {Module} module the module
|
|
* @returns {Iterable<string> | undefined} still-lazy requests, if any
|
|
*/
|
|
getLazyRequests(module) {
|
|
const factoryMeta = module.factoryMeta;
|
|
if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
|
|
const state = this._modules.get(module);
|
|
if (state === undefined) return;
|
|
const info = state.lazyBarrelInfo;
|
|
if (info === undefined || info.isEmpty()) return;
|
|
return info.getLazyRequests();
|
|
}
|
|
|
|
/**
|
|
* Classifies the re-export dependencies of a side-effect-free module (lazy barrel)
|
|
* into deferrable groups and marks the deferred ones.
|
|
* @param {Module} module the module whose dependencies are processed
|
|
* @returns {boolean} true, when some dependencies were deferred
|
|
*/
|
|
classify(module) {
|
|
const modules = this._modules;
|
|
const factoryMeta = module.factoryMeta;
|
|
if (factoryMeta === undefined || !factoryMeta.sideEffectFree) {
|
|
return false;
|
|
}
|
|
const dependencies = module.dependencies;
|
|
const dependencyFactories = this._compilation.dependencyFactories;
|
|
/** @type {LazyBarrelInfo | undefined} */
|
|
let info;
|
|
let hasFallback = false;
|
|
for (const dep of dependencies) {
|
|
// TODO remove in webpack 6
|
|
// It may be missing on custom dependency types not extending the base Dependency
|
|
if (!("getLazyUntil" in dep)) continue;
|
|
|
|
const until = dep.getLazyUntil();
|
|
// null (eager) and LAZY_UNTIL_LOCAL (terminal) defer nothing; terminals are
|
|
// only consulted alongside a star re-export, so they are recorded below
|
|
if (until === null || until === Dependency.LAZY_UNTIL_LOCAL) continue;
|
|
// deferrable: setLazy is needed to toggle its deferred state
|
|
if (!("setLazy" in dep)) continue;
|
|
const resourceIdent = dep.getResourceIdentifier();
|
|
if (resourceIdent === null) continue;
|
|
const factory = dependencyFactories.get(
|
|
/** @type {DependencyConstructor} */ (dep.constructor)
|
|
);
|
|
if (factory === undefined) continue;
|
|
const category = dep.category;
|
|
// Match the request grouping key of `processDependencyForResolving`
|
|
const requestKey =
|
|
category === esmDependencyCategory
|
|
? resourceIdent
|
|
: `${category}${resourceIdent}`;
|
|
if (info === undefined) info = new LazyBarrelInfo();
|
|
if (dep.isLazy()) {
|
|
info.addLazy(requestKey, dep, factory, dep.getContext());
|
|
}
|
|
if (until === Dependency.LAZY_UNTIL_ID) {
|
|
info.addForwardId(
|
|
/** @type {string} */ (dep.getLazyName()),
|
|
requestKey
|
|
);
|
|
} else if (until === Dependency.LAZY_UNTIL_FALLBACK) {
|
|
info.addFallback(requestKey);
|
|
hasFallback = true;
|
|
}
|
|
}
|
|
const state = modules.get(module);
|
|
// info is only created once a deferrable target exists, so it is never empty here
|
|
if (info === undefined) {
|
|
if (state !== undefined) modules.delete(module);
|
|
return false;
|
|
}
|
|
// terminals only matter together with a star re-export (to avoid building it
|
|
// for a locally-provided name); skip the extra pass otherwise
|
|
if (hasFallback) {
|
|
for (const dep of dependencies) {
|
|
if (
|
|
"getLazyUntil" in dep &&
|
|
dep.getLazyUntil() === Dependency.LAZY_UNTIL_LOCAL
|
|
) {
|
|
info.addTerminal(/** @type {string} */ (dep.getLazyName()));
|
|
}
|
|
}
|
|
}
|
|
if (state !== undefined) {
|
|
// barrel classified after its targets were already requested: replay the
|
|
// recorded ids so processDependency un-lazies and builds those targets now
|
|
info.request(state.forwardedIds);
|
|
// stay lazy only while some targets remain deferred
|
|
if (info.isEmpty()) return false;
|
|
state.lazyBarrelInfo = info;
|
|
return true;
|
|
}
|
|
|
|
modules.set(module, {
|
|
forwardedIds: new Set(),
|
|
lazyBarrelInfo: info
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Requests the export names that `dependencies` need from a lazy barrel and
|
|
* returns the deferred re-export targets that must be built now. Records the
|
|
* request when the barrel's dependencies were not classified yet.
|
|
* @param {Module} module the resolved module
|
|
* @param {Dependency[]} dependencies the dependencies that resolved to the module
|
|
* @returns {UnlazyDependencyInfo[] | undefined} items to process, if any
|
|
*/
|
|
request(module, dependencies) {
|
|
// state only exists for side-effect-free modules, so a non-side-effect-free
|
|
// module never has state — skip the WeakMap lookup entirely for it
|
|
const factoryMeta = module.factoryMeta;
|
|
if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
|
|
const modules = this._modules;
|
|
const state = modules.get(module);
|
|
|
|
if (state === undefined) {
|
|
const forwardedIds = getForwardedIds(dependencies);
|
|
if (forwardedIds !== true && forwardedIds.size === 0) return;
|
|
modules.set(module, {
|
|
forwardedIds,
|
|
lazyBarrelInfo: undefined
|
|
});
|
|
return;
|
|
}
|
|
|
|
const forwardedIds = getForwardedIds(dependencies);
|
|
if (forwardedIds !== true && forwardedIds.size === 0) return;
|
|
if (state.forwardedIds !== true) {
|
|
if (forwardedIds === true) state.forwardedIds = true;
|
|
else for (const id of forwardedIds) state.forwardedIds.add(id);
|
|
}
|
|
const info = state.lazyBarrelInfo;
|
|
// Pending requests will be replayed during `classify` later
|
|
if (info === undefined) return;
|
|
const groups = info.request(forwardedIds);
|
|
// drop the per-module state once every deferred target has been built
|
|
if (info.isEmpty()) state.lazyBarrelInfo = undefined;
|
|
if (groups.length === 0) return;
|
|
return groups.map((group) => ({
|
|
factory: group.factory,
|
|
dependencies: group.dependencies,
|
|
context: group.context,
|
|
originModule: module
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Queues the deferred dependency groups of a lazy barrel requested by a single dependency.
|
|
* @param {Module} module the resolved module of the dependency
|
|
* @param {Dependency} dependency the requesting dependency
|
|
* @param {{ factory: ModuleFactory, dependencies: Dependency[], context: string | undefined, originModule: Module | null }[]} sortedDependencies item list to append to
|
|
*/
|
|
unlazyForDependency(module, dependency, sortedDependencies) {
|
|
const unlazyItems = this.request(module, [dependency]);
|
|
if (unlazyItems === undefined) return;
|
|
for (const item of unlazyItems) sortedDependencies.push(item);
|
|
}
|
|
}
|
|
|
|
module.exports = LazyBarrelController;
|