mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			365 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			11 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("./ExportsInfo")} ExportsInfo */
 | 
						|
/** @typedef {import("./Module")} Module */
 | 
						|
 | 
						|
class FlagDependencyExportsPlugin {
 | 
						|
	/**
 | 
						|
	 * Apply the plugin
 | 
						|
	 * @param {Compiler} compiler the compiler instance
 | 
						|
	 * @returns {void}
 | 
						|
	 */
 | 
						|
	apply(compiler) {
 | 
						|
		compiler.hooks.compilation.tap(
 | 
						|
			"FlagDependencyExportsPlugin",
 | 
						|
			compilation => {
 | 
						|
				const moduleGraph = compilation.moduleGraph;
 | 
						|
				const cache = compilation.getCache("FlagDependencyExportsPlugin");
 | 
						|
				compilation.hooks.finishModules.tapAsync(
 | 
						|
					"FlagDependencyExportsPlugin",
 | 
						|
					(modules, callback) => {
 | 
						|
						const logger = compilation.getLogger(
 | 
						|
							"webpack.FlagDependencyExportsPlugin"
 | 
						|
						);
 | 
						|
						let statRestoredFromCache = 0;
 | 
						|
						let statFlaggedUncached = 0;
 | 
						|
						let statNotCached = 0;
 | 
						|
						let statQueueItemsProcessed = 0;
 | 
						|
 | 
						|
						/** @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) => {
 | 
						|
								if (
 | 
						|
									module.buildInfo.cacheable !== true ||
 | 
						|
									typeof module.buildInfo.hash !== "string"
 | 
						|
								) {
 | 
						|
									statFlaggedUncached++;
 | 
						|
									// Enqueue uncacheable module for determining the exports
 | 
						|
									queue.enqueue(module);
 | 
						|
									moduleGraph.getExportsInfo(module).setHasProvideInfo();
 | 
						|
									return callback();
 | 
						|
								}
 | 
						|
								cache.get(
 | 
						|
									module.identifier(),
 | 
						|
									module.buildInfo.hash,
 | 
						|
									(err, result) => {
 | 
						|
										if (err) return callback(err);
 | 
						|
 | 
						|
										if (result !== undefined) {
 | 
						|
											statRestoredFromCache++;
 | 
						|
											moduleGraph
 | 
						|
												.getExportsInfo(module)
 | 
						|
												.restoreProvided(result);
 | 
						|
										} else {
 | 
						|
											statNotCached++;
 | 
						|
											// Without cached info enqueue module for determining the exports
 | 
						|
											queue.enqueue(module);
 | 
						|
											moduleGraph.getExportsInfo(module).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;
 | 
						|
 | 
						|
								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;
 | 
						|
									const exports = exportDesc.exports;
 | 
						|
									const globalCanMangle = exportDesc.canMangle;
 | 
						|
									const globalFrom = exportDesc.from;
 | 
						|
									const globalTerminalBinding =
 | 
						|
										exportDesc.terminalBinding || false;
 | 
						|
									const exportDeps = exportDesc.dependencies;
 | 
						|
									if (exports === true) {
 | 
						|
										// unknown exports
 | 
						|
										if (
 | 
						|
											exportsInfo.setUnknownExportsProvided(
 | 
						|
												globalCanMangle,
 | 
						|
												exportDesc.excludeExports,
 | 
						|
												globalFrom && dep,
 | 
						|
												globalFrom
 | 
						|
											)
 | 
						|
										) {
 | 
						|
											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 = undefined;
 | 
						|
												let from = globalFrom;
 | 
						|
												let fromExport = undefined;
 | 
						|
												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.terminalBinding !== undefined)
 | 
						|
														terminalBinding = exportNameOrSpec.terminalBinding;
 | 
						|
												}
 | 
						|
												const exportInfo = exportsInfo.getExportInfo(name);
 | 
						|
 | 
						|
												if (exportInfo.provided === false) {
 | 
						|
													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(nestedExportsInfo, exports);
 | 
						|
												}
 | 
						|
 | 
						|
												if (
 | 
						|
													from &&
 | 
						|
													exportInfo.setTarget(
 | 
						|
														dep,
 | 
						|
														from,
 | 
						|
														fromExport === undefined ? [name] : fromExport
 | 
						|
													)
 | 
						|
												) {
 | 
						|
													changed = true;
 | 
						|
												}
 | 
						|
 | 
						|
												// Recalculate target exportsInfo
 | 
						|
												const target = exportInfo.getTarget(moduleGraph);
 | 
						|
												let targetExportsInfo = undefined;
 | 
						|
												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 (
 | 
						|
														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 = queue.dequeue();
 | 
						|
 | 
						|
									statQueueItemsProcessed++;
 | 
						|
 | 
						|
									exportsInfo = moduleGraph.getExportsInfo(module);
 | 
						|
									if (!module.buildMeta || !module.buildMeta.exportsType) {
 | 
						|
										if (exportsInfo.otherExportsInfo.provided !== null) {
 | 
						|
											// It's a module without declared exports
 | 
						|
											exportsInfo.setUnknownExportsProvided();
 | 
						|
											modulesToStore.add(module);
 | 
						|
											notifyDependencies();
 | 
						|
										}
 | 
						|
									} else {
 | 
						|
										// It's a module with declared exports
 | 
						|
 | 
						|
										cacheable = true;
 | 
						|
										changed = false;
 | 
						|
 | 
						|
										processDependenciesBlock(module);
 | 
						|
 | 
						|
										if (cacheable) {
 | 
						|
											modulesToStore.add(module);
 | 
						|
										}
 | 
						|
 | 
						|
										if (changed) {
 | 
						|
											notifyDependencies();
 | 
						|
										}
 | 
						|
									}
 | 
						|
								}
 | 
						|
								logger.timeEnd("figure out provided exports");
 | 
						|
 | 
						|
								logger.log(
 | 
						|
									`${Math.round(
 | 
						|
										100 -
 | 
						|
											(100 * statRestoredFromCache) /
 | 
						|
												(statRestoredFromCache +
 | 
						|
													statNotCached +
 | 
						|
													statFlaggedUncached)
 | 
						|
									)}% of exports of modules have been determined (${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${
 | 
						|
										statQueueItemsProcessed -
 | 
						|
										statNotCached -
 | 
						|
										statFlaggedUncached
 | 
						|
									} additional calculations due to dependencies)`
 | 
						|
								);
 | 
						|
 | 
						|
								logger.time("store provided exports into cache");
 | 
						|
								asyncLib.each(
 | 
						|
									modulesToStore,
 | 
						|
									(module, callback) => {
 | 
						|
										if (
 | 
						|
											module.buildInfo.cacheable !== true ||
 | 
						|
											typeof module.buildInfo.hash !== "string"
 | 
						|
										) {
 | 
						|
											// not cacheable
 | 
						|
											return callback();
 | 
						|
										}
 | 
						|
										cache.store(
 | 
						|
											module.identifier(),
 | 
						|
											module.buildInfo.hash,
 | 
						|
											moduleGraph
 | 
						|
												.getExportsInfo(module)
 | 
						|
												.getRestoreProvidedData(),
 | 
						|
											callback
 | 
						|
										);
 | 
						|
									},
 | 
						|
									err => {
 | 
						|
										logger.timeEnd("store provided exports into cache");
 | 
						|
										callback(err);
 | 
						|
									}
 | 
						|
								);
 | 
						|
							}
 | 
						|
						);
 | 
						|
					}
 | 
						|
				);
 | 
						|
 | 
						|
				/** @type {WeakMap<Module, any>} */
 | 
						|
				const providedExportsCache = new WeakMap();
 | 
						|
				compilation.hooks.rebuildModule.tap(
 | 
						|
					"FlagDependencyExportsPlugin",
 | 
						|
					module => {
 | 
						|
						providedExportsCache.set(
 | 
						|
							module,
 | 
						|
							moduleGraph.getExportsInfo(module).getRestoreProvidedData()
 | 
						|
						);
 | 
						|
					}
 | 
						|
				);
 | 
						|
				compilation.hooks.finishRebuildingModule.tap(
 | 
						|
					"FlagDependencyExportsPlugin",
 | 
						|
					module => {
 | 
						|
						moduleGraph
 | 
						|
							.getExportsInfo(module)
 | 
						|
							.restoreProvided(providedExportsCache.get(module));
 | 
						|
					}
 | 
						|
				);
 | 
						|
			}
 | 
						|
		);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
module.exports = FlagDependencyExportsPlugin;
 |