You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			358 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			358 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||
|  | 	Author Tobias Koppers @sokra | ||
|  | */ | ||
|  | 
 | ||
|  | "use strict"; | ||
|  | 
 | ||
|  | const LazySet = require("../util/LazySet"); | ||
|  | const makeSerializable = require("../util/makeSerializable"); | ||
|  | 
 | ||
|  | /** @typedef {import("enhanced-resolve/lib/Resolver")} Resolver */ | ||
|  | /** @typedef {import("../CacheFacade").ItemCacheFacade} ItemCacheFacade */ | ||
|  | /** @typedef {import("../Compiler")} Compiler */ | ||
|  | /** @typedef {import("../FileSystemInfo")} FileSystemInfo */ | ||
|  | /** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */ | ||
|  | 
 | ||
|  | class CacheEntry { | ||
|  | 	constructor(result, snapshot) { | ||
|  | 		this.result = result; | ||
|  | 		this.snapshot = snapshot; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	serialize({ write }) { | ||
|  | 		write(this.result); | ||
|  | 		write(this.snapshot); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	deserialize({ read }) { | ||
|  | 		this.result = read(); | ||
|  | 		this.snapshot = read(); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | makeSerializable(CacheEntry, "webpack/lib/cache/ResolverCachePlugin"); | ||
|  | 
 | ||
|  | /** | ||
|  |  * @template T | ||
|  |  * @param {Set<T> | LazySet<T>} set set to add items to | ||
|  |  * @param {Set<T> | LazySet<T>} otherSet set to add items from | ||
|  |  * @returns {void} | ||
|  |  */ | ||
|  | const addAllToSet = (set, otherSet) => { | ||
|  | 	if (set instanceof LazySet) { | ||
|  | 		set.addAll(otherSet); | ||
|  | 	} else { | ||
|  | 		for (const item of otherSet) { | ||
|  | 			set.add(item); | ||
|  | 		} | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * @param {Object} object an object | ||
|  |  * @param {boolean} excludeContext if true, context is not included in string | ||
|  |  * @returns {string} stringified version | ||
|  |  */ | ||
|  | const objectToString = (object, excludeContext) => { | ||
|  | 	let str = ""; | ||
|  | 	for (const key in object) { | ||
|  | 		if (excludeContext && key === "context") continue; | ||
|  | 		const value = object[key]; | ||
|  | 		if (typeof value === "object" && value !== null) { | ||
|  | 			str += `|${key}=[${objectToString(value, false)}|]`; | ||
|  | 		} else { | ||
|  | 			str += `|${key}=|${value}`; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return str; | ||
|  | }; | ||
|  | 
 | ||
|  | class ResolverCachePlugin { | ||
|  | 	/** | ||
|  | 	 * Apply the plugin | ||
|  | 	 * @param {Compiler} compiler the compiler instance | ||
|  | 	 * @returns {void} | ||
|  | 	 */ | ||
|  | 	apply(compiler) { | ||
|  | 		const cache = compiler.getCache("ResolverCachePlugin"); | ||
|  | 		/** @type {FileSystemInfo} */ | ||
|  | 		let fileSystemInfo; | ||
|  | 		let snapshotOptions; | ||
|  | 		let realResolves = 0; | ||
|  | 		let cachedResolves = 0; | ||
|  | 		let cacheInvalidResolves = 0; | ||
|  | 		let concurrentResolves = 0; | ||
|  | 		compiler.hooks.thisCompilation.tap("ResolverCachePlugin", compilation => { | ||
|  | 			snapshotOptions = compilation.options.snapshot.resolve; | ||
|  | 			fileSystemInfo = compilation.fileSystemInfo; | ||
|  | 			compilation.hooks.finishModules.tap("ResolverCachePlugin", () => { | ||
|  | 				if (realResolves + cachedResolves > 0) { | ||
|  | 					const logger = compilation.getLogger("webpack.ResolverCachePlugin"); | ||
|  | 					logger.log( | ||
|  | 						`${Math.round( | ||
|  | 							(100 * realResolves) / (realResolves + cachedResolves) | ||
|  | 						)}% really resolved (${realResolves} real resolves with ${cacheInvalidResolves} cached but invalid, ${cachedResolves} cached valid, ${concurrentResolves} concurrent)`
 | ||
|  | 					); | ||
|  | 					realResolves = 0; | ||
|  | 					cachedResolves = 0; | ||
|  | 					cacheInvalidResolves = 0; | ||
|  | 					concurrentResolves = 0; | ||
|  | 				} | ||
|  | 			}); | ||
|  | 		}); | ||
|  | 		/** | ||
|  | 		 * @param {ItemCacheFacade} itemCache cache | ||
|  | 		 * @param {Resolver} resolver the resolver | ||
|  | 		 * @param {Object} resolveContext context for resolving meta info | ||
|  | 		 * @param {Object} request the request info object | ||
|  | 		 * @param {function((Error | null)=, Object=): void} callback callback function | ||
|  | 		 * @returns {void} | ||
|  | 		 */ | ||
|  | 		const doRealResolve = ( | ||
|  | 			itemCache, | ||
|  | 			resolver, | ||
|  | 			resolveContext, | ||
|  | 			request, | ||
|  | 			callback | ||
|  | 		) => { | ||
|  | 			realResolves++; | ||
|  | 			const newRequest = { | ||
|  | 				_ResolverCachePluginCacheMiss: true, | ||
|  | 				...request | ||
|  | 			}; | ||
|  | 			const newResolveContext = { | ||
|  | 				...resolveContext, | ||
|  | 				stack: new Set(), | ||
|  | 				/** @type {LazySet<string>} */ | ||
|  | 				missingDependencies: new LazySet(), | ||
|  | 				/** @type {LazySet<string>} */ | ||
|  | 				fileDependencies: new LazySet(), | ||
|  | 				/** @type {LazySet<string>} */ | ||
|  | 				contextDependencies: new LazySet() | ||
|  | 			}; | ||
|  | 			let yieldResult; | ||
|  | 			let withYield = false; | ||
|  | 			if (typeof newResolveContext.yield === "function") { | ||
|  | 				yieldResult = []; | ||
|  | 				withYield = true; | ||
|  | 				newResolveContext.yield = obj => yieldResult.push(obj); | ||
|  | 			} | ||
|  | 			const propagate = key => { | ||
|  | 				if (resolveContext[key]) { | ||
|  | 					addAllToSet(resolveContext[key], newResolveContext[key]); | ||
|  | 				} | ||
|  | 			}; | ||
|  | 			const resolveTime = Date.now(); | ||
|  | 			resolver.doResolve( | ||
|  | 				resolver.hooks.resolve, | ||
|  | 				newRequest, | ||
|  | 				"Cache miss", | ||
|  | 				newResolveContext, | ||
|  | 				(err, result) => { | ||
|  | 					propagate("fileDependencies"); | ||
|  | 					propagate("contextDependencies"); | ||
|  | 					propagate("missingDependencies"); | ||
|  | 					if (err) return callback(err); | ||
|  | 					const fileDependencies = newResolveContext.fileDependencies; | ||
|  | 					const contextDependencies = newResolveContext.contextDependencies; | ||
|  | 					const missingDependencies = newResolveContext.missingDependencies; | ||
|  | 					fileSystemInfo.createSnapshot( | ||
|  | 						resolveTime, | ||
|  | 						fileDependencies, | ||
|  | 						contextDependencies, | ||
|  | 						missingDependencies, | ||
|  | 						snapshotOptions, | ||
|  | 						(err, snapshot) => { | ||
|  | 							if (err) return callback(err); | ||
|  | 							const resolveResult = withYield ? yieldResult : result; | ||
|  | 							// since we intercept resolve hook
 | ||
|  | 							// we still can get result in callback
 | ||
|  | 							if (withYield && result) yieldResult.push(result); | ||
|  | 							if (!snapshot) { | ||
|  | 								if (resolveResult) return callback(null, resolveResult); | ||
|  | 								return callback(); | ||
|  | 							} | ||
|  | 							itemCache.store( | ||
|  | 								new CacheEntry(resolveResult, snapshot), | ||
|  | 								storeErr => { | ||
|  | 									if (storeErr) return callback(storeErr); | ||
|  | 									if (resolveResult) return callback(null, resolveResult); | ||
|  | 									callback(); | ||
|  | 								} | ||
|  | 							); | ||
|  | 						} | ||
|  | 					); | ||
|  | 				} | ||
|  | 			); | ||
|  | 		}; | ||
|  | 		compiler.resolverFactory.hooks.resolver.intercept({ | ||
|  | 			factory(type, hook) { | ||
|  | 				/** @type {Map<string, (function(Error=, Object=): void)[]>} */ | ||
|  | 				const activeRequests = new Map(); | ||
|  | 				/** @type {Map<string, [function(Error=, Object=): void, function(Error=, Object=): void][]>} */ | ||
|  | 				const activeRequestsWithYield = new Map(); | ||
|  | 				hook.tap( | ||
|  | 					"ResolverCachePlugin", | ||
|  | 					/** | ||
|  | 					 * @param {Resolver} resolver the resolver | ||
|  | 					 * @param {Object} options resolve options | ||
|  | 					 * @param {Object} userOptions resolve options passed by the user | ||
|  | 					 * @returns {void} | ||
|  | 					 */ | ||
|  | 					(resolver, options, userOptions) => { | ||
|  | 						if (options.cache !== true) return; | ||
|  | 						const optionsIdent = objectToString(userOptions, false); | ||
|  | 						const cacheWithContext = | ||
|  | 							options.cacheWithContext !== undefined | ||
|  | 								? options.cacheWithContext | ||
|  | 								: false; | ||
|  | 						resolver.hooks.resolve.tapAsync( | ||
|  | 							{ | ||
|  | 								name: "ResolverCachePlugin", | ||
|  | 								stage: -100 | ||
|  | 							}, | ||
|  | 							(request, resolveContext, callback) => { | ||
|  | 								if (request._ResolverCachePluginCacheMiss || !fileSystemInfo) { | ||
|  | 									return callback(); | ||
|  | 								} | ||
|  | 								const withYield = typeof resolveContext.yield === "function"; | ||
|  | 								const identifier = `${type}${ | ||
|  | 									withYield ? "|yield" : "|default" | ||
|  | 								}${optionsIdent}${objectToString(request, !cacheWithContext)}`;
 | ||
|  | 
 | ||
|  | 								if (withYield) { | ||
|  | 									const activeRequest = activeRequestsWithYield.get(identifier); | ||
|  | 									if (activeRequest) { | ||
|  | 										activeRequest[0].push(callback); | ||
|  | 										activeRequest[1].push(resolveContext.yield); | ||
|  | 										return; | ||
|  | 									} | ||
|  | 								} else { | ||
|  | 									const activeRequest = activeRequests.get(identifier); | ||
|  | 									if (activeRequest) { | ||
|  | 										activeRequest.push(callback); | ||
|  | 										return; | ||
|  | 									} | ||
|  | 								} | ||
|  | 								const itemCache = cache.getItemCache(identifier, null); | ||
|  | 								let callbacks, yields; | ||
|  | 								const done = withYield | ||
|  | 									? (err, result) => { | ||
|  | 											if (callbacks === undefined) { | ||
|  | 												if (err) { | ||
|  | 													callback(err); | ||
|  | 												} else { | ||
|  | 													if (result) | ||
|  | 														for (const r of result) resolveContext.yield(r); | ||
|  | 													callback(null, null); | ||
|  | 												} | ||
|  | 												yields = undefined; | ||
|  | 												callbacks = false; | ||
|  | 											} else { | ||
|  | 												if (err) { | ||
|  | 													for (const cb of callbacks) cb(err); | ||
|  | 												} else { | ||
|  | 													for (let i = 0; i < callbacks.length; i++) { | ||
|  | 														const cb = callbacks[i]; | ||
|  | 														const yield_ = yields[i]; | ||
|  | 														if (result) for (const r of result) yield_(r); | ||
|  | 														cb(null, null); | ||
|  | 													} | ||
|  | 												} | ||
|  | 												activeRequestsWithYield.delete(identifier); | ||
|  | 												yields = undefined; | ||
|  | 												callbacks = false; | ||
|  | 											} | ||
|  | 									  } | ||
|  | 									: (err, result) => { | ||
|  | 											if (callbacks === undefined) { | ||
|  | 												callback(err, result); | ||
|  | 												callbacks = false; | ||
|  | 											} else { | ||
|  | 												for (const callback of callbacks) { | ||
|  | 													callback(err, result); | ||
|  | 												} | ||
|  | 												activeRequests.delete(identifier); | ||
|  | 												callbacks = false; | ||
|  | 											} | ||
|  | 									  }; | ||
|  | 								/** | ||
|  | 								 * @param {Error=} err error if any | ||
|  | 								 * @param {CacheEntry=} cacheEntry cache entry | ||
|  | 								 * @returns {void} | ||
|  | 								 */ | ||
|  | 								const processCacheResult = (err, cacheEntry) => { | ||
|  | 									if (err) return done(err); | ||
|  | 
 | ||
|  | 									if (cacheEntry) { | ||
|  | 										const { snapshot, result } = cacheEntry; | ||
|  | 										fileSystemInfo.checkSnapshotValid( | ||
|  | 											snapshot, | ||
|  | 											(err, valid) => { | ||
|  | 												if (err || !valid) { | ||
|  | 													cacheInvalidResolves++; | ||
|  | 													return doRealResolve( | ||
|  | 														itemCache, | ||
|  | 														resolver, | ||
|  | 														resolveContext, | ||
|  | 														request, | ||
|  | 														done | ||
|  | 													); | ||
|  | 												} | ||
|  | 												cachedResolves++; | ||
|  | 												if (resolveContext.missingDependencies) { | ||
|  | 													addAllToSet( | ||
|  | 														resolveContext.missingDependencies, | ||
|  | 														snapshot.getMissingIterable() | ||
|  | 													); | ||
|  | 												} | ||
|  | 												if (resolveContext.fileDependencies) { | ||
|  | 													addAllToSet( | ||
|  | 														resolveContext.fileDependencies, | ||
|  | 														snapshot.getFileIterable() | ||
|  | 													); | ||
|  | 												} | ||
|  | 												if (resolveContext.contextDependencies) { | ||
|  | 													addAllToSet( | ||
|  | 														resolveContext.contextDependencies, | ||
|  | 														snapshot.getContextIterable() | ||
|  | 													); | ||
|  | 												} | ||
|  | 												done(null, result); | ||
|  | 											} | ||
|  | 										); | ||
|  | 									} else { | ||
|  | 										doRealResolve( | ||
|  | 											itemCache, | ||
|  | 											resolver, | ||
|  | 											resolveContext, | ||
|  | 											request, | ||
|  | 											done | ||
|  | 										); | ||
|  | 									} | ||
|  | 								}; | ||
|  | 								itemCache.get(processCacheResult); | ||
|  | 								if (withYield && callbacks === undefined) { | ||
|  | 									callbacks = [callback]; | ||
|  | 									yields = [resolveContext.yield]; | ||
|  | 									activeRequestsWithYield.set( | ||
|  | 										identifier, | ||
|  | 										/** @type {[any, any]} */ ([callbacks, yields]) | ||
|  | 									); | ||
|  | 								} else if (callbacks === undefined) { | ||
|  | 									callbacks = [callback]; | ||
|  | 									activeRequests.set(identifier, callbacks); | ||
|  | 								} | ||
|  | 							} | ||
|  | 						); | ||
|  | 					} | ||
|  | 				); | ||
|  | 				return hook; | ||
|  | 			} | ||
|  | 		}); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = ResolverCachePlugin; |