/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/FlagDependencyExportsPlugin.js
566 строк
18 KB
Alexander Akait
fix(html): report comment and DOCTYPE parts from the tokenizer's own states (#21630)
09 авг 2026, 21:23
Не верифицирован
09 авг 2026, 21:23
1b246ec
Код
Авторство
О чём код?
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const asyncLib = require("neo-async"); const Queue = require("./util/Queue"); const { compareModulesByIdentifier } = require("./util/comparators"); const createHash = require("./util/createHash"); /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency").ExportSpec} ExportSpec */ /** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("./ExportsInfo")} ExportsInfo */ /** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */ /** @typedef {import("./ExportsInfo").RestoreProvidedData} RestoreProvidedData */ /** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */ /** @typedef {import("./LazyBarrel")} LazyBarrelController */ /** @typedef {import("./Module")} Module */ /** @typedef {import("./Module").BuildInfo} BuildInfo */ const PLUGIN_NAME = "FlagDependencyExportsPlugin"; const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; /** @typedef {{ etag: string, data: RestoreProvidedData }} MemCacheEntry */ /** * @param {Module} module the module * @param {LazyBarrelController} lazyBarrelController the compilation's lazy barrel controller * @param {HashFunction} hashFunction hash function for long keys * @returns {string} the hash */ const getLazyRequestsHash = (module, lazyBarrelController, hashFunction) => { const lazyKeys = lazyBarrelController.getLazyRequests(module); if (!lazyKeys) return ""; const joined = [...lazyKeys].join("|"); if (joined.length < 100) return joined; const hash = createHash(hashFunction); hash.update(joined); return /** @type {string} */ (hash.digest("hex")); }; class FlagDependencyExportsPlugin { /** * Applies the plugin by registering its hooks on the compiler. * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { const moduleGraph = compilation.moduleGraph; const cache = compilation.getCache(PLUGIN_NAME); const lazyBarrelController = compilation._lazyBarrelController; const hashFunction = compilation.outputOptions.hashFunction; compilation.hooks.finishModules.tapAsync( PLUGIN_NAME, (modules, callback) => { const logger = compilation.getLogger(PLUGIN_LOGGER_NAME); let statRestoredFromMemCache = 0; let statRestoredFromCache = 0; let statNoExports = 0; let statFlaggedUncached = 0; let statNotCached = 0; let statQueueItemsProcessed = 0; const { moduleMemCaches } = compilation; /** @type {Queue<Module>} */ const queue = new Queue(); /** @type {Set<Module>} */ const modulesToAnalyse = new Set(); // Step 1: Try to restore cached provided export info from cache logger.time("restore cached provided exports"); asyncLib.each( /** @type {import("neo-async").IterableCollection<Module>} */ ( /** @type {unknown} */ (modules) ), (module, callback) => { const exportsInfo = moduleGraph.getExportsInfo(module); // If the module doesn't have an exportsType, it's a module // without declared exports. if ( (!module.buildMeta || !module.buildMeta.exportsType) && exportsInfo.otherExportsInfo.provided !== null ) { // It's a module without declared exports statNoExports++; exportsInfo.setHasProvideInfo(); exportsInfo.setUnknownExportsProvided(); return callback(); } // If the module has no hash, it's uncacheable if ( typeof (/** @type {BuildInfo} */ (module.buildInfo).hash) !== "string" ) { statFlaggedUncached++; // Determined below, in a stable order modulesToAnalyse.add(module); exportsInfo.setHasProvideInfo(); return callback(); } const memCache = moduleMemCaches && moduleMemCaches.get(module); /** @type {MemCacheEntry | undefined} */ const memCacheEntry = memCache && memCache.get(this); const hash = getLazyRequestsHash( module, lazyBarrelController, hashFunction ); // validated by the still-lazy request keys: reference-change // detection cannot invalidate the mem cache when the un-lazied // connections are unsafe-cached (they are skipped when comparing // references) if (memCacheEntry !== undefined && memCacheEntry.etag === hash) { statRestoredFromMemCache++; exportsInfo.restoreProvided(memCacheEntry.data); return callback(); } const buildHash = /** @type {string} */ ( /** @type {BuildInfo} */ (module.buildInfo).hash ); cache.get( module.identifier(), hash ? `${buildHash}|${hash}` : buildHash, (err, result) => { if (err) return callback(err); if (result !== undefined) { statRestoredFromCache++; exportsInfo.restoreProvided(result); } else { statNotCached++; // Without cached info determine the exports below modulesToAnalyse.add(module); exportsInfo.setHasProvideInfo(); } callback(); } ); }, (err) => { logger.timeEnd("restore cached provided exports"); if (err) return callback(err); // Seed the queue with targets before the modules that read // them. Enqueueing at the two branches above ordered the work by // how fast each cache lookup answered — one is synchronous, the // other a `cache.get` callback — so the persistent cache decided // it. A module analysed before the module it re-exports from // reads exports that are not determined yet and over-estimates, // and this loop can add exports info but never retract it, so // the over-estimate survives the pass that corrects it. The // roots are sorted so the walk is stable, and post-order visits // each module after everything it points at. /** @type {Set<Module>} */ const visited = new Set(); /** @type {Module[]} */ const stack = []; /** @type {Set<Module>} */ const expanded = new Set(); for (const root of [...modulesToAnalyse].sort( compareModulesByIdentifier )) { if (visited.has(root)) continue; stack.push(root); while (stack.length > 0) { const current = stack[stack.length - 1]; if (expanded.has(current)) { stack.pop(); if (!visited.has(current)) { visited.add(current); if (modulesToAnalyse.has(current)) { queue.enqueue(current); } } continue; } expanded.add(current); for (const connection of moduleGraph.getOutgoingConnections( current )) { const target = connection.module; // A module restored from cache is already final, so // nothing has to be ordered before it and the walk stops // there — on an incremental build that is most of them. if ( target !== undefined && target !== null && modulesToAnalyse.has(target) && !expanded.has(target) ) { stack.push(target); } } } } /** @type {Set<Module>} */ const modulesToStore = new Set(); /** @type {Map<Module, Set<Module>>} */ const dependencies = new Map(); /** @type {Module} */ let module; /** @type {ExportsInfo} */ let exportsInfo; /** @type {Map<Dependency, ExportsSpec>} */ const exportsSpecsFromDependencies = new Map(); let cacheable = true; let changed = false; /** * Process dependencies block. * @param {DependenciesBlock} depBlock the dependencies block * @returns {void} */ const processDependenciesBlock = (depBlock) => { for (const dep of depBlock.dependencies) { processDependency(dep); } for (const block of depBlock.blocks) { processDependenciesBlock(block); } }; /** * Process dependency. * @param {Dependency} dep the dependency * @returns {void} */ const processDependency = (dep) => { const exportDesc = dep.getExports(moduleGraph); if (!exportDesc) return; exportsSpecsFromDependencies.set(dep, exportDesc); }; /** * Process exports spec. * @param {Dependency} dep dependency * @param {ExportsSpec} exportDesc info * @returns {void} */ const processExportsSpec = (dep, exportDesc) => { const exports = exportDesc.exports; const globalCanMangle = exportDesc.canMangle; const globalFrom = exportDesc.from; const globalPriority = exportDesc.priority; const globalTerminalBinding = exportDesc.terminalBinding || false; const globalPure = exportDesc.isPure || false; const exportDeps = exportDesc.dependencies; if (exportDesc.hideExports) { for (const name of exportDesc.hideExports) { const exportInfo = exportsInfo.getExportInfo(name); exportInfo.unsetTarget(dep); } } if (exports === true) { // unknown exports if ( exportsInfo.setUnknownExportsProvided( globalCanMangle, exportDesc.excludeExports, globalFrom && dep, globalFrom, globalPriority ) ) { changed = true; } } else if (Array.isArray(exports)) { /** * merge in new exports * @param {ExportsInfo} exportsInfo own exports info * @param {(ExportSpec | string)[]} exports list of exports */ const mergeExports = (exportsInfo, exports) => { for (const exportNameOrSpec of exports) { /** @type {ExportInfoName} */ let name; let canMangle = globalCanMangle; let terminalBinding = globalTerminalBinding; let pure = globalPure; /** @type {ExportSpec["exports"]} */ let exports; let from = globalFrom; /** @type {ExportSpec["export"]} */ let fromExport; let priority = globalPriority; let hidden = false; /** @type {ExportSpec["inlined"]} */ let inlined; if (typeof exportNameOrSpec === "string") { name = exportNameOrSpec; } else { name = exportNameOrSpec.name; if (exportNameOrSpec.canMangle !== undefined) { canMangle = exportNameOrSpec.canMangle; } if (exportNameOrSpec.export !== undefined) { fromExport = exportNameOrSpec.export; } if (exportNameOrSpec.exports !== undefined) { exports = exportNameOrSpec.exports; } if (exportNameOrSpec.from !== undefined) { from = exportNameOrSpec.from; } if (exportNameOrSpec.priority !== undefined) { priority = exportNameOrSpec.priority; } if (exportNameOrSpec.terminalBinding !== undefined) { terminalBinding = exportNameOrSpec.terminalBinding; } if (exportNameOrSpec.isPure !== undefined) { pure = exportNameOrSpec.isPure; } if (exportNameOrSpec.hidden !== undefined) { hidden = exportNameOrSpec.hidden; } if (exportNameOrSpec.inlined !== undefined) { inlined = exportNameOrSpec.inlined; } } const exportInfo = exportsInfo.getExportInfo(name); if ( exportInfo.provided === false || exportInfo.provided === null ) { exportInfo.provided = true; changed = true; } if ( exportInfo.canMangleProvide !== false && canMangle === false ) { exportInfo.canMangleProvide = false; changed = true; } if ( inlined !== undefined && exportInfo.canInlineProvide === undefined ) { exportInfo.canInlineProvide = inlined; changed = true; } if (terminalBinding && !exportInfo.terminalBinding) { exportInfo.terminalBinding = true; changed = true; } if (pure && exportInfo.pureProvide !== true) { exportInfo.pureProvide = true; changed = true; } if (exports) { const nestedExportsInfo = exportInfo.createNestedExportsInfo(); mergeExports( /** @type {ExportsInfo} */ (nestedExportsInfo), exports ); } if ( from && (hidden ? exportInfo.unsetTarget(dep) : exportInfo.setTarget( dep, from, fromExport === undefined ? [name] : fromExport, priority )) ) { changed = true; } // Recalculate target exportsInfo const target = exportInfo.getTarget(moduleGraph); /** @type {undefined | ExportsInfo} */ let targetExportsInfo; if (target) { const targetModuleExportsInfo = moduleGraph.getExportsInfo(target.module); targetExportsInfo = targetModuleExportsInfo.getNestedExportsInfo( target.export ); // add dependency for this module const set = dependencies.get(target.module); if (set === undefined) { dependencies.set(target.module, new Set([module])); } else { set.add(module); } } if (exportInfo.exportsInfoOwned) { if ( /** @type {ExportsInfo} */ (exportInfo.exportsInfo).setRedirectNamedTo( targetExportsInfo ) ) { changed = true; } } else if (exportInfo.exportsInfo !== targetExportsInfo) { exportInfo.exportsInfo = targetExportsInfo; changed = true; } } }; mergeExports(exportsInfo, exports); } // store dependencies if (exportDeps) { cacheable = false; for (const exportDependency of exportDeps) { // add dependency for this module const set = dependencies.get(exportDependency); if (set === undefined) { dependencies.set(exportDependency, new Set([module])); } else { set.add(module); } } } }; const notifyDependencies = () => { const deps = dependencies.get(module); if (deps !== undefined) { for (const dep of deps) { queue.enqueue(dep); } } }; logger.time("figure out provided exports"); while (queue.length > 0) { module = /** @type {Module} */ (queue.dequeue()); statQueueItemsProcessed++; exportsInfo = moduleGraph.getExportsInfo(module); cacheable = true; changed = false; exportsSpecsFromDependencies.clear(); moduleGraph.freeze(); processDependenciesBlock(module); moduleGraph.unfreeze(); for (const [dep, exportsSpec] of exportsSpecsFromDependencies) { processExportsSpec(dep, exportsSpec); } if (cacheable) { modulesToStore.add(module); } if (changed) { notifyDependencies(); } } logger.timeEnd("figure out provided exports"); logger.log( `${Math.round( (100 * (statFlaggedUncached + statNotCached)) / (statRestoredFromMemCache + statRestoredFromCache + statNotCached + statFlaggedUncached + statNoExports) )}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${statRestoredFromMemCache} from mem cache, ${ statQueueItemsProcessed - statNotCached - statFlaggedUncached } additional calculations due to dependencies)` ); logger.time("store provided exports into cache"); asyncLib.each( modulesToStore, (module, callback) => { if ( typeof ( /** @type {BuildInfo} */ (module.buildInfo).hash ) !== "string" ) { // not cacheable return callback(); } const cachedData = moduleGraph .getExportsInfo(module) .getRestoreProvidedData(); const memCache = moduleMemCaches && moduleMemCaches.get(module); const hash = getLazyRequestsHash( module, lazyBarrelController, hashFunction ); if (memCache) { /** @type {MemCacheEntry} */ const entry = { etag: hash, data: cachedData }; memCache.set(this, entry); } const buildHash = /** @type {string} */ ( /** @type {BuildInfo} */ (module.buildInfo).hash ); cache.store( module.identifier(), hash ? `${buildHash}|${hash}` : buildHash, cachedData, callback ); }, (err) => { logger.timeEnd("store provided exports into cache"); callback(err); } ); } ); } ); /** @type {WeakMap<Module, RestoreProvidedData>} */ const providedExportsCache = new WeakMap(); compilation.hooks.rebuildModule.tap(PLUGIN_NAME, (module) => { providedExportsCache.set( module, moduleGraph.getExportsInfo(module).getRestoreProvidedData() ); }); compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, (module) => { moduleGraph.getExportsInfo(module).restoreProvided( /** @type {RestoreProvidedData} */ (providedExportsCache.get(module)) ); }); }); } } module.exports = FlagDependencyExportsPlugin;