Projekt

Obecné

Profil

Stáhnout (10.7 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
var fs = require("fs");
6
var readFile = fs.readFile.bind(fs);
7
var loadLoader = require("./loadLoader");
8
9
function utf8BufferToString(buf) {
10
	var str = buf.toString("utf-8");
11
	if(str.charCodeAt(0) === 0xFEFF) {
12
		return str.substr(1);
13
	} else {
14
		return str;
15
	}
16
}
17
18
function splitQuery(req) {
19
	var i = req.indexOf("?");
20
	if(i < 0) return [req, ""];
21
	return [req.substr(0, i), req.substr(i)];
22
}
23
24
function dirname(path) {
25
	if(path === "/") return "/";
26
	var i = path.lastIndexOf("/");
27
	var j = path.lastIndexOf("\\");
28
	var i2 = path.indexOf("/");
29
	var j2 = path.indexOf("\\");
30
	var idx = i > j ? i : j;
31
	var idx2 = i > j ? i2 : j2;
32
	if(idx < 0) return path;
33
	if(idx === idx2) return path.substr(0, idx + 1);
34
	return path.substr(0, idx);
35
}
36
37
function createLoaderObject(loader) {
38
	var obj = {
39
		path: null,
40
		query: null,
41
		options: null,
42
		ident: null,
43
		normal: null,
44
		pitch: null,
45
		raw: null,
46
		data: null,
47
		pitchExecuted: false,
48
		normalExecuted: false
49
	};
50
	Object.defineProperty(obj, "request", {
51
		enumerable: true,
52
		get: function() {
53
			return obj.path + obj.query;
54
		},
55
		set: function(value) {
56
			if(typeof value === "string") {
57
				var splittedRequest = splitQuery(value);
58
				obj.path = splittedRequest[0];
59
				obj.query = splittedRequest[1];
60
				obj.options = undefined;
61
				obj.ident = undefined;
62
			} else {
63
				if(!value.loader)
64
					throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")");
65
				obj.path = value.loader;
66
				obj.options = value.options;
67
				obj.ident = value.ident;
68
				if(obj.options === null)
69
					obj.query = "";
70
				else if(obj.options === undefined)
71
					obj.query = "";
72
				else if(typeof obj.options === "string")
73
					obj.query = "?" + obj.options;
74
				else if(obj.ident)
75
					obj.query = "??" + obj.ident;
76
				else if(typeof obj.options === "object" && obj.options.ident)
77
					obj.query = "??" + obj.options.ident;
78
				else
79
					obj.query = "?" + JSON.stringify(obj.options);
80
			}
81
		}
82
	});
83
	obj.request = loader;
84
	if(Object.preventExtensions) {
85
		Object.preventExtensions(obj);
86
	}
87
	return obj;
88
}
89
90
function runSyncOrAsync(fn, context, args, callback) {
91
	var isSync = true;
92
	var isDone = false;
93
	var isError = false; // internal error
94
	var reportedError = false;
95
	context.async = function async() {
96
		if(isDone) {
97
			if(reportedError) return; // ignore
98
			throw new Error("async(): The callback was already called.");
99
		}
100
		isSync = false;
101
		return innerCallback;
102
	};
103
	var innerCallback = context.callback = function() {
104
		if(isDone) {
105
			if(reportedError) return; // ignore
106
			throw new Error("callback(): The callback was already called.");
107
		}
108
		isDone = true;
109
		isSync = false;
110
		try {
111
			callback.apply(null, arguments);
112
		} catch(e) {
113
			isError = true;
114
			throw e;
115
		}
116
	};
117
	try {
118
		var result = (function LOADER_EXECUTION() {
119
			return fn.apply(context, args);
120
		}());
121
		if(isSync) {
122
			isDone = true;
123
			if(result === undefined)
124
				return callback();
125
			if(result && typeof result === "object" && typeof result.then === "function") {
126
				return result.then(function(r) {
127
					callback(null, r);
128
				}, callback);
129
			}
130
			return callback(null, result);
131
		}
132
	} catch(e) {
133
		if(isError) throw e;
134
		if(isDone) {
135
			// loader is already "done", so we cannot use the callback function
136
			// for better debugging we print the error on the console
137
			if(typeof e === "object" && e.stack) console.error(e.stack);
138
			else console.error(e);
139
			return;
140
		}
141
		isDone = true;
142
		reportedError = true;
143
		callback(e);
144
	}
145
146
}
147
148
function convertArgs(args, raw) {
149
	if(!raw && Buffer.isBuffer(args[0]))
150
		args[0] = utf8BufferToString(args[0]);
151
	else if(raw && typeof args[0] === "string")
152
		args[0] = new Buffer(args[0], "utf-8"); // eslint-disable-line
153
}
154
155
function iteratePitchingLoaders(options, loaderContext, callback) {
156
	// abort after last loader
157
	if(loaderContext.loaderIndex >= loaderContext.loaders.length)
158
		return processResource(options, loaderContext, callback);
159
160
	var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
161
162
	// iterate
163
	if(currentLoaderObject.pitchExecuted) {
164
		loaderContext.loaderIndex++;
165
		return iteratePitchingLoaders(options, loaderContext, callback);
166
	}
167
168
	// load loader module
169
	loadLoader(currentLoaderObject, function(err) {
170
		if(err) {
171
			loaderContext.cacheable(false);
172
			return callback(err);
173
		}
174
		var fn = currentLoaderObject.pitch;
175
		currentLoaderObject.pitchExecuted = true;
176
		if(!fn) return iteratePitchingLoaders(options, loaderContext, callback);
177
178
		runSyncOrAsync(
179
			fn,
180
			loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}],
181
			function(err) {
182
				if(err) return callback(err);
183
				var args = Array.prototype.slice.call(arguments, 1);
184
				if(args.length > 0) {
185
					loaderContext.loaderIndex--;
186
					iterateNormalLoaders(options, loaderContext, args, callback);
187
				} else {
188
					iteratePitchingLoaders(options, loaderContext, callback);
189
				}
190
			}
191
		);
192
	});
