• webpack源码分析——loader-runner库之runLoaders函数


    一、 runLoaders 功能

    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
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    三、 源码解析

    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 = [];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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] || ""
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    初始化loader配置

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    初始化loaderContext对象

    	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
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    调用iteratePitchingLoaders函数

    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
    		});
    	});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    编写中间件以用于 Express 应用程序
    附加进程 到远程服务器中Docker容器内 调试
    每日一题——将一个正整数分解质因数
    数据结构-树(Tree)
    负载均衡与高可用
    【马蹄集】—— 数论专题
    46. 全排列
    黑洞路由的几种应用场景
    RobotFramework框架+Selenium实现UI自动化测试(十六)
    【数据结构阶级】链表面试题(万字详解带你手撕链表)
  • 原文地址:https://blog.csdn.net/qq_42683219/article/details/138134793