runLoaders 函数是loader-runner库导出的两个方法之一,它用来执行loader,对指定的文件进行“转义”\
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
processResource: (loaderContext, resourcePath, callback) => { ... },
// Optional: A function to process the resource
// Must have signature function(context, path, function(err, buffer))
// By default readResource is used and the resource is added a fileDependency
readResource: fs.readFile.bind(fs)
// Optional: A function to read the resource
// Only used when 'processResource' is not provided
// Must have signature function(path, function(err, buffer))
// By default fs.readFile is used
}, function(err, result) {
// err: Error?
// result.result: Buffer | String
// The result
// only available when no error occured
// result.resourceBuffer: Buffer
// The raw resource as Buffer (useful for SourceMaps)
// only available when no error occured
// result.cacheable: Bool
// Is the result cacheable or do it require reexecution?
// result.fileDependencies: String[]
// An array of paths (existing files) on which the result depends on
// result.missingDependencies: String[]
// An array of paths (not existing files) on which the result depends on
// result.contextDependencies: String[]
// An array of paths (directories) on which the result depends on
})
runLoaders 函数内分为“读取配置和变量初始化”、“初始化loader配置”、“初始化loaderContext对象”和“调用iteratePitchingLoaders函数”四个阶段
var resource = options.resource || "";
var loaders = options.loaders || [];
var loaderContext = options.context || {};
var processResource = options.processResource || ((readResource, context, resource, callback) => {
context.addDependency(resource); // 把当前文件目录添加到 context 中
readResource(resource, callback);
}).bind(null, options.readResource || readFile); // 没有传入 processResource 就默认赋值一个。 readFile 为默认的文件读取函数 (readFile = fs.readFile.bind(fs))
var splittedResource = resource && parsePathQueryFragment(resource); // parsePathQueryFragment 函数是解析文件路径 path、query和fragment
// 通过parsePathQueryFragment 解析的文件路径进行如下赋值,如果没有直接赋值为 undefined
var resourcePath = splittedResource ? splittedResource.path : undefined;
var resourceQuery = splittedResource ? splittedResource.query : undefined;
var resourceFragment = splittedResource ? splittedResource.fragment : undefined;
var contextDirectory = resourcePath ? dirname(resourcePath) : null; // dirname 函数用于获取当前文件目录的上层目录
// execution state
var requestCacheable = true;
var fileDependencies = [];
var contextDependencies = [];
var missingDependencies = [];
parsePathQueryFragment 函数
const PATH_QUERY_FRAGMENT_REGEXP = /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
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] || ""
};
}
dirname 函数(__dirname 的变体实现)
function dirname(path) {
if(path === "/") return "/";
var i = path.lastIndexOf("/"); // 寻找 path 中最后一次出现‘/’的位置
var j = path.lastIndexOf("\\"); // 寻找 path 中最后一次出现‘\\’的位置
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);
}
loaders = loaders.map(createLoaderObject);
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
};
// 对 request 属性进行 get和set拦截
Object.defineProperty(obj, "request", {
enumerable: true,
get: function() {
return obj.path.replace(/#/g, "\0#") + obj.query.replace(/#/g, "\0#") + obj.fragment;
},
set: function(value) { // set 拦截进行判断
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; // 赋值,赋值的时候被 set 拦截
if(Object.preventExtensions) {
Object.preventExtensions(obj);// 进行该对象在拓展其他属性
}
return obj;
}
loaderContext.context = contextDirectory; // 对 loaderContext 进行赋值
loaderContext.loaderIndex = 0; // 当前处于 loader 中第一个loader位置
loaderContext.loaders = 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); // 把当前 file 推向 fileDependencies 数组中完成依赖收集
};
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; // 把三个数组长度置为0
contextDependencies.length = 0;
missingDependencies.length = 0;
requestCacheable = true;
};
Object.defineProperty(loaderContext, "resource", { // 对 resource 进行 get和set 拦截
enumerable: true,
get: function() { // 在 pitch 阶段时,通过get方法可以获取改变量处理后的值
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", { // 对 request进行 get和set 拦截
enumerable: true,
get: function() { // 在 pitch 阶段时,通过get方法可以获取改变量处理后的值
return loaderContext.loaders.map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "remainingRequest", {// 对 remainingRequest进行 get和set 拦截
enumerable: true,
get: function() { // 在 pitch 阶段时,通过get方法可以获取改变量处理后的值
if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource)
return "";
return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) {
return o.request; // 这里用到 request 就会触发 request get 方法
}).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); // 禁止拓展 loaderContext
}
var processOptions = {
resourceBuffer: null,
processResource: processResource
};
// 因为loader 执行是先执行loader的pitch阶段,所以先调用iteratePitchingLoaders,进行pitch阶段的判断。再该函数中继续递归调用iterateNormalLoaders 进行loader执行。最后出栈
iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
if(err) { // 出错时,这里的callback为用户传入的函数,并调用
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
});
});