You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							347 lines
						
					
					
						
							8.9 KiB
						
					
					
				
			
		
		
	
	
							347 lines
						
					
					
						
							8.9 KiB
						
					
					
				/* | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php | 
						|
	Author Tobias Koppers @sokra | 
						|
*/ | 
						|
"use strict"; | 
						|
 | 
						|
const util = require("util"); | 
						|
 | 
						|
const Tapable = require("tapable/lib/Tapable"); | 
						|
const SyncHook = require("tapable/lib/SyncHook"); | 
						|
const AsyncSeriesBailHook = require("tapable/lib/AsyncSeriesBailHook"); | 
						|
const AsyncSeriesHook = require("tapable/lib/AsyncSeriesHook"); | 
						|
const createInnerContext = require("./createInnerContext"); | 
						|
 | 
						|
const REGEXP_NOT_MODULE = /^\.$|^\.[\\/]|^\.\.$|^\.\.[\\/]|^\/|^[A-Z]:[\\/]/i; | 
						|
const REGEXP_DIRECTORY = /[\\/]$/i; | 
						|
 | 
						|
const memoryFsJoin = require("memory-fs/lib/join"); | 
						|
const memoizedJoin = new Map(); | 
						|
const memoryFsNormalize = require("memory-fs/lib/normalize"); | 
						|
 | 
						|
function withName(name, hook) { | 
						|
	hook.name = name; | 
						|
	return hook; | 
						|
} | 
						|
 | 
						|
function toCamelCase(str) { | 
						|
	return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase()); | 
						|
} | 
						|
 | 
						|
const deprecatedPushToMissing = util.deprecate((set, item) => { | 
						|
	set.add(item); | 
						|
}, "Resolver: 'missing' is now a Set. Use add instead of push."); | 
						|
 | 
						|
const deprecatedResolveContextInCallback = util.deprecate(x => { | 
						|
	return x; | 
						|
}, "Resolver: The callback argument was splitted into resolveContext and callback."); | 
						|
 | 
						|
const deprecatedHookAsString = util.deprecate(x => { | 
						|
	return x; | 
						|
}, "Resolver#doResolve: The type arguments (string) is now a hook argument (Hook). Pass a reference to the hook instead."); | 
						|
 | 
						|
