mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			424 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	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");
 | |
| 
 | |
| /** @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").RestoreProvidedData} RestoreProvidedData */
 | |
| /** @typedef {import("./Module")} Module */
 | |
| /** @typedef {import("./Module").BuildInfo} BuildInfo */
 | |
| 
 | |
| const PLUGIN_NAME = "FlagDependencyExportsPlugin";
 | |
| const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
 | |
| 
 | |
| class FlagDependencyExportsPlugin {
 | |
| 	/**
 | |
| 	 * Apply the plugin
 | |
| 	 * @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);
 | |
| 			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();
 | |
| 
 | |
| 					// Step 1: Try to restore cached provided export info from cache
 | |
| 					logger.time("restore cached provided exports");
 | |
| 					asyncLib.each(
 | |
| 						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++;
 | |
| 								// Enqueue uncacheable module for determining the exports
 | |
| 								queue.enqueue(module);
 | |
| 								exportsInfo.setHasProvideInfo();
 | |
| 								return callback();
 | |
| 							}
 | |
| 							const memCache = moduleMemCaches && moduleMemCaches.get(module);
 | |
| 							const memCacheValue = memCache && memCache.get(this);
 | |
| 							if (memCacheValue !== undefined) {
 | |
| 								statRestoredFromMemCache++;
 | |
| 								exportsInfo.restoreProvided(memCacheValue);
 | |
| 								return callback();
 | |
| 							}
 | |
| 							cache.get(
 | |
| 								module.identifier(),
 | |
| 								/** @type {BuildInfo} */
 | |
| 								(module.buildInfo).hash,
 | |
| 								(err, result) => {
 | |
| 									if (err) return callback(err);
 | |
| 
 | |
| 									if (result !== undefined) {
 | |
| 										statRestoredFromCache++;
 | |
| 										exportsInfo.restoreProvided(result);
 | |
| 									} else {
 | |
| 										statNotCached++;
 | |
| 										// Without cached info enqueue module for determining the exports
 | |
| 										queue.enqueue(module);
 | |
| 										exportsInfo.setHasProvideInfo();
 | |
| 									}
 | |
| 									callback();
 | |
| 								}
 | |
| 							);
 | |
| 						},
 | |
| 						err => {
 | |
| 							logger.timeEnd("restore cached provided exports");
 | |
| 							if (err) return callback(err);
 | |
| 
 | |
| 							/** @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;
 | |
| 
 | |
| 							/**
 | |
| 							 * @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);
 | |
| 								}
 | |
| 							};
 | |
| 
 | |
| 							/**
 | |
| 							 * @param {Dependency} dep the dependency
 | |
| 							 * @returns {void}
 | |
| 							 */
 | |
| 							const processDependency = dep => {
 | |
| 								const exportDesc = dep.getExports(moduleGraph);
 | |
| 								if (!exportDesc) return;
 | |
| 								exportsSpecsFromDependencies.set(dep, exportDesc);
 | |
| 							};
 | |
| 
 | |
| 							/**
 | |
| 							 * @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 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) {
 | |
| 											let name;
 | |
| 											let canMangle = globalCanMangle;
 | |
| 											let terminalBinding = globalTerminalBinding;
 | |
| 											let exports;
 | |
| 											let from = globalFrom;
 | |
| 											let fromExport;
 | |
| 											let priority = globalPriority;
 | |
| 											let hidden = false;
 | |
| 											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.hidden !== undefined)
 | |
| 													hidden = exportNameOrSpec.hidden;
 | |
| 											}
 | |
| 											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 (terminalBinding && !exportInfo.terminalBinding) {
 | |
| 												exportInfo.terminalBinding = 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);
 | |
| 											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);
 | |
| 									if (memCache) {
 | |
| 										memCache.set(this, cachedData);
 | |
| 									}
 | |
| 									cache.store(
 | |
| 										module.identifier(),
 | |
| 										/** @type {BuildInfo} */
 | |
| 										(module.buildInfo).hash,
 | |
| 										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;
 |