| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 | /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */"use strict";const path = require("path");const asyncLib = require("neo-async");const {	Tapable,	AsyncSeriesWaterfallHook,	SyncWaterfallHook,	SyncBailHook,	SyncHook,	HookMap} = require("tapable");const NormalModule = require("./NormalModule");const RawModule = require("./RawModule");const RuleSet = require("./RuleSet");const { cachedCleverMerge } = require("./util/cleverMerge");const EMPTY_RESOLVE_OPTIONS = {};const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;const loaderToIdent = data => {	if (!data.options) {		return data.loader;	}	if (typeof data.options === "string") {		return data.loader + "?" + data.options;	}	if (typeof data.options !== "object") {		throw new Error("loader options must be string or object");	}	if (data.ident) {		return data.loader + "??" + data.ident;	}	return data.loader + "?" + JSON.stringify(data.options);};const identToLoaderRequest = resultString => {	const idx = resultString.indexOf("?");	if (idx >= 0) {		const loader = resultString.substr(0, idx);		const options = resultString.substr(idx + 1);		return {			loader,			options		};	} else {		return {			loader: resultString,			options: undefined		};	}};const dependencyCache = new WeakMap();class NormalModuleFactory extends Tapable {	constructor(context, resolverFactory, options) {		super();		this.hooks = {			resolver: new SyncWaterfallHook(["resolver"]),			factory: new SyncWaterfallHook(["factory"]),			beforeResolve: new AsyncSeriesWaterfallHook(["data"]),			afterResolve: new AsyncSeriesWaterfallHook(["data"]),			createModule: new SyncBailHook(["data"]),			module: new SyncWaterfallHook(["module", "data"]),			createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),			parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),			createGenerator: new HookMap(				() => new SyncBailHook(["generatorOptions"])			),			generator: new HookMap(				() => new SyncHook(["generator", "generatorOptions"])			)		};		this._pluginCompat.tap("NormalModuleFactory", options => {			switch (options.name) {				case "before-resolve":				case "after-resolve":					options.async = true;					break;				case "parser":					this.hooks.parser						.for("javascript/auto")						.tap(options.fn.name || "unnamed compat plugin", options.fn);					return true;			}			let match;			match = /^parser (.+)$/.exec(options.name);			if (match) {				this.hooks.parser					.for(match[1])					.tap(						options.fn.name || "unnamed compat plugin",						options.fn.bind(this)					);				return true;			}			match = /^create-parser (.+)$/.exec(options.name);			if (match) {				this.hooks.createParser					.for(match[1])					.tap(						options.fn.name || "unnamed compat plugin",						options.fn.bind(this)					);				return true;			}		});		this.resolverFactory = resolverFactory;		this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));		this.cachePredicate =			typeof options.unsafeCache === "function"				? options.unsafeCache				: Boolean.bind(null, options.unsafeCache);		this.context = context || "";		this.parserCache = Object.create(null);		this.generatorCache = Object.create(null);		this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {			let resolver = this.hooks.resolver.call(null);			// Ignored			if (!resolver) return callback();			resolver(result, (err, data) => {				if (err) return callback(err);				// Ignored				if (!data) return callback();				// direct module				if (typeof data.source === "function") return callback(null, data);				this.hooks.afterResolve.callAsync(data, (err, result) => {					if (err) return callback(err);					// Ignored					if (!result) return callback();					let createdModule = this.hooks.createModule.call(result);					if (!createdModule) {						if (!result.request) {							return callback(new Error("Empty dependency (no request)"));						}						createdModule = new NormalModule(result);					}					createdModule = this.hooks.module.call(createdModule, result);					return callback(null, createdModule);				});			});		});		this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {			const contextInfo = data.contextInfo;			const context = data.context;			const request = data.request;			const loaderResolver = this.getResolver("loader");			const normalResolver = this.getResolver("normal", data.resolveOptions);			let matchResource = undefined;			let requestWithoutMatchResource = request;			const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);			if (matchResourceMatch) {				matchResource = matchResourceMatch[1];				if (/^\.\.?\//.test(matchResource)) {					matchResource = path.join(context, matchResource);				}				requestWithoutMatchResource = request.substr(					matchResourceMatch[0].length				);			}			const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");			const noAutoLoaders =				noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");			const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!");			let elements = requestWithoutMatchResource				.replace(/^-?!+/, "")				.replace(/!!+/g, "!")				.split("!");			let resource = elements.pop();			elements = elements.map(identToLoaderRequest);			asyncLib.parallel(				[					callback =>						this.resolveRequestArray(							contextInfo,							context,							elements,							loaderResolver,							callback						),					callback => {						if (resource === "" || resource[0] === "?") {							return callback(null, {								resource							});						}						normalResolver.resolve(							contextInfo,							context,							resource,							{},							(err, resource, resourceResolveData) => {								if (err) return callback(err);								callback(null, {									resourceResolveData,									resource								});							}						);					}				],				(err, results) => {					if (err) return callback(err);					let loaders = results[0];					const resourceResolveData = results[1].resourceResolveData;					resource = results[1].resource;					// translate option idents					try {						for (const item of loaders) {							if (typeof item.options === "string" && item.options[0] === "?") {								const ident = item.options.substr(1);								item.options = this.ruleSet.findOptionsByIdent(ident);								item.ident = ident;							}						}					} catch (e) {						return callback(e);					}					if (resource === false) {						// ignored						return callback(							null,							new RawModule(								"/* (ignored) */",								`ignored ${context} ${request}`,								`${request} (ignored)`							)						);					}					const userRequest =						(matchResource !== undefined ? `${matchResource}!=!` : "") +						loaders							.map(loaderToIdent)							.concat([resource])							.join("!");					let resourcePath =						matchResource !== undefined ? matchResource : resource;					let resourceQuery = "";					const queryIndex = resourcePath.indexOf("?");					if (queryIndex >= 0) {						resourceQuery = resourcePath.substr(queryIndex);						resourcePath = resourcePath.substr(0, queryIndex);					}					const result = this.ruleSet.exec({						resource: resourcePath,						realResource:							matchResource !== undefined								? resource.replace(/\?.*/, "")								: resourcePath,						resourceQuery,						issuer: contextInfo.issuer,						compiler: contextInfo.compiler					});					const settings = {};					const useLoadersPost = [];					const useLoaders = [];					const useLoadersPre = [];					for (const r of result) {						if (r.type === "use") {							if (r.enforce === "post" && !noPrePostAutoLoaders) {								useLoadersPost.push(r.value);							} else if (								r.enforce === "pre" &&								!noPreAutoLoaders &&								!noPrePostAutoLoaders							) {								useLoadersPre.push(r.value);							} else if (								!r.enforce &&								!noAutoLoaders &&								!noPrePostAutoLoaders							) {								useLoaders.push(r.value);							}						} else if (							typeof r.value === "object" &&							r.value !== null &&							typeof settings[r.type] === "object" &&							settings[r.type] !== null						) {							settings[r.type] = cachedCleverMerge(settings[r.type], r.value);						} else {							settings[r.type] = r.value;						}					}					asyncLib.parallel(						[							this.resolveRequestArray.bind(								this,								contextInfo,								this.context,								useLoadersPost,								loaderResolver							),							this.resolveRequestArray.bind(								this,								contextInfo,								this.context,								useLoaders,								loaderResolver							),							this.resolveRequestArray.bind(								this,								contextInfo,								this.context,								useLoadersPre,								loaderResolver							)						],						(err, results) => {							if (err) return callback(err);							if (matchResource === undefined) {								loaders = results[0].concat(loaders, results[1], results[2]);							} else {								loaders = results[0].concat(results[1], loaders, results[2]);							}							process.nextTick(() => {								const type = settings.type;								const resolveOptions = settings.resolve;								callback(null, {									context: context,									request: loaders										.map(loaderToIdent)										.concat([resource])										.join("!"),									dependencies: data.dependencies,									userRequest,									rawRequest: request,									loaders,									resource,									matchResource,									resourceResolveData,									settings,									type,									parser: this.getParser(type, settings.parser),									generator: this.getGenerator(type, settings.generator),									resolveOptions								});							});						}					);				}			);		});	}	create(data, callback) {		const dependencies = data.dependencies;		const cacheEntry = dependencyCache.get(dependencies[0]);		if (cacheEntry) return callback(null, cacheEntry);		const context = data.context || this.context;		const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;		const request = dependencies[0].request;		const contextInfo = data.contextInfo || {};		this.hooks.beforeResolve.callAsync(			{				contextInfo,				resolveOptions,				context,				request,				dependencies			},			(err, result) => {				if (err) return callback(err);				// Ignored				if (!result) return callback();				const factory = this.hooks.factory.call(null);				// Ignored				if (!factory) return callback();				factory(result, (err, module) => {					if (err) return callback(err);					if (module && this.cachePredicate(module)) {						for (const d of dependencies) {							dependencyCache.set(d, module);						}					}					callback(null, module);				});			}		);	}	resolveRequestArray(contextInfo, context, array, resolver, callback) {		if (array.length === 0) return callback(null, []);		asyncLib.map(			array,			(item, callback) => {				resolver.resolve(					contextInfo,					context,					item.loader,					{},					(err, result) => {						if (							err &&							/^[^/]*$/.test(item.loader) &&							!/-loader$/.test(item.loader)						) {							return resolver.resolve(								contextInfo,								context,								item.loader + "-loader",								{},								err2 => {									if (!err2) {										err.message =											err.message +											"\n" +											"BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +											`                 You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +											"                 see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";									}									callback(err);								}							);						}						if (err) return callback(err);						const optionsOnly = item.options							? {									options: item.options							  }							: undefined;						return callback(							null,							Object.assign({}, item, identToLoaderRequest(result), optionsOnly)						);					}				);			},			callback		);	}	getParser(type, parserOptions) {		let ident = type;		if (parserOptions) {			if (parserOptions.ident) {				ident = `${type}|${parserOptions.ident}`;			} else {				ident = JSON.stringify([type, parserOptions]);			}		}		if (ident in this.parserCache) {			return this.parserCache[ident];		}		return (this.parserCache[ident] = this.createParser(type, parserOptions));	}	createParser(type, parserOptions = {}) {		const parser = this.hooks.createParser.for(type).call(parserOptions);		if (!parser) {			throw new Error(`No parser registered for ${type}`);		}		this.hooks.parser.for(type).call(parser, parserOptions);		return parser;	}	getGenerator(type, generatorOptions) {		let ident = type;		if (generatorOptions) {			if (generatorOptions.ident) {				ident = `${type}|${generatorOptions.ident}`;			} else {				ident = JSON.stringify([type, generatorOptions]);			}		}		if (ident in this.generatorCache) {			return this.generatorCache[ident];		}		return (this.generatorCache[ident] = this.createGenerator(			type,			generatorOptions		));	}	createGenerator(type, generatorOptions = {}) {		const generator = this.hooks.createGenerator			.for(type)			.call(generatorOptions);		if (!generator) {			throw new Error(`No generator registered for ${type}`);		}		this.hooks.generator.for(type).call(generator, generatorOptions);		return generator;	}	getResolver(type, resolveOptions) {		return this.resolverFactory.get(			type,			resolveOptions || EMPTY_RESOLVE_OPTIONS		);	}}module.exports = NormalModuleFactory;
 |