class Resolver extends Tapable { | 
						|
	constructor(fileSystem) { | 
						|
		super(); | 
						|
		this.fileSystem = fileSystem; | 
						|
		this.hooks = { | 
						|
			resolveStep: withName("resolveStep", new SyncHook(["hook", "request"])), | 
						|
			noResolve: withName("noResolve", new SyncHook(["request", "error"])), | 
						|
			resolve: withName( | 
						|
				"resolve", | 
						|
				new AsyncSeriesBailHook(["request", "resolveContext"]) | 
						|
			), | 
						|
			result: new AsyncSeriesHook(["result", "resolveContext"]) | 
						|
		}; | 
						|
		this._pluginCompat.tap("Resolver: before/after", options => { | 
						|
			if (/^before-/.test(options.name)) { | 
						|
				options.name = options.name.substr(7); | 
						|
				options.stage = -10; | 
						|
			} else if (/^after-/.test(options.name)) { | 
						|
				options.name = options.name.substr(6); | 
						|
				options.stage = 10; | 
						|
			} | 
						|
		}); | 
						|
		this._pluginCompat.tap("Resolver: step hooks", options => { | 
						|
			const name = options.name; | 
						|
			const stepHook = !/^resolve(-s|S)tep$|^no(-r|R)esolve$/.test(name); | 
						|
			if (stepHook) { | 
						|
				options.async = true; | 
						|
				this.ensureHook(name); | 
						|
				const fn = options.fn; | 
						|
				options.fn = (request, resolverContext, callback) => { | 
						|
					const innerCallback = (err, result) => { | 
						|
						if (err) return callback(err); | 
						|
						if (result !== undefined) return callback(null, result); | 
						|
						callback(); | 
						|
					}; | 
						|
					for (const key in resolverContext) { | 
						|
						innerCallback[key] = resolverContext[key]; | 
						|
					} | 
						|
					fn.call(this, request, innerCallback); | 
						|
				}; | 
						|
			} | 
						|
		}); | 
						|
	} | 
						|
 | 
						|
	ensureHook(name) { | 
						|
		if (typeof name !== "string") return name; | 
						|
		name = toCamelCase(name); | 
						|
		if (/^before/.test(name)) { | 
						|
			return this.ensureHook( | 
						|
				name[6].toLowerCase() + name.substr(7) | 
						|
			).withOptions({ | 
						|
				stage: -10 | 
						|
			}); | 
						|
		} | 
						|
		if (/^after/.test(name)) { | 
						|
			return this.ensureHook( | 
						|
				name[5].toLowerCase() + name.substr(6) | 
						|
			).withOptions({ | 
						|
				stage: 10 | 
						|
			}); | 
						|
		} | 
						|
		const hook = this.hooks[name]; | 
						|
		if (!hook) { | 
						|
			return (this.hooks[name] = withName( | 
						|
				name, | 
						|
				new AsyncSeriesBailHook(["request", "resolveContext"]) | 
						|
			)); | 
						|
		} | 
						|
		return hook; | 
						|
	} | 
						|
 | 
						|
	getHook(name) { | 
						|
		if (typeof name !== "string") return name; | 
						|
		name = toCamelCase(name); | 
						|
		if (/^before/.test(name)) { | 
						|
			return this.getHook(name[6].toLowerCase() + name.substr(7)).withOptions({ | 
						|
				stage: -10 | 
						|
			}); | 
						|
		} | 
						|
		if (/^after/.test(name)) { | 
						|
			return this.getHook(name[5].toLowerCase() + name.substr(6)).withOptions({ | 
						|
				stage: 10 | 
						|
			}); | 
						|
		} | 
						|
		const hook = this.hooks[name]; | 
						|
		if (!hook) { | 
						|
			throw new Error(`Hook ${name} doesn't exist`); | 
						|
		} | 
						|
		return hook; | 
						|
	} | 
						|
 | 
						|
	resolveSync(context, path, request) { | 
						|
		let err, | 
						|
			result, | 
						|
			sync = false; | 
						|
		this.resolve(context, path, request, {}, (e, r) => { | 
						|
			err = e; | 
						|
			result = r; | 
						|
			sync = true; | 
						|
		}); | 
						|
		if (!sync) | 
						|
			throw new Error( | 
						|
				"Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!" | 
						|
			); | 
						|
		if (err) throw err; | 
						|
		return result; | 
						|
	} | 
						|
 | 
						|
	resolve(context, path, request, resolveContext, callback) { | 
						|
		// TODO remove in enhanced-resolve 5 | 
						|
		// For backward compatiblity START | 
						|
		if (typeof callback !== "function") { | 
						|
			callback = deprecatedResolveContextInCallback(resolveContext); | 
						|
			// resolveContext is a function containing additional properties | 
						|
			// It's now used for resolveContext and callback | 
						|
		} | 
						|
		// END | 
						|
		const obj = { | 
						|
			context: context, | 
						|
			path: path, | 
						|
			request: request | 
						|
		}; | 
						|
 | 
						|
		const message = "resolve '" + request + "' in '" + path + "'"; | 
						|
 | 
						|
		// Try to resolve assuming there is no error | 
						|
		// We don't log stuff in this case | 
						|
		return this.doResolve( | 
						|
			this.hooks.resolve, | 
						|
			obj, | 
						|
			message, | 
						|
			{ | 
						|
				missing: resolveContext.missing, | 
						|
				stack: resolveContext.stack | 
						|
			}, | 
						|
			(err, result) => { | 
						|
				if (!err && result) { | 
						|
					return callback( | 
						|
						null, | 
						|
						result.path === false ? false : result.path + (result.query || ""), | 
						|
						result | 
						|
					); | 
						|
				} | 
						|
 | 
						|
				const localMissing = new Set(); | 
						|
				// TODO remove in enhanced-resolve 5 | 
						|
				localMissing.push = item => deprecatedPushToMissing(localMissing, item); | 
						|
				const log = []; | 
						|
 | 
						|
				return this.doResolve( | 
						|
					this.hooks.resolve, | 
						|
					obj, | 
						|
					message, | 
						|
					{ | 
						|
						log: msg => { | 
						|
							if (resolveContext.log) { | 
						|
								resolveContext.log(msg); | 
						|
							} | 
						|
							log.push(msg); | 
						|
						}, | 
						|
						missing: localMissing, | 
						|
						stack: resolveContext.stack | 
						|
					}, | 
						|
					(err, result) => { | 
						|
						if (err) return callback(err); | 
						|
 | 
						|
						const error = new Error("Can't " + message); | 
						|
						error.details = log.join("\n"); | 
						|
						error.missing = Array.from(localMissing); | 
						|
						this.hooks.noResolve.call(obj, error); | 
						|
						return callback(error); | 
						|
					} | 
						|
				); | 
						|
			} | 
						|
		); | 
						|
	} | 
						|
 | 
						|
	doResolve(hook, request, message, resolveContext, callback) { | 
						|
		// TODO remove in enhanced-resolve 5 | 
						|
		// For backward compatiblity START | 
						|
		if (typeof callback !== "function") { | 
						|
			callback = deprecatedResolveContextInCallback(resolveContext); | 
						|
			// resolveContext is a function containing additional properties | 
						|
			// It's now used for resolveContext and callback | 
						|
		} | 
						|
		if (typeof hook === "string") { | 
						|
			const name = toCamelCase(hook); | 
						|
			hook = deprecatedHookAsString(this.hooks[name]); | 
						|
			if (!hook) { | 
						|
				throw new Error(`Hook "${name}" doesn't exist`); | 
						|
			} | 
						|
		} | 
						|
		// END | 
						|
		if (typeof callback !== "function") | 
						|
			throw new Error("callback is not a function " + Array.from(arguments)); | 
						|
		if (!resolveContext) | 
						|
			throw new Error( | 
						|
				"resolveContext is not an object " + Array.from(arguments) | 
						|
			); | 
						|
 | 
						|
		const stackLine = | 
						|
			hook.name + | 
						|
			": (" + | 
						|
			request.path + | 
						|
			") " + | 
						|
			(request.request || "") + | 
						|
			(request.query || "") + | 
						|
			(request.directory ? " directory" : "") + | 
						|
			(request.module ? " module" : ""); | 
						|
 | 
						|
		let newStack; | 
						|
		if (resolveContext.stack) { | 
						|
			newStack = new Set(resolveContext.stack); | 
						|
			if (resolveContext.stack.has(stackLine)) { | 
						|
				// Prevent recursion | 
						|
				const recursionError = new Error( | 
						|
					"Recursion in resolving\nStack:\n  " + | 
						|
						Array.from(newStack).join("\n  ") | 
						|
				); | 
						|
				recursionError.recursion = true; | 
						|
				if (resolveContext.log) | 
						|
					resolveContext.log("abort resolving because of recursion"); | 
						|
				return callback(recursionError); | 
						|
			} | 
						|
			newStack.add(stackLine); | 
						|
		} else { | 
						|
			newStack = new Set([stackLine]); | 
						|
		} | 
						|
		this.hooks.resolveStep.call(hook, request); | 
						|
 | 
						|
		if (hook.isUsed()) { | 
						|
			const innerContext = createInnerContext( | 
						|
				{ | 
						|
					log: resolveContext.log, | 
						|
					missing: resolveContext.missing, | 
						|
					stack: newStack | 
						|
				}, | 
						|
				message | 
						|
			); | 
						|
			return hook.callAsync(request, innerContext, (err, result) => { | 
						|
				if (err) return callback(err); | 
						|
				if (result) return callback(null, result); | 
						|
				callback(); | 
						|
			}); | 
						|
		} else { | 
						|
			callback(); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	parse(identifier) { | 
						|
		if (identifier === "") return null; | 
						|
		const part = { | 
						|
			request: "", | 
						|
			query: "", | 
						|
			module: false, | 
						|
			directory: false, | 
						|
			file: false | 
						|
		}; | 
						|
		const idxQuery = identifier.indexOf("?"); | 
						|
		if (idxQuery === 0) { | 
						|
			part.query = identifier; | 
						|
		} else if (idxQuery > 0) { | 
						|
			part.request = identifier.slice(0, idxQuery); | 
						|
			part.query = identifier.slice(idxQuery); | 
						|
		} else { | 
						|
			part.request = identifier; | 
						|
		} | 
						|
		if (part.request) { | 
						|
			part.module = this.isModule(part.request); | 
						|
			part.directory = this.isDirectory(part.request); | 
						|
			if (part.directory) { | 
						|
				part.request = part.request.substr(0, part.request.length - 1); | 
						|
			} | 
						|
		} | 
						|
		return part; | 
						|
	} | 
						|
 | 
						|
	isModule(path) { | 
						|
		return !REGEXP_NOT_MODULE.test(path); | 
						|
	} | 
						|
 | 
						|
	isDirectory(path) { | 
						|
		return REGEXP_DIRECTORY.test(path); | 
						|
	} | 
						|
 | 
						|
	join(path, request) { | 
						|
		let cacheEntry; | 
						|
		let pathCache = memoizedJoin.get(path); | 
						|
		if (typeof pathCache === "undefined") { | 
						|
			memoizedJoin.set(path, (pathCache = new Map())); | 
						|
		} else { | 
						|
			cacheEntry = pathCache.get(request); | 
						|
			if (typeof cacheEntry !== "undefined") return cacheEntry; | 
						|
		} | 
						|
		cacheEntry = memoryFsJoin(path, request); | 
						|
		pathCache.set(request, cacheEntry); | 
						|
		return cacheEntry; | 
						|
	} | 
						|
 | 
						|
	normalize(path) { | 
						|
		return memoryFsNormalize(path); | 
						|
	} | 
						|
} | 
						|
 | 
						|
module.exports = Resolver;
 | 
						|
 |