| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Haijie Xie @hai-x
- */
- "use strict";
- const Dependency = require("./Dependency");
- /** @import Compilation, { DependencyConstructor } from "./Compilation" */
- /** @import Module from "./Module" */
- /** @import ModuleFactory from "./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;
|