193
}
194
195
function processResource(options, loaderContext, callback) {
196
	// set loader index to last loader
197
	loaderContext.loaderIndex = loaderContext.loaders.length - 1;
198
199
	var resourcePath = loaderContext.resourcePath;
200
	if(resourcePath) {
201
		loaderContext.addDependency(resourcePath);
202
		options.readResource(resourcePath, function(err, buffer) {
203
			if(err) return callback(err);
204
			options.resourceBuffer = buffer;
205
			iterateNormalLoaders(options, loaderContext, [buffer], callback);
206
		});
207
	} else {
208
		iterateNormalLoaders(options, loaderContext, [null], callback);
209
	}
210
}
211
212
function iterateNormalLoaders(options, loaderContext, args, callback) {
213
	if(loaderContext.loaderIndex < 0)
214
		return callback(null, args);
215
216
	var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
217
218
	// iterate
219
	if(currentLoaderObject.normalExecuted) {
220
		loaderContext.loaderIndex--;
221
		return iterateNormalLoaders(options, loaderContext, args, callback);
222
	}
223
224
	var fn = currentLoaderObject.normal;
225
	currentLoaderObject.normalExecuted = true;
226
	if(!fn) {
227
		return iterateNormalLoaders(options, loaderContext, args, callback);
228
	}
229
230
	convertArgs(args, currentLoaderObject.raw);
231
232
	runSyncOrAsync(fn, loaderContext, args, function(err) {
233
		if(err) return callback(err);
234
235
		var args = Array.prototype.slice.call(arguments, 1);
236
		iterateNormalLoaders(options, loaderContext, args, callback);
237
	});
238
}
239
240
exports.getContext = function getContext(resource) {
241
	var splitted = splitQuery(resource);
242
	return dirname(splitted[0]);
243
};
244
245
exports.runLoaders = function runLoaders(options, callback) {
246
	// read options
247
	var resource = options.resource || "";
248
	var loaders = options.loaders || [];
249
	var loaderContext = options.context || {};
250
	var readResource = options.readResource || readFile;
251
252
	//
253
	var splittedResource = resource && splitQuery(resource);
254
	var resourcePath = splittedResource ? splittedResource[0] : undefined;
255
	var resourceQuery = splittedResource ? splittedResource[1] : undefined;
256
	var contextDirectory = resourcePath ? dirname(resourcePath) : null;
257
258
	// execution state
259
	var requestCacheable = true;
260
	var fileDependencies = [];
261
	var contextDependencies = [];
262
263
	// prepare loader objects
264
	loaders = loaders.map(createLoaderObject);
265
266
	loaderContext.context = contextDirectory;
267
	loaderContext.loaderIndex = 0;
268
	loaderContext.loaders = loaders;
269
	loaderContext.resourcePath = resourcePath;
270
	loaderContext.resourceQuery = resourceQuery;
271
	loaderContext.async = null;
272
	loaderContext.callback = null;
273
	loaderContext.cacheable = function cacheable(flag) {
274
		if(flag === false) {
275
			requestCacheable = false;
276
		}
277
	};
278
	loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
279
		fileDependencies.push(file);
280
	};
