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;