Projekt

Obecné

Profil

Stáhnout (8.63 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
const util = require("util");
8

    
9
const Tapable = require("tapable/lib/Tapable");
10
const SyncHook = require("tapable/lib/SyncHook");
11
const AsyncSeriesBailHook = require("tapable/lib/AsyncSeriesBailHook");
12
const AsyncSeriesHook = require("tapable/lib/AsyncSeriesHook");
13
const createInnerContext = require("./createInnerContext");
14

    
15
const REGEXP_NOT_MODULE = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i;
16
const REGEXP_DIRECTORY = /[\/\\]$/i;
17

    
18
const memoryFsJoin = require("memory-fs/lib/join");
19
const memoizedJoin = new Map();
20
const memoryFsNormalize = require("memory-fs/lib/normalize");
21

    
22
function withName(name, hook) {
23
	hook.name = name;
24
	return hook;
25
}
26

    
27
function toCamelCase(str) {
28
	return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());
29
}
30

    
31
const deprecatedPushToMissing = util.deprecate((set, item) => {
32
	set.add(item);
33
}, "Resolver: 'missing' is now a Set. Use add instead of push.");
34

    
35
const deprecatedResolveContextInCallback = util.deprecate((x) => {
36
	return x;
37
}, "Resolver: The callback argument was splitted into resolveContext and callback.");
38

    
39
const deprecatedHookAsString = util.deprecate((x) => {
40
	return x;
41
}, "Resolver#doResolve: The type arguments (string) is now a hook argument (Hook). Pass a reference to the hook instead.");
42

    
43
class Resolver extends Tapable {
44
	constructor(fileSystem) {
45
		super();
46
		this.fileSystem = fileSystem;
47
		this.hooks = {
48
			resolveStep: withName("resolveStep", new SyncHook(["hook", "request"])),
49
			noResolve: withName("noResolve", new SyncHook(["request", "error"])),
50
			resolve: withName("resolve", new AsyncSeriesBailHook(["request", "resolveContext"])),
51
			result: new AsyncSeriesHook(["result", "resolveContext"])
52
		};
53
		this._pluginCompat.tap("Resolver: before/after", options => {
54
			if(/^before-/.test(options.name)) {
55
				options.name = options.name.substr(7);
56
				options.stage = -10;
57
			} else if(/^after-/.test(options.name)) {
58
				options.name = options.name.substr(6);
59
				options.stage = 10;
60
			}
61
		});
62
		this._pluginCompat.tap("Resolver: step hooks", options => {
63
			const name = options.name;
64
			const stepHook = !/^resolve(-s|S)tep$|^no(-r|R)esolve$/.test(name);
65
			if(stepHook) {
66
				options.async = true;
67
				this.ensureHook(name);
68
				const fn = options.fn;
69
				options.fn = (request, resolverContext, callback) => {
70
					const innerCallback = (err, result) => {
71
						if(err) return callback(err);
72
						if(result !== undefined) return callback(null, result);
73
						callback();
74
					};
75
					for(const key in resolverContext) {
76
						innerCallback[key] = resolverContext[key];
77
					}
78
					fn.call(this, request, innerCallback);
79
				};
80
			}
81
		});
82
	}
83

    
84
	ensureHook(name) {
85
		if(typeof name !== "string") return name;
86
		name = toCamelCase(name);
87
		if(/^before/.test(name)) {
88
			return this.ensureHook(name[6].toLowerCase() + name.substr(7)).withOptions({
89
				stage: -10
90
			});
91
		}
92
		if(/^after/.test(name)) {
93
			return this.ensureHook(name[5].toLowerCase() + name.substr(6)).withOptions({
94
				stage: 10
95
			});
96
		}
97
		const hook = this.hooks[name];
98
		if(!hook) {
99
			return this.hooks[name] = withName(name, new AsyncSeriesBailHook(["request", "resolveContext"]));
100
		}
101
		return hook;
102
	}
103

    
104
	getHook(name) {
105
		if(typeof name !== "string") return name;
106
		name = toCamelCase(name);
107
		if(/^before/.test(name)) {
108
			return this.getHook(name[6].toLowerCase() + name.substr(7)).withOptions({
109
				stage: -10
110
			});
111
		}
112
		if(/^after/.test(name)) {
113
			return this.getHook(name[5].toLowerCase() + name.substr(6)).withOptions({
114
				stage: 10
115
			});
116
		}
117
		const hook = this.hooks[name];
118
		if(!hook) {
119
			throw new Error(`Hook ${name} doesn't exist`);
120
		}
121
		return hook;
122
	}
123

    
124
	resolveSync(context, path, request) {
125
		let err, result, sync = false;
126
		this.resolve(context, path, request, {}, (e, r) => {
127
			err = e;
128
			result = r;
129
			sync = true;
130
		});
131
		if(!sync) throw new Error("Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!");
132
		if(err) throw err;
133
		return result;
134
	}
135

    
136
	resolve(context, path, request, resolveContext, callback) {
137
		// TODO remove in enhanced-resolve 5
138
		// For backward compatiblity START
139
		if(typeof callback !== "function") {
140
			callback = deprecatedResolveContextInCallback(resolveContext);
141
			// resolveContext is a function containing additional properties
142
			// It's now used for resolveContext and callback
143
		}
144
		// END
145
		const obj = {
146
			context: context,
147
			path: path,
148
			request: request
149
		};
150

    
151
		const message = "resolve '" + request + "' in '" + path + "'";
152

    
153
		// Try to resolve assuming there is no error
154
		// We don't log stuff in this case
155
		return this.doResolve(this.hooks.resolve, obj, message, {
156
			missing: resolveContext.missing,
157
			stack: resolveContext.stack
158
		}, (err, result) => {
159
			if(!err && result) {
160
				return callback(null, result.path === false ? false : result.path + (result.query || ""), result);
161
			}
162

    
163
			const localMissing = new Set();
164
			// TODO remove in enhanced-resolve 5
165
			localMissing.push = item => deprecatedPushToMissing(localMissing, item);
166
			const log = [];
167

    
168
			return this.doResolve(this.hooks.resolve, obj, message, {
169
				log: msg => {
170
					if(resolveContext.log) {
171
						resolveContext.log(msg);
172
					}
173
					log.push(msg);
174
				},
175
				missing: localMissing,
176
				stack: resolveContext.stack
177
			}, (err, result) => {
178
				if(err) return callback(err);
179

    
180
				const error = new Error("Can't " + message);
181
				error.details = log.join("\n");
182
				error.missing = Array.from(localMissing);
183
				this.hooks.noResolve.call(obj, error);
184
				return callback(error);
185
			});
186
		});
187
	}
188

    
189
	doResolve(hook, request, message, resolveContext, callback) {
190
		// TODO remove in enhanced-resolve 5
191
		// For backward compatiblity START
192
		if(typeof callback !== "function") {
193
			callback = deprecatedResolveContextInCallback(resolveContext);
194
			// resolveContext is a function containing additional properties
195
			// It's now used for resolveContext and callback
196
		}
197
		if(typeof hook === "string") {
198
			const name = toCamelCase(hook);
199
			hook = deprecatedHookAsString(this.hooks[name]);
200
			if(!hook) {
201
				throw new Error(`Hook "${name}" doesn't exist`);
202
			}
203
		}
204
		// END
205
		if(typeof callback !== "function") throw new Error("callback is not a function " + Array.from(arguments));
206
		if(!resolveContext) throw new Error("resolveContext is not an object " + Array.from(arguments));
207

    
208
		const stackLine = hook.name + ": (" + request.path + ") " +
209
			(request.request || "") + (request.query || "") +
210
			(request.directory ? " directory" : "") +
211
			(request.module ? " module" : "");
212

    
213
		let newStack;
214
		if(resolveContext.stack) {
215
			newStack = new Set(resolveContext.stack);
216
			if(resolveContext.stack.has(stackLine)) {
217
				// Prevent recursion
218
				const recursionError = new Error("Recursion in resolving\nStack:\n  " + Array.from(newStack).join("\n  "));
219
				recursionError.recursion = true;
220
				if(resolveContext.log) resolveContext.log("abort resolving because of recursion");
221
				return callback(recursionError);
222
			}
223
			newStack.add(stackLine);
224
		} else {
225
			newStack = new Set([stackLine]);
226
		}
227
		this.hooks.resolveStep.call(hook, request);
228

    
229
		if(hook.isUsed()) {
230
			const innerContext = createInnerContext({
231
				log: resolveContext.log,
232
				missing: resolveContext.missing,
233
				stack: newStack
234
			}, message);
235
			return hook.callAsync(request, innerContext, (err, result) => {
236
				if(err) return callback(err);
237
				if(result) return callback(null, result);
238
				callback();
239
			});
240
		} else {
241
			callback();
242
		}
243
	}
244

    
245
	parse(identifier) {
246
		if(identifier === "") return null;
247
		const part = {
248
			request: "",
249
			query: "",
250
			module: false,
251
			directory: false,
252
			file: false
253
		};
254
		const idxQuery = identifier.indexOf("?");
255
		if(idxQuery === 0) {
256
			part.query = identifier;
257
		} else if(idxQuery > 0) {
258
			part.request = identifier.slice(0, idxQuery);
259
			part.query = identifier.slice(idxQuery);
260
		} else {
261
			part.request = identifier;
262
		}
263
		if(part.request) {
264
			part.module = this.isModule(part.request);
265
			part.directory = this.isDirectory(part.request);
266
			if(part.directory) {
267
				part.request = part.request.substr(0, part.request.length - 1);
268
			}
269
		}
270
		return part;
271
	}
272

    
273
	isModule(path) {
274
		return !REGEXP_NOT_MODULE.test(path);
275
	}
276

    
277
	isDirectory(path) {
278
		return REGEXP_DIRECTORY.test(path);
279
	}
280

    
281
	join(path, request) {
282
		let cacheEntry;
283
		let pathCache = memoizedJoin.get(path);
284
		if(typeof pathCache === "undefined") {
285
			memoizedJoin.set(path, pathCache = new Map());
286
		} else {
287
			cacheEntry = pathCache.get(request);
288
			if(typeof cacheEntry !== "undefined")
289
				return cacheEntry;
290
		}
291
		cacheEntry = memoryFsJoin(path, request);
292
		pathCache.set(request, cacheEntry);
293
		return cacheEntry;
294
	}
295

    
296
	normalize(path) {
297
		return memoryFsNormalize(path);
298
	}
299
}
300

    
301
module.exports = Resolver;
(24-24/39)