simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

FlagDependencyExportsPlugin.js (13222B)


      1 /*
      2 	MIT License http://www.opensource.org/licenses/mit-license.php
      3 	Author Tobias Koppers @sokra
      4 */
      5 
      6 "use strict";
      7 
      8 const asyncLib = require("neo-async");
      9 const Queue = require("./util/Queue");
     10 
     11 /** @typedef {import("./Compiler")} Compiler */
     12 /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
     13 /** @typedef {import("./Dependency")} Dependency */
     14 /** @typedef {import("./Dependency").ExportSpec} ExportSpec */
     15 /** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */
     16 /** @typedef {import("./ExportsInfo")} ExportsInfo */
     17 /** @typedef {import("./Module")} Module */
     18 
     19 class FlagDependencyExportsPlugin {
     20 	/**
     21 	 * Apply the plugin
     22 	 * @param {Compiler} compiler the compiler instance
     23 	 * @returns {void}
     24 	 */
     25 	apply(compiler) {
     26 		compiler.hooks.compilation.tap(
     27 			"FlagDependencyExportsPlugin",
     28 			compilation => {
     29 				const moduleGraph = compilation.moduleGraph;
     30 				const cache = compilation.getCache("FlagDependencyExportsPlugin");
     31 				compilation.hooks.finishModules.tapAsync(
     32 					"FlagDependencyExportsPlugin",
     33 					(modules, callback) => {
     34 						const logger = compilation.getLogger(
     35 							"webpack.FlagDependencyExportsPlugin"
     36 						);
     37 						let statRestoredFromMemCache = 0;
     38 						let statRestoredFromCache = 0;
     39 						let statNoExports = 0;
     40 						let statFlaggedUncached = 0;
     41 						let statNotCached = 0;
     42 						let statQueueItemsProcessed = 0;
     43 
     44 						const { moduleMemCaches } = compilation;
     45 
     46 						/** @type {Queue<Module>} */
     47 						const queue = new Queue();
     48 
     49 						// Step 1: Try to restore cached provided export info from cache
     50 						logger.time("restore cached provided exports");
     51 						asyncLib.each(
     52 							modules,
     53 							(module, callback) => {
     54 								const exportsInfo = moduleGraph.getExportsInfo(module);
     55 								if (!module.buildMeta || !module.buildMeta.exportsType) {
     56 									if (exportsInfo.otherExportsInfo.provided !== null) {
     57 										// It's a module without declared exports
     58 										statNoExports++;
     59 										exportsInfo.setHasProvideInfo();
     60 										exportsInfo.setUnknownExportsProvided();
     61 										return callback();
     62 									}
     63 								}
     64 								if (typeof module.buildInfo.hash !== "string") {
     65 									statFlaggedUncached++;
     66 									// Enqueue uncacheable module for determining the exports
     67 									queue.enqueue(module);
     68 									exportsInfo.setHasProvideInfo();
     69 									return callback();
     70 								}
     71 								const memCache = moduleMemCaches && moduleMemCaches.get(module);
     72 								const memCacheValue = memCache && memCache.get(this);
     73 								if (memCacheValue !== undefined) {
     74 									statRestoredFromMemCache++;
     75 									exportsInfo.restoreProvided(memCacheValue);
     76 									return callback();
     77 								}
     78 								cache.get(
     79 									module.identifier(),
     80 									module.buildInfo.hash,
     81 									(err, result) => {
     82 										if (err) return callback(err);
     83 
     84 										if (result !== undefined) {
     85 											statRestoredFromCache++;
     86 											exportsInfo.restoreProvided(result);
     87 										} else {
     88 											statNotCached++;
     89 											// Without cached info enqueue module for determining the exports
     90 											queue.enqueue(module);
     91 											exportsInfo.setHasProvideInfo();
     92 										}
     93 										callback();
     94 									}
     95 								);
     96 							},
     97 							err => {
     98 								logger.timeEnd("restore cached provided exports");
     99 								if (err) return callback(err);
    100 
    101 								/** @type {Set<Module>} */
    102 								const modulesToStore = new Set();
    103 
    104 								/** @type {Map<Module, Set<Module>>} */
    105 								const dependencies = new Map();
    106 
    107 								/** @type {Module} */
    108 								let module;
    109 
    110 								/** @type {ExportsInfo} */
    111 								let exportsInfo;
    112 
    113 								/** @type {Map<Dependency, ExportsSpec>} */
    114 								const exportsSpecsFromDependencies = new Map();
    115 
    116 								let cacheable = true;
    117 								let changed = false;
    118 
    119 								/**
    120 								 * @param {DependenciesBlock} depBlock the dependencies block
    121 								 * @returns {void}
    122 								 */
    123 								const processDependenciesBlock = depBlock => {
    124 									for (const dep of depBlock.dependencies) {
    125 										processDependency(dep);
    126 									}
    127 									for (const block of depBlock.blocks) {
    128 										processDependenciesBlock(block);
    129 									}
    130 								};
    131 
    132 								/**
    133 								 * @param {Dependency} dep the dependency
    134 								 * @returns {void}
    135 								 */
    136 								const processDependency = dep => {
    137 									const exportDesc = dep.getExports(moduleGraph);
    138 									if (!exportDesc) return;
    139 									exportsSpecsFromDependencies.set(dep, exportDesc);
    140 								};
    141 
    142 								/**
    143 								 * @param {Dependency} dep dependency
    144 								 * @param {ExportsSpec} exportDesc info
    145 								 * @returns {void}
    146 								 */
    147 								const processExportsSpec = (dep, exportDesc) => {
    148 									const exports = exportDesc.exports;
    149 									const globalCanMangle = exportDesc.canMangle;
    150 									const globalFrom = exportDesc.from;
    151 									const globalPriority = exportDesc.priority;
    152 									const globalTerminalBinding =
    153 										exportDesc.terminalBinding || false;
    154 									const exportDeps = exportDesc.dependencies;
    155 									if (exportDesc.hideExports) {
    156 										for (const name of exportDesc.hideExports) {
    157 											const exportInfo = exportsInfo.getExportInfo(name);
    158 											exportInfo.unsetTarget(dep);
    159 										}
    160 									}
    161 									if (exports === true) {
    162 										// unknown exports
    163 										if (
    164 											exportsInfo.setUnknownExportsProvided(
    165 												globalCanMangle,
    166 												exportDesc.excludeExports,
    167 												globalFrom && dep,
    168 												globalFrom,
    169 												globalPriority
    170 											)
    171 										) {
    172 											changed = true;
    173 										}
    174 									} else if (Array.isArray(exports)) {
    175 										/**
    176 										 * merge in new exports
    177 										 * @param {ExportsInfo} exportsInfo own exports info
    178 										 * @param {(ExportSpec | string)[]} exports list of exports
    179 										 */
    180 										const mergeExports = (exportsInfo, exports) => {
    181 											for (const exportNameOrSpec of exports) {
    182 												let name;
    183 												let canMangle = globalCanMangle;
    184 												let terminalBinding = globalTerminalBinding;
    185 												let exports = undefined;
    186 												let from = globalFrom;
    187 												let fromExport = undefined;
    188 												let priority = globalPriority;
    189 												let hidden = false;
    190 												if (typeof exportNameOrSpec === "string") {
    191 													name = exportNameOrSpec;
    192 												} else {
    193 													name = exportNameOrSpec.name;
    194 													if (exportNameOrSpec.canMangle !== undefined)
    195 														canMangle = exportNameOrSpec.canMangle;
    196 													if (exportNameOrSpec.export !== undefined)
    197 														fromExport = exportNameOrSpec.export;
    198 													if (exportNameOrSpec.exports !== undefined)
    199 														exports = exportNameOrSpec.exports;
    200 													if (exportNameOrSpec.from !== undefined)
    201 														from = exportNameOrSpec.from;
    202 													if (exportNameOrSpec.priority !== undefined)
    203 														priority = exportNameOrSpec.priority;
    204 													if (exportNameOrSpec.terminalBinding !== undefined)
    205 														terminalBinding = exportNameOrSpec.terminalBinding;
    206 													if (exportNameOrSpec.hidden !== undefined)
    207 														hidden = exportNameOrSpec.hidden;
    208 												}
    209 												const exportInfo = exportsInfo.getExportInfo(name);
    210 
    211 												if (
    212 													exportInfo.provided === false ||
    213 													exportInfo.provided === null
    214 												) {
    215 													exportInfo.provided = true;
    216 													changed = true;
    217 												}
    218 
    219 												if (
    220 													exportInfo.canMangleProvide !== false &&
    221 													canMangle === false
    222 												) {
    223 													exportInfo.canMangleProvide = false;
    224 													changed = true;
    225 												}
    226 
    227 												if (terminalBinding && !exportInfo.terminalBinding) {
    228 													exportInfo.terminalBinding = true;
    229 													changed = true;
    230 												}
    231 
    232 												if (exports) {
    233 													const nestedExportsInfo =
    234 														exportInfo.createNestedExportsInfo();
    235 													mergeExports(nestedExportsInfo, exports);
    236 												}
    237 
    238 												if (
    239 													from &&
    240 													(hidden
    241 														? exportInfo.unsetTarget(dep)
    242 														: exportInfo.setTarget(
    243 																dep,
    244 																from,
    245 																fromExport === undefined ? [name] : fromExport,
    246 																priority
    247 														  ))
    248 												) {
    249 													changed = true;
    250 												}
    251 
    252 												// Recalculate target exportsInfo
    253 												const target = exportInfo.getTarget(moduleGraph);
    254 												let targetExportsInfo = undefined;
    255 												if (target) {
    256 													const targetModuleExportsInfo =
    257 														moduleGraph.getExportsInfo(target.module);
    258 													targetExportsInfo =
    259 														targetModuleExportsInfo.getNestedExportsInfo(
    260 															target.export
    261 														);
    262 													// add dependency for this module
    263 													const set = dependencies.get(target.module);
    264 													if (set === undefined) {
    265 														dependencies.set(target.module, new Set([module]));
    266 													} else {
    267 														set.add(module);
    268 													}
    269 												}
    270 
    271 												if (exportInfo.exportsInfoOwned) {
    272 													if (
    273 														exportInfo.exportsInfo.setRedirectNamedTo(
    274 															targetExportsInfo
    275 														)
    276 													) {
    277 														changed = true;
    278 													}
    279 												} else if (
    280 													exportInfo.exportsInfo !== targetExportsInfo
    281 												) {
    282 													exportInfo.exportsInfo = targetExportsInfo;
    283 													changed = true;
    284 												}
    285 											}
    286 										};
    287 										mergeExports(exportsInfo, exports);
    288 									}
    289 									// store dependencies
    290 									if (exportDeps) {
    291 										cacheable = false;
    292 										for (const exportDependency of exportDeps) {
    293 											// add dependency for this module
    294 											const set = dependencies.get(exportDependency);
    295 											if (set === undefined) {
    296 												dependencies.set(exportDependency, new Set([module]));
    297 											} else {
    298 												set.add(module);
    299 											}
    300 										}
    301 									}
    302 								};
    303 
    304 								const notifyDependencies = () => {
    305 									const deps = dependencies.get(module);
    306 									if (deps !== undefined) {
    307 										for (const dep of deps) {
    308 											queue.enqueue(dep);
    309 										}
    310 									}
    311 								};
    312 
    313 								logger.time("figure out provided exports");
    314 								while (queue.length > 0) {
    315 									module = queue.dequeue();
    316 
    317 									statQueueItemsProcessed++;
    318 
    319 									exportsInfo = moduleGraph.getExportsInfo(module);
    320 
    321 									cacheable = true;
    322 									changed = false;
    323 
    324 									exportsSpecsFromDependencies.clear();
    325 									moduleGraph.freeze();
    326 									processDependenciesBlock(module);
    327 									moduleGraph.unfreeze();
    328 									for (const [
    329 										dep,
    330 										exportsSpec
    331 									] of exportsSpecsFromDependencies) {
    332 										processExportsSpec(dep, exportsSpec);
    333 									}
    334 
    335 									if (cacheable) {
    336 										modulesToStore.add(module);
    337 									}
    338 
    339 									if (changed) {
    340 										notifyDependencies();
    341 									}
    342 								}
    343 								logger.timeEnd("figure out provided exports");
    344 
    345 								logger.log(
    346 									`${Math.round(
    347 										(100 * (statFlaggedUncached + statNotCached)) /
    348 											(statRestoredFromMemCache +
    349 												statRestoredFromCache +
    350 												statNotCached +
    351 												statFlaggedUncached +
    352 												statNoExports)
    353 									)}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${statRestoredFromMemCache} from mem cache, ${
    354 										statQueueItemsProcessed -
    355 										statNotCached -
    356 										statFlaggedUncached
    357 									} additional calculations due to dependencies)`
    358 								);
    359 
    360 								logger.time("store provided exports into cache");
    361 								asyncLib.each(
    362 									modulesToStore,
    363 									(module, callback) => {
    364 										if (typeof module.buildInfo.hash !== "string") {
    365 											// not cacheable
    366 											return callback();
    367 										}
    368 										const cachedData = moduleGraph
    369 											.getExportsInfo(module)
    370 											.getRestoreProvidedData();
    371 										const memCache =
    372 											moduleMemCaches && moduleMemCaches.get(module);
    373 										if (memCache) {
    374 											memCache.set(this, cachedData);
    375 										}
    376 										cache.store(
    377 											module.identifier(),
    378 											module.buildInfo.hash,
    379 											cachedData,
    380 											callback
    381 										);
    382 									},
    383 									err => {
    384 										logger.timeEnd("store provided exports into cache");
    385 										callback(err);
    386 									}
    387 								);
    388 							}
    389 						);
    390 					}
    391 				);
    392 
    393 				/** @type {WeakMap<Module, any>} */
    394 				const providedExportsCache = new WeakMap();
    395 				compilation.hooks.rebuildModule.tap(
    396 					"FlagDependencyExportsPlugin",
    397 					module => {
    398 						providedExportsCache.set(
    399 							module,
    400 							moduleGraph.getExportsInfo(module).getRestoreProvidedData()
    401 						);
    402 					}
    403 				);
    404 				compilation.hooks.finishRebuildingModule.tap(
    405 					"FlagDependencyExportsPlugin",
    406 					module => {
    407 						moduleGraph
    408 							.getExportsInfo(module)
    409 							.restoreProvided(providedExportsCache.get(module));
    410 					}
    411 				);
    412 			}
    413 		);
    414 	}
    415 }
    416 
    417 module.exports = FlagDependencyExportsPlugin;