/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/LazyBarrel.js
389 строк
13 KB
hai-x
fix: invalidate provided-exports cache with lazy barrel (#21326)
03 июл 2026, 23:02
Не верифицирован
03 июл 2026, 23:02
7639066
Код
Авторство
О чём код?
/* 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;