携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
关于Loader的逻辑RuleSet.js或者是NormalModule.js中,如果不太好找,可以在idea中直接全局搜索rule.test,如下:
调用过程如下 webpack-module-rules-loader :
在最后一个环节RuleSet下:
"use strict";
module.exports = class RuleSet {
constructor(rules) {
//askcomputer:创建一个干净的空对象,使用这种方法创建的对象,它不继承Object基类的原型链上的属性
this.references = Object.create(null);
this.rules = RuleSet.normalizeRules(rules, this.references, "ref-");
}
static normalizeRules(rules, refs, ident) {
//askcomputer:判断rules规则是否是个数组
if(Array.isArray(rules)) {
//askcomputer:根据当前数组,数组中的每个元素执行响应的函数后,返回一个新的数组
return rules.map((rule, idx) => {
return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`);
});
} else if(rules) {
return [RuleSet.normalizeRule(rules, refs, ident)];
} else {
return [];
}
}
//askcomputer:规范规则
static normalizeRule(rule, refs, ident) {
//如果rule是字符串,则直接返回 {use: [{loader: rule}]
if(typeof rule === "string")
return {
use: [{
loader: rule
}]
};
//askcomputer:如果rule是null,则直接抛出异常
if(!rule)
throw new Error("Unexcepted null when object was expected as rule");
//askcomputer:如果不是object,则直接抛出异常
if(typeof rule !== "object")
throw new Error("Unexcepted " + typeof rule + " when object was expected as rule (" + rule + ")");
const newRule = {};
let useSource;
let resourceSource;
let condition;
//askcomputer:这三个规则才是核心的问题,如果三个条件有一个为true,则执行 checkResourceSource
if(rule.test || rule.include || rule.exclude) {
//askcomputer:第一次加载这个规则的时候得到的结果是 resourceSource="test + include + exclude"
checkResourceSource("test + include + exclude");
condition = {
test: rule.test,
include: rule.include,
exclude: rule.exclude
};
try {
//askcomputer:规范化条件
newRule.resource = RuleSet.normalizeCondition(condition);
} catch(error) {
throw new Error(RuleSet.buildErrorMessage(condition, error));
}
}
if(rule.resource) {
checkResourceSource("resource");
try {
newRule.resource = RuleSet.normalizeCondition(rule.resource);
} catch(error) {
throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
}
}
if(rule.resourceQuery) {
try {
newRule.resourceQuery = RuleSet.normalizeCondition(rule.resourceQuery);
} catch(error) {
throw new Error(RuleSet.buildErrorMessage(rule.resourceQuery, error));
}
}
if(rule.compiler) {
try {
newRule.compiler = RuleSet.normalizeCondition(rule.compiler);
} catch(error) {
throw new Error(RuleSet.buildErrorMessage(rule.compiler, error));
}
}
if(rule.issuer) {
try {
newRule.issuer = RuleSet.normalizeCondition(rule.issuer);
} catch(error) {
throw new Error(RuleSet.buildErrorMessage(rule.issuer, error));
}
}
if(rule.loader && rule.loaders)
throw new Error(RuleSet.buildErrorMessage(rule, new Error("Provided loader and loaders for rule (use only one of them)")));
//askcomputer:loader部分--如果 loader 是 string,且 rule.options 和 rule.query 都不为空的情况下
const loader = rule.loaders || rule.loader;
if(typeof loader === "string" && !rule.options && !rule.query) {
checkUseSource("loader");
newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
} else if(typeof loader === "string" && (rule.options || rule.query)) {
checkUseSource("loader + options/query");
newRule.use = RuleSet.normalizeUse({
loader: loader,
options: rule.options,
query: rule.query
}, ident);
} else if(loader && (rule.options || rule.query)) {
throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query cannot be used with loaders (use options for each array item)")));
} else if(loader) {
checkUseSource("loaders");
newRule.use = RuleSet.normalizeUse(loader, ident);
} else if(rule.options || rule.query) {
throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));
}
//askcomputer:use
if(rule.use) {
checkUseSource("use");
newRule.use = RuleSet.normalizeUse(rule.use, ident);
}
//askcomputer:rules
if(rule.rules)
newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
//askcomputer:oneOf
if(rule.oneOf)
newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);
const keys = Object.keys(rule).filter((key) => {
return ["resource", "resourceQuery", "compiler", "test", "include", "exclude", "issuer", "loader", "options", "query", "loaders", "use", "rules", "oneOf"].indexOf(key) < 0;
});
keys.forEach((key) => {
newRule[key] = rule[key];
});
function checkUseSource(newSource) {
if(useSource && useSource !== newSource)
throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one result source (provided " + newSource + " and " + useSource + ")")));
useSource = newSource;
}
function checkResourceSource(newSource) {
if(resourceSource && resourceSource !== newSource)
throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")")));
resourceSource = newSource;
}
if(Array.isArray(newRule.use)) {
newRule.use.forEach((item) => {
if(item.ident) {
refs[item.ident] = item.options;
}
});
}
return newRule;
}
static buildErrorMessage(condition, error) {
const conditionAsText = JSON.stringify(condition, (key, value) => {
return value === undefined ? "undefined" : value;
}, 2);
return error.message + " in " + conditionAsText;
}
//askcomputer:给 use的属性,新增了一个 ident 属性,并且规范了返回的几个属性,分别为 ident loader options
static normalizeUse(use, ident) {
if(Array.isArray(use)) {
return use
.map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))
.reduce((arr, items) => arr.concat(items), []);
}
return [RuleSet.normalizeUseItem(use, ident)];
}
...
//askcomputer:规范化条件
static normalizeCondition(condition) {
//如果condition为空或者undefined,将会抛出异常
if(!condition)
throw new Error("Expected condition but got falsy value");
if(typeof condition === "string") {
return str => str.indexOf(condition) === 0;
}
//askcomputer:如果condition是函数,则直接返回
if(typeof condition === "function") {
return condition;
}
//askcomputer:如果condition是正则表达式,这个步骤可能稍微难理解一点。主要问题点应该是在 .test 和 .bind ,test用于判断在字符串中是否存在正则表达式对应的内容,如果有则返回true,如果没有返回false。而bind的目的是什么呢?对于给定的函数,创建一个与原始函数具有相同主体的绑定函数。绑定函数的this对象与指定对象相关联,并具有指定的初始参数。
if(condition instanceof RegExp) {
return condition.test.bind(condition); //askcomputer:这儿返回的是一个Function
}
//askcomputer:如果condition是数组
if(Array.isArray(condition)) {
const items = condition.map(c => RuleSet.normalizeCondition(c));
return orMatcher(items);
}
//askcomputer:如果condition不是对象
if(typeof condition !== "object")
throw Error("Unexcepted " + typeof condition + " when condition was expected (" + condition + ")");
//askcomputer:上述判断全是对于condition实参的验证内容
const matchers = [];
//askcomputer:遍历对象中的属性
Object.keys(condition).forEach(key => {
const value = condition[key];
switch(key) {
case "or":
case "include":
case "test":
if(value)
matchers.push(RuleSet.normalizeCondition(value));
break;
case "and":
if(value) {
const items = value.map(c => RuleSet.normalizeCondition(c));
matchers.push(andMatcher(items));
}
break;
case "not":
case "exclude":
if(value) {
const matcher = RuleSet.normalizeCondition(value);
matchers.push(notMatcher(matcher));
}
break;
default:
throw new Error("Unexcepted property " + key + " in condition");
}
});
if(matchers.length === 0)
throw new Error("Excepted condition but got " + condition);
if(matchers.length === 1)
return matchers[0];
return andMatcher(matchers);
}
exec(data) {
const result = [];
this._run(data, {
rules: this.rules
}, result);
return result;
}
_run(data, rule, result) {
...
}
...
};
...
在上述代码块的第81行,得到的结果如下图:
其中关于resource的部分,是会让人难以理解的地方,如下位置:
这一些列返回的不是具体的值,而是Function。它在NormalModuleFactory.js执行的情况下会调用上图中定义的Function,如下:
到目前为止,基本上全是判断和规则解析的过程,但是最终的目的应该是 如何将模块编译并且呈现渲染在浏览器中的,如何使用这些Loader进行转换的?否则这个流程就没有形成闭环。这一点很重要。
下一篇继续深入Loader的build过程。
小TIPS:在加载和编译过程会在终端输出模块编译的进度,它的逻辑在ProgressPlugin.js中,如下: