绝大多数人都在使用 webpack 作为构建工具。那么 loader
作为处理各种资源的工具,大家肯定也不会陌生。很多人没写过 loader,但是都对 loader 的具体怎么写,怎样执行的一无所知。那么本文就对 3.0.0
版本做一个全方位的揭秘。
loader
所谓 loader 只是一个导出为函数的 JavaScript 模块。它接收上一个 loader 产生的结果或者资源文件(resource file)作为入参。也可以用多个 loader 函数组成 loader chain。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string)。具体的用法,可以看 Loader 官网的描述。接下来我们从源码的角度去分析,为什么可以这样做,为什么可以实现同异步钩子,内部到底是怎么实现的。那么这就是 loader-runner 的作用所在。
入口
loader-runner 是一个独立出去的 npm 包,它的入口在 lib/LoaderRunner.js
。
exports.runLoaders = function runLoaders(options, callback) {
// read options
var resource = options.resource || ""; // loaders 处理的资源
var loaders = options.loaders || []; // loaders 配置
var loaderContext = options.context || {}; // 所有 loaders 共享的数据
var readResource = options.readResource || readFile; // 文件输入系统
//
var splittedResource = resource && splitQuery(resource);
var resourcePath = splittedResource ? splittedResource[0] : undefined; // 资源路径
var resourceQuery = splittedResource ? splittedResource[1] : undefined; // 资源的 query
var contextDirectory = resourcePath ? dirname(resourcePath) : null; // 资源的目录
// execution state
var requestCacheable = true; // 缓存的标识位
var fileDependencies = []; // 文件依赖的缓存
var contextDependencies = []; // 目录依赖的缓存
// prepare loader objects
loaders = loaders.map(createLoaderObject); // 处理 loaders 的若干属性
// loaderContext 是在所有 loaders 处理资源时候共享的一份数据
// loaderIndex 是一个指针,它控制了所有 loaders 的 pitch 与 normal 函数的执行
loaderContext.context = contextDirectory;
loaderContext.loaderIndex = 0;
loaderContext.loaders = loaders;
loaderContext.resourcePath = resourcePath;
loaderContext.resourceQuery = resourceQuery;
loaderContext.async = null; // 为了实现异步 loader 的闭包函数
loaderContext.callback = null; // 为了实现同步或者异步 loader 的闭包函数
loaderContext.cacheable = function cacheable(flag) {
if(flag === false) {
requestCacheable = false;
}
};
loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
fileDependencies.push(file);
};
loaderContext.addContextDependency = function addContextDependency(context) {
contextDependencies.push(context);
};
loaderContext.getDependencies = function getDependencies() {
return fileDependencies.slice();
};
loaderContext.getContextDependencies = function getContextDependencies() {
return contextDependencies.slice();
};
// 清除所有缓存
loaderContext.clearDependencies = function clearDependencies() {
fileDependencies.length = 0;
contextDependencies.length = 0;
requestCacheable = true;
};
// 这些 getter/setter 都是为了在 loader 函数里面通过 this 求值能动态得到对应的值
Object.defineProperty(loaderContext, "resource", {
enumerable: true,
get: function() {
if(loaderContext.resourcePath === undefined)
return undefined;
return loaderContext.resourcePath + loaderContext.resourceQuery;
},
set: function(value) {
var splittedResource = value && splitQuery(value);
loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined;
loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined;
}
});
Object.defineProperty(loaderContext, "request", {
enumerable: true,
get: function() {
return loaderContext.loaders.map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "remainingRequest", {
enumerable: true,
get: function() {
if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource)
return "";
return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) {
return o.request;
}).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(