runLoaders({
resource: "/abs/path/to/file.txt?query",
// String: Absolute path to the resource (optionally including query string)
loaders: ["/abs/path/to/loader.js?query"],
// String[]: Absolute paths to the loaders (optionally including query string)
// {loader, options}[]: Absolute paths to the loaders with options object
context: { minimize: true },
// Additional loader context which is used as base context
readResource: fs.readFile.bind(fs)
// A function to read the resource
// Must have signature function(path, function(err, buffer))
}, function(err, result) {
// err: Error?
// result.result: Buffer | String
// The result
// result.resourceBuffer: Buffer
// The raw resource as Buffer (useful for SourceMaps)
// result.cacheable: Bool
// Is the result cacheable or do it require reexecution?
// result.fileDependencies: String[]
// An array of paths (files) on which the result depends on
// result.contextDependencies: String[]
// An array of paths (directories) on which the result depends on
})
function splitQuery(req) {
var i = req.indexOf("?");
if(i < 0) return [req, ""];
return [req.substr(0, i), req.substr(i)];
}
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);
}
//loader开始执行阶段
function processResource(options, loaderContext, callback) {
// 将loader索引设置为最后一个loader
loaderContext.loaderIndex = loaderContext.loaders.length - 1;
var resourcePath = loaderContext.resourcePath
if(resourcePath) {
//添加文件依赖
loaderContext.addDependency(resourcePath);
//读取文件
options.readResource(resourcePath, function(err, buffer) {
if(err) return callback(err);
//读取完成后放入options
options.resourceBuffer = buffer;
iterateNormalLoaders(options, loaderContext, [buffer], callback);
});
} else {
iterateNormalLoaders(options, loaderContext, [null], callback);
}
}
//从右往左递归执行loader
function iterateNormalLoaders(options, loaderContext, args, callback) {
//结束条件,loader读取完毕
if(loaderContext.loaderIndex < 0)
return callback(null, args);
var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
//迭代
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);
}
//转换buffer数据。如果当前loader设置了raw属性
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);
});
}
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");
}
exports.getContext = function getContext(resource) {
var splitted = splitQuery(resource);
return dirname(splitted[0]);
};
function createLoaderObject(loader){
//初始化loader配置
var obj = {
path: null,
query: 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 + obj.query;
},
set: function(value) {
if(typeof value === "string") {
var splittedRequest = splitQuery(value);
obj.path = splittedRequest[0];
obj.query = splittedRequest[1];
obj.options = undefined;
obj.ident = undefined;
} else {
if(!value.loader)
throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")");
obj.path = value.loader;
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;
}
exports.runLoaders = function runLoaders(options, callback) {
//options = {resource...,fn...}
// 读取options
var resource = options.resource || "";
var loaders = options.loaders || [];
var loaderContext = options.context || {};
var readResource = options.readResource || readFile;
//
var splittedResource = resource && splitQuery(resource);
var resourcePath = splittedResource ? splittedResource[0] : undefined;
var resourceQuery = splittedResource ? splittedResource[1] : undefined;
var contextDirectory = resourcePath ? dirname(resourcePath) : null;
//执行状态
var requestCacheable = true;
var fileDependencies = [];
var contextDependencies = [];