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.
		
		
		
		
		
			
		
			
	
	
		
			417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||
|  | 	Author Tobias Koppers @sokra | ||
|  | */ | ||
|  | var fs = require("fs"); | ||
|  | var readFile = fs.readFile.bind(fs); | ||
|  | var loadLoader = require("./loadLoader"); | ||
|  | 
 | ||
|  | function utf8BufferToString(buf) { | ||
|  | 	var str = buf.toString("utf-8"); | ||
|  | 	if(str.charCodeAt(0) === 0xFEFF) { | ||
|  | 		return str.substr(1); | ||
|  | 	} else { | ||
|  | 		return str; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | const PATH_QUERY_FRAGMENT_REGEXP = /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; | ||
|  | 
 | ||
|  | /** | ||
|  |  * @param {string} str the path with query and fragment | ||
|  |  * @returns {{ path: string, query: string, fragment: string }} parsed parts | ||
|  |  */ | ||
|  | function parsePathQueryFragment(str) { | ||
|  | 	var match = PATH_QUERY_FRAGMENT_REGEXP.exec(str); | ||
|  | 	return { | ||
|  | 		path: match[1].replace(/\0(.)/g, "$1"), | ||
|  | 		query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", | ||
|  | 		fragment: match[3] || "" | ||
|  | 	}; | ||
|  | } | ||
|  | 
 | ||
|  | function dirname(path) { | ||
|  | 	if(path === "/") return "/"; | ||
|  | 	var i = path.lastIndexOf("/"); | ||
|  | 	var j = path.lastIndexOf("\\"); | ||
|  | 	var i2 = path.indexOf("/"); | ||
|  | 	var j2 = path.indexOf("\\"); | ||
|  | 	var idx = i > j ? i : j; | ||
|  | 	var idx2 = i > j ? i2 : j2; | ||
|  | 	if(idx < 0) return path; | ||
|  | 	if(idx === idx2) return path.substr(0, idx + 1); | ||
|  | 	return path.substr(0, idx); | ||
|  | } | ||
|  | 
 | ||
|  | function createLoaderObject(loader) { | ||
|  | 	var obj = { | ||
|  | 		path: null, | ||
|  | 		query: null, | ||
|  | 		fragment: null, | ||
|  | 		options: null, | ||
|  | 		ident: null, | ||
|  | 		normal: null, | ||
|  | 		pitch: null, | ||
|  | 		raw: null, | ||
|  | 		data: null, | ||
|  | 		pitchExecuted: false, | ||
|  | 		normalExecuted: false | ||
|  | 	}; | ||
|  | 	Object.defineProperty(obj, "request", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			return obj.path.replace(/#/g, "\0#") + obj.query.replace(/#/g, "\0#") + obj.fragment; | ||
|  | 		}, | ||
|  | 		set: function(value) { | ||
|  | 			if(typeof value === "string") { | ||
|  | 				var splittedRequest = parsePathQueryFragment(value); | ||
|  | 				obj.path = splittedRequest.path; | ||
|  | 				obj.query = splittedRequest.query; | ||
|  | 				obj.fragment = splittedRequest.fragment; | ||
|  | 				obj.options = undefined; | ||
|  | 				obj.ident = undefined; | ||
|  | 			} else { | ||
|  | 				if(!value.loader) | ||
|  | 					throw new Error("request should be a string or object with loader and options (" + JSON.stringify(value) + ")"); | ||
|  | 				obj.path = value.loader; | ||
|  | 				obj.fragment = value.fragment || ""; | ||
|  | 				obj.type = value.type; | ||
|  | 				obj.options = value.options; | ||
|  | 				obj.ident = value.ident; | ||
|  | 				if(obj.options === null) | ||
|  | 					obj.query = ""; | ||
|  | 				else if(obj.options === undefined) | ||
|  | 					obj.query = ""; | ||
|  | 				else if(typeof obj.options === "string") | ||
|  | 					obj.query = "?" + obj.options; | ||
|  | 				else if(obj.ident) | ||
|  | 					obj.query = "??" + obj.ident; | ||
|  | 				else if(typeof obj.options === "object" && obj.options.ident) | ||
|  | 					obj.query = "??" + obj.options.ident; | ||
|  | 				else | ||
|  | 					obj.query = "?" + JSON.stringify(obj.options); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	obj.request = loader; | ||
|  | 	if(Object.preventExtensions) { | ||
|  | 		Object.preventExtensions(obj); | ||
|  | 	} | ||
|  | 	return obj; | ||
|  | } | ||
|  | 
 | ||
|  | function runSyncOrAsync(fn, context, args, callback) { | ||
|  | 	var isSync = true; | ||
|  | 	var isDone = false; | ||
|  | 	var isError = false; // internal error
 | ||
|  | 	var reportedError = false; | ||
|  | 	context.async = function async() { | ||
|  | 		if(isDone) { | ||
|  | 			if(reportedError) return; // ignore
 | ||
|  | 			throw new Error("async(): The callback was already called."); | ||
|  | 		} | ||
|  | 		isSync = false; | ||
|  | 		return innerCallback; | ||
|  | 	}; | ||
|  | 	var innerCallback = context.callback = function() { | ||
|  | 		if(isDone) { | ||
|  | 			if(reportedError) return; // ignore
 | ||
|  | 			throw new Error("callback(): The callback was already called."); | ||
|  | 		} | ||
|  | 		isDone = true; | ||
|  | 		isSync = false; | ||
|  | 		try { | ||
|  | 			callback.apply(null, arguments); | ||
|  | 		} catch(e) { | ||
|  | 			isError = true; | ||
|  | 			throw e; | ||
|  | 		} | ||
|  | 	}; | ||
|  | 	try { | ||
|  | 		var result = (function LOADER_EXECUTION() { | ||
|  | 			return fn.apply(context, args); | ||
|  | 		}()); | ||
|  | 		if(isSync) { | ||
|  | 			isDone = true; | ||
|  | 			if(result === undefined) | ||
|  | 				return callback(); | ||
|  | 			if(result && typeof result === "object" && typeof result.then === "function") { | ||
|  | 				return result.then(function(r) { | ||
|  | 					callback(null, r); | ||
|  | 				}, callback); | ||
|  | 			} | ||
|  | 			return callback(null, result); | ||
|  | 		} | ||
|  | 	} catch(e) { | ||
|  | 		if(isError) throw e; | ||
|  | 		if(isDone) { | ||
|  | 			// loader is already "done", so we cannot use the callback function
 | ||
|  | 			// for better debugging we print the error on the console
 | ||
|  | 			if(typeof e === "object" && e.stack) console.error(e.stack); | ||
|  | 			else console.error(e); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		isDone = true; | ||
|  | 		reportedError = true; | ||
|  | 		callback(e); | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | function convertArgs(args, raw) { | ||
|  | 	if(!raw && Buffer.isBuffer(args[0])) | ||
|  | 		args[0] = utf8BufferToString(args[0]); | ||
|  | 	else if(raw && typeof args[0] === "string") | ||
|  | 		args[0] = Buffer.from(args[0], "utf-8"); | ||
|  | } | ||
|  | 
 | ||
|  | function iteratePitchingLoaders(options, loaderContext, callback) { | ||
|  | 	// abort after last loader
 | ||
|  | 	if(loaderContext.loaderIndex >= loaderContext.loaders.length) | ||
|  | 		return processResource(options, loaderContext, callback); | ||
|  | 
 | ||
|  | 	var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; | ||
|  | 
 | ||
|  | 	// iterate
 | ||
|  | 	if(currentLoaderObject.pitchExecuted) { | ||
|  | 		loaderContext.loaderIndex++; | ||
|  | 		return iteratePitchingLoaders(options, loaderContext, callback); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// load loader module
 | ||
|  | 	loadLoader(currentLoaderObject, function(err) { | ||
|  | 		if(err) { | ||
|  | 			loaderContext.cacheable(false); | ||
|  | 			return callback(err); | ||
|  | 		} | ||
|  | 		var fn = currentLoaderObject.pitch; | ||
|  | 		currentLoaderObject.pitchExecuted = true; | ||
|  | 		if(!fn) return iteratePitchingLoaders(options, loaderContext, callback); | ||
|  | 
 | ||
|  | 		runSyncOrAsync( | ||
|  | 			fn, | ||
|  | 			loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}], | ||
|  | 			function(err) { | ||
|  | 				if(err) return callback(err); | ||
|  | 				var args = Array.prototype.slice.call(arguments, 1); | ||
|  | 				// Determine whether to continue the pitching process based on
 | ||
|  | 				// argument values (as opposed to argument presence) in order
 | ||
|  | 				// to support synchronous and asynchronous usages.
 | ||
|  | 				var hasArg = args.some(function(value) { | ||
|  | 					return value !== undefined; | ||
|  | 				}); | ||
|  | 				if(hasArg) { | ||
|  | 					loaderContext.loaderIndex--; | ||
|  | 					iterateNormalLoaders(options, loaderContext, args, callback); | ||
|  | 				} else { | ||
|  | 					iteratePitchingLoaders(options, loaderContext, callback); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		); | ||
|  | 	}); | ||
|  | } | ||
|  | 
 | ||
|  | function processResource(options, loaderContext, callback) { | ||
|  | 	// set loader index to last loader
 | ||
|  | 	loaderContext.loaderIndex = loaderContext.loaders.length - 1; | ||
|  | 
 | ||
|  | 	var resourcePath = loaderContext.resourcePath; | ||
|  | 	if(resourcePath) { | ||
|  | 		options.processResource(loaderContext, resourcePath, function(err) { | ||
|  | 			if(err) return callback(err); | ||
|  | 			var args = Array.prototype.slice.call(arguments, 1); | ||
|  | 			options.resourceBuffer = args[0]; | ||
|  | 			iterateNormalLoaders(options, loaderContext, args, callback); | ||
|  | 		}); | ||
|  | 	} else { | ||
|  | 		iterateNormalLoaders(options, loaderContext, [null], callback); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | function iterateNormalLoaders(options, loaderContext, args, callback) { | ||
|  | 	if(loaderContext.loaderIndex < 0) | ||
|  | 		return callback(null, args); | ||
|  | 
 | ||
|  | 	var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; | ||
|  | 
 | ||
|  | 	// iterate
 | ||
|  | 	if(currentLoaderObject.normalExecuted) { | ||
|  | 		loaderContext.loaderIndex--; | ||
|  | 		return iterateNormalLoaders(options, loaderContext, args, callback); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var fn = currentLoaderObject.normal; | ||
|  | 	currentLoaderObject.normalExecuted = true; | ||
|  | 	if(!fn) { | ||
|  | 		return iterateNormalLoaders(options, loaderContext, args, callback); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	convertArgs(args, currentLoaderObject.raw); | ||
|  | 
 | ||
|  | 	runSyncOrAsync(fn, loaderContext, args, function(err) { | ||
|  | 		if(err) return callback(err); | ||
|  | 
 | ||
|  | 		var args = Array.prototype.slice.call(arguments, 1); | ||
|  | 		iterateNormalLoaders(options, loaderContext, args, callback); | ||
|  | 	}); | ||
|  | } | ||
|  | 
 | ||
|  | exports.getContext = function getContext(resource) { | ||
|  | 	var path = parsePathQueryFragment(resource).path; | ||
|  | 	return dirname(path); | ||
|  | }; | ||
|  | 
 | ||
|  | exports.runLoaders = function runLoaders(options, callback) { | ||
|  | 	// read options
 | ||
|  | 	var resource = options.resource || ""; | ||
|  | 	var loaders = options.loaders || []; | ||
|  | 	var loaderContext = options.context || {}; | ||
|  | 	var processResource = options.processResource || ((readResource, context, resource, callback) => { | ||
|  | 		context.addDependency(resource); | ||
|  | 		readResource(resource, callback); | ||
|  | 	}).bind(null, options.readResource || readFile); | ||
|  | 
 | ||
|  | 	//
 | ||
|  | 	var splittedResource = resource && parsePathQueryFragment(resource); | ||
|  | 	var resourcePath = splittedResource ? splittedResource.path : undefined; | ||
|  | 	var resourceQuery = splittedResource ? splittedResource.query : undefined; | ||
|  | 	var resourceFragment = splittedResource ? splittedResource.fragment : undefined; | ||
|  | 	var contextDirectory = resourcePath ? dirname(resourcePath) : null; | ||
|  | 
 | ||
|  | 	// execution state
 | ||
|  | 	var requestCacheable = true; | ||
|  | 	var fileDependencies = []; | ||
|  | 	var contextDependencies = []; | ||
|  | 	var missingDependencies = []; | ||
|  | 
 | ||
|  | 	// prepare loader objects
 | ||
|  | 	loaders = loaders.map(createLoaderObject); | ||
|  | 
 | ||
|  | 	loaderContext.context = contextDirectory; | ||
|  | 	loaderContext.loaderIndex = 0; | ||
|  | 	loaderContext.loaders = loaders; | ||
|  | 	loaderContext.resourcePath = resourcePath; | ||
|  | 	loaderContext.resourceQuery = resourceQuery; | ||
|  | 	loaderContext.resourceFragment = resourceFragment; | ||
|  | 	loaderContext.async = null; | ||
|  | 	loaderContext.callback = null; | ||
|  | 	loaderContext.cacheable = function cacheable(flag) { | ||
|  | 		if(flag === false) { | ||
|  | 			requestCacheable = false; | ||
|  | 		} | ||
|  | 	}; | ||
|  | 	loaderContext.dependency = loaderContext.addDependency = function addDependency(file) { | ||
|  | 		fileDependencies.push(file); | ||
|  | 	}; | ||
|  | 	loaderContext.addContextDependency = function addContextDependency(context) { | ||
|  | 		contextDependencies.push(context); | ||
|  | 	}; | ||
|  | 	loaderContext.addMissingDependency = function addMissingDependency(context) { | ||
|  | 		missingDependencies.push(context); | ||
|  | 	}; | ||
|  | 	loaderContext.getDependencies = function getDependencies() { | ||
|  | 		return fileDependencies.slice(); | ||
|  | 	}; | ||
|  | 	loaderContext.getContextDependencies = function getContextDependencies() { | ||
|  | 		return contextDependencies.slice(); | ||
|  | 	}; | ||
|  | 	loaderContext.getMissingDependencies = function getMissingDependencies() { | ||
|  | 		return missingDependencies.slice(); | ||
|  | 	}; | ||
|  | 	loaderContext.clearDependencies = function clearDependencies() { | ||
|  | 		fileDependencies.length = 0; | ||
|  | 		contextDependencies.length = 0; | ||
|  | 		missingDependencies.length = 0; | ||
|  | 		requestCacheable = true; | ||
|  | 	}; | ||
|  | 	Object.defineProperty(loaderContext, "resource", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			if(loaderContext.resourcePath === undefined) | ||
|  | 				return undefined; | ||
|  | 			return loaderContext.resourcePath.replace(/#/g, "\0#") + loaderContext.resourceQuery.replace(/#/g, "\0#") + loaderContext.resourceFragment; | ||
|  | 		}, | ||
|  | 		set: function(value) { | ||
|  | 			var splittedResource = value && parsePathQueryFragment(value); | ||
|  | 			loaderContext.resourcePath = splittedResource ? splittedResource.path : undefined; | ||
|  | 			loaderContext.resourceQuery = splittedResource ? splittedResource.query : undefined; | ||
|  | 			loaderContext.resourceFragment = splittedResource ? splittedResource.fragment : undefined; | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "request", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			return loaderContext.loaders.map(function(o) { | ||
|  | 				return o.request; | ||
|  | 			}).concat(loaderContext.resource || "").join("!"); | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "remainingRequest", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource) | ||
|  | 				return ""; | ||
|  | 			return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) { | ||
|  | 				return o.request; | ||
|  | 			}).concat(loaderContext.resource || "").join("!"); | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "currentRequest", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			return loaderContext.loaders.slice(loaderContext.loaderIndex).map(function(o) { | ||
|  | 				return o.request; | ||
|  | 			}).concat(loaderContext.resource || "").join("!"); | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "previousRequest", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(function(o) { | ||
|  | 				return o.request; | ||
|  | 			}).join("!"); | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "query", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			var entry = loaderContext.loaders[loaderContext.loaderIndex]; | ||
|  | 			return entry.options && typeof entry.options === "object" ? entry.options : entry.query; | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	Object.defineProperty(loaderContext, "data", { | ||
|  | 		enumerable: true, | ||
|  | 		get: function() { | ||
|  | 			return loaderContext.loaders[loaderContext.loaderIndex].data; | ||
|  | 		} | ||
|  | 	}); | ||
|  | 
 | ||
|  | 	// finish loader context
 | ||
|  | 	if(Object.preventExtensions) { | ||
|  | 		Object.preventExtensions(loaderContext); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var processOptions = { | ||
|  | 		resourceBuffer: null, | ||
|  | 		processResource: processResource | ||
|  | 	}; | ||
|  | 	iteratePitchingLoaders(processOptions, loaderContext, function(err, result) { | ||
|  | 		if(err) { | ||
|  | 			return callback(err, { | ||
|  | 				cacheable: requestCacheable, | ||
|  | 				fileDependencies: fileDependencies, | ||
|  | 				contextDependencies: contextDependencies, | ||
|  | 				missingDependencies: missingDependencies | ||
|  | 			}); | ||
|  | 		} | ||
|  | 		callback(null, { | ||
|  | 			result: result, | ||
|  | 			resourceBuffer: processOptions.resourceBuffer, | ||
|  | 			cacheable: requestCacheable, | ||
|  | 			fileDependencies: fileDependencies, | ||
|  | 			contextDependencies: contextDependencies, | ||
|  | 			missingDependencies: missingDependencies | ||
|  | 		}); | ||
|  | 	}); | ||
|  | }; |