281
	loaderContext.addContextDependency = function addContextDependency(context) {
282
		contextDependencies.push(context);
283
	};
284
	loaderContext.getDependencies = function getDependencies() {
285
		return fileDependencies.slice();
286
	};
287
	loaderContext.getContextDependencies = function getContextDependencies() {
288
		return contextDependencies.slice();
289
	};
290
	loaderContext.clearDependencies = function clearDependencies() {
291
		fileDependencies.length = 0;
292
		contextDependencies.length = 0;
293
		requestCacheable = true;
294
	};
295
	Object.defineProperty(loaderContext, "resource", {
296
		enumerable: true,
297
		get: function() {
298
			if(loaderContext.resourcePath === undefined)
299
				return undefined;
300
			return loaderContext.resourcePath + loaderContext.resourceQuery;
301
		},
302
		set: function(value) {
303
			var splittedResource = value && splitQuery(value);
304
			loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined;
305
			loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined;
306
		}
307
	});
308
	Object.defineProperty(loaderContext, "request", {
309
		enumerable: true,
310
		get: function() {
311
			return loaderContext.loaders.map(function(o) {
312
				return o.request;
313
			}).concat(loaderContext.resource || "").join("!");
314
		}
315
	});
316
	Object.defineProperty(loaderContext, "remainingRequest", {
317
		enumerable: true,
318
		get: function() {
319
			if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource)
320
				return "";
321
			return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) {
322
				return o.request;
323
			}).concat(loaderContext.resource || "").join("!");
324
		}
325
	});
326
	Object.defineProperty(loaderContext, "currentRequest", {
327
		enumerable: true,
328
		get: function() {
329
			return loaderContext.loaders.slice(loaderContext.loaderIndex).map(function(o) {
330
				return o.request;
331
			}).concat(loaderContext.resource || "").join("!");
332
		}
333
	});
334
	Object.defineProperty(loaderContext, "previousRequest", {
335
		enumerable: true,
336
		get: function() {
337
			return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(function(o) {
338
				return o.request;
339
			}).join("!");
340
		}
341
	});
342
	Object.defineProperty(loaderContext, "query", {
343
		enumerable: true,
344
		get: function() {
345
			var entry = loaderContext.loaders[loaderContext.loaderIndex];
346
			return entry.options && typeof entry.options === "object" ? entry.options : entry.query;
347
		}
348
	});
349
	Object.defineProperty(loaderContext, "data", {
350
		enumerable: true,
351
		get: function() {
352
			return loaderContext.loaders[loaderContext.loaderIndex].data;
353
		}
354
	});
355
356
	// finish loader context
357
	if(Object.preventExtensions) {
358
		Object.preventExtensions(loaderContext);
359
	}
360
361
	var processOptions = {
362
		resourceBuffer: null,
363
		readResource: readResource
364
	};
365
	iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
366
		if(err) {
367
			return callback(err, {
368
				cacheable: requestCacheable,
369
				fileDependencies: fileDependencies,
370
				contextDependencies: contextDependencies
371
			});
372
		}
373
		callback(null, {
374
			result: result,
375
			resourceBuffer: processOptions.resourceBuffer,
376
			cacheable: requestCacheable,
377
			fileDependencies: fileDependencies,
378
			contextDependencies: contextDependencies
379
		});
380
	});
381
};