这篇文章主要介绍了webpack源码之compile流程-rules参数处理技巧的相关知识,需要的朋友参考下吧
上篇文章给大家介绍了细说webpack源码之compile流程-rules参数处理技巧(1), 细说webpack源码之compile流程-入口函数run
大家可以点击查看。
第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。
test
然后处理test、include、exclude,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if (rule.test || rule. include || rule.exclude) {
checkResourceSource( "test + include + exclude" );
condition = {
test: rule.test,
include : rule. include ,
exclude: rule.exclude
};
try {
newRule.resource = RuleSet.normalizeCondition(condition);
} catch (error) {
throw new Error(RuleSet.buildErrorMessage(condition, error));
}
}
|
Copy after login
checkResourceSource直接看源码:
1 2 3 4 5 6 7 8 | let resourceSource;
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;
}
|
Copy after login
这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。
随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:
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 | class RuleSet {
constructor(rules) { };
static normalizeCondition(condition) {
if (!condition) throw new Error( "Expected condition but got falsy value" );
if (typeof condition === "string" ) { return str => str.indexOf(condition) === 0; }
if (typeof condition === "function" ) { return condition; }
if (condition instanceof RegExp) { return condition.test.bind(condition); }
if (Array.isArray(condition)) {
const items = condition.map(c => RuleSet.normalizeCondition(c));
return orMatcher(items);
}
if (typeof condition !== "object" ) throw Error( "Unexcepted " + typeof condition + " when condition was expected (" + condition + ")" );
const matchers = [];
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);
}
}
|
Copy after login
这里用js的rules做案例,看这个方法的返回:
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 | class RuleSet {
constructor(rules) { };
static normalizeCondition(condition) {
if (typeof condition === "string" ) { return str => str.indexOf(condition) === 0; }
if (condition instanceof RegExp) { return condition.test.bind(condition); }
if (Array.isArray(condition)) {
const items = condition.map(c => RuleSet.normalizeCondition(c));
return orMatcher(items);
}
const matchers = [];
Object.keys(condition).forEach(key => {
const value = condition[key];
switch (key) {
case "include" :
case "test" :
if (value)
matchers.push(RuleSet.normalizeCondition(value));
break ;
case "exclude" :
if (value) { }
break ;
default :
throw new Error( "Unexcepted property " + key + " in condition" );
}
});
return andMatcher(matchers);
}
}
|
Copy after login
这里继续看orMatcher、andMatcher函数的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function orMatcher(items) {
return function (str) {
for (let i = 0; i < items.length; i++) {
if (items[i](str))
return true;
}
return false;
};
}
function andMatcher(items) {
return function (str) {
for (let i = 0; i < items.length; i++) {
if (!items[i](str))
return false;
}
return true;
};
}
|
Copy after login
从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。
resource
下面是对resource参数的处理,源码如下:
1 2 3 4 5 6 7 8 9 | if (rule.resource) {
checkResourceSource( "resource" );
try {
newRule.resource = RuleSet.normalizeCondition(rule.resource);
} catch (error) {
throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
}
}
|
Copy after login
可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Copy after login
接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。
loader
下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:
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 | 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)" )));
}
|
Copy after login
之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。
首先normalizeUse方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 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)];
};
|
Copy after login
先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:
loader && (options || query)
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 | static normalizeUseItem(item, ident) {
if (typeof item === "function" )
return item;
if (typeof item === "string" ) {
return RuleSet.normalizeUseItemString(item);
}
const newItem = {};
if (item.options && item.query) throw new Error( "Provided options and query in use" );
if (!item.loader) throw new Error( "No loader specified" );
newItem.options = item.options || item.query;
if (typeof newItem.options === "object" && newItem.options) {
if (newItem.options.ident)
newItem.ident = newItem.options.ident;
else
newItem.ident = ident;
}
const keys = Object.keys(item).filter( function (key) {
return [ "options" , "query" ].indexOf(key) < 0;
});
keys.forEach( function (key) {
newItem[key] = item[key];
});
return newItem;
}
|
Copy after login
比起对test的处理,这里就简单的多,简述如下:
1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-
2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???
3、返回newItem对象
总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。
单loader
第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:
1 2 3 4 5 6 | /*
Example:
{
test: /\.css$/,
loader: 'css-loader?{opt:1}!style-loader'
}
|
Copy after login
返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [{loader:'css-loader',options:{opt:1}},
{loader:'style-loader'}]
*/
static normalizeUseItemString(useItemString) {
const idx = useItemString.indexOf( "?" );
if (idx >= 0) {
return {
loader: useItemString. substr (0, idx),
options: useItemString. substr (idx + 1)
};
}
return {
loader: useItemString
};
}
|
Copy after login
这种就是串行调用loader的处理方式,代码很简单。
Tips
这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!
这两种方式只是不同的使用方法,最后的结果都是一样的。
use
下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:
1 2 3 4 | if (rule. use ) {
checkUseSource( "use" );
newRule. use = RuleSet.normalizeUse(rule. use , ident);
}
|
Copy after login
如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:
而对应options或query,需要这样写:
因为基本上不用了,所以这里简单看一下。
1 2 3 4 5 | rules、oneOf
if (rule.rules)
newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
if (rule.oneOf)
newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);
|
Copy after login
这两个用得少,也没啥难理解的。
下一步是过滤出没有处理的参数,添加到newRuls上:
1 2 3 4 5 6 | 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];
});
|
Copy after login
基本上用到都是test、loader、options,暂时不知道有啥额外参数。
ident
1 2 3 4 5 6 7 8 9 | if (Array.isArray(newRule. use )) {
newRule. use .forEach((item) => {
if (item.ident) {
refs[item.ident] = item.options;
}
});
}
|
Copy after login
最后这个地方是终于用到了传进来的纯净对象refs。
如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。
至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
在nodejs中如何实现websocket通信功能
在js中如何实现绑定点击事件(详细教程)
通过Vue如何实现SSR(详细教程)
The above is the detailed content of About rules parameter processing in webpack. For more information, please follow other related articles on the PHP Chinese website!