细说webpack源码之compile流程-rules参数处理技巧(2)
上篇文章给大家介绍了细说webpack源码之compile流程-rules参数处理技巧(1), 细说webpack源码之compile流程-入口函数run
大家可以点击查看。
第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。
test
然后处理test、include、exclude,如下:
if(rule.test||rule.include||rule.exclude){
//标记使用参数
checkResourceSource("test+include+exclude");
//没有就是undefined
condition={
test:rule.test,
include:rule.include,
exclude:rule.exclude
};
//处理常规参数
try{
newRule.resource=RuleSet.normalizeCondition(condition);
}catch(error){
thrownewError(RuleSet.buildErrorMessage(condition,error));
}
}
checkResourceSource直接看源码:
letresourceSource;
//...
functioncheckResourceSource(newSource){
//第一次直接跳到后面赋值
if(resourceSource&&resourceSource!==newSource)
thrownewError(RuleSet.buildErrorMessage(rule,newError("Rulecanonlyhaveoneresourcesource(provided"+newSource+"and"+resourceSource+")")));
resourceSource=newSource;
}
这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。
随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:
classRuleSet{
constructor(rules){/**/};
staticnormalizeCondition(condition){
//假值报错
if(!condition)thrownewError("Expectedconditionbutgotfalsyvalue");
//检测给定字符串是否以这个开头
if(typeofcondition==="string"){returnstr=>str.indexOf(condition)===0;}
//函数直接返回
if(typeofcondition==="function"){returncondition;}
//正则表达式返回一个正则的test函数
if(conditioninstanceofRegExp){returncondition.test.bind(condition);}
//数组map递归处理有一个满足返回true
if(Array.isArray(condition)){
constitems=condition.map(c=>RuleSet.normalizeCondition(c));
returnorMatcher(items);
}
if(typeofcondition!=="object")throwError("Unexcepted"+typeofcondition+"whenconditionwasexpected("+condition+")");
constmatchers=[];
//对象会对每个值进行函数包装弹入matchers中
Object.keys(condition).forEach(key=>{
constvalue=condition[key];
switch(key){
case"or":
case"include":
case"test":
if(value)
matchers.push(RuleSet.normalizeCondition(value));
break;
case"and":
if(value){
constitems=value.map(c=>RuleSet.normalizeCondition(c));
matchers.push(andMatcher(items));
}
break;
case"not":
case"exclude":
if(value){
constmatcher=RuleSet.normalizeCondition(value);
matchers.push(notMatcher(matcher));
}
break;
default:
thrownewError("Unexceptedproperty"+key+"incondition");
}
});
if(matchers.length===0)
thrownewError("Exceptedconditionbutgot"+condition);
if(matchers.length===1)
returnmatchers[0];
returnandMatcher(matchers);
}
}
这里用js的rules做案例,看这个方法的返回:
classRuleSet{
constructor(rules){/**/};
/*
Example:
{
test:/\.js$/,
loader:'babel-loader',
include:[resolve('src'),resolve('test')]
}
*/
/*
condition:
{
test:/\.js$/,
include:['d:\\workspace\\src','d:\\workspace\\test'],
exclude:undefined
}
*/
staticnormalizeCondition(condition){
//include返回类似于[(str)=>str.indexOf('d:\\workspace\\src')===0,...]的函数
if(typeofcondition==="string"){returnstr=>str.indexOf(condition)===0;}
//test参数返回了/\.js$/.test函数
if(conditioninstanceofRegExp){returncondition.test.bind(condition);}
//include为数组
if(Array.isArray(condition)){
constitems=condition.map(c=>RuleSet.normalizeCondition(c));
returnorMatcher(items);
}
constmatchers=[];
//解析出['test','include','exclude']
Object.keys(condition).forEach(key=>{
constvalue=condition[key];
switch(key){
//此value为一个数组
case"include":
case"test":
if(value)
matchers.push(RuleSet.normalizeCondition(value));
break;
//undefined跳过
case"exclude":
if(value){/**/}
break;
default:
thrownewError("Unexceptedproperty"+key+"incondition");
}
});
returnandMatcher(matchers);
}
}
这里继续看orMatcher、andMatcher函数的处理:
functionorMatcher(items){
//当一个函数被满足条件时返回true
returnfunction(str){
for(leti=0;i
从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。
resource
下面是对resource参数的处理,源码如下:
if(rule.resource){
//如果前面检测到了test||include||exclude参数这里会报错
checkResourceSource("resource");
try{
newRule.resource=RuleSet.normalizeCondition(rule.resource);
}catch(error){
thrownewError(RuleSet.buildErrorMessage(rule.resource,error));
}
}
可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:
/*
方式1:
rules:[
{
test:/\.js$/,
loader:'babel-loader',
include:[resolve('src'),resolve('test')]
}
]
*/
/*
方式2:
rules:[
{
resource:{
test:/\.js$/,
include:[resolve('src'),resolve('test')]
exclude:undefined
}
}
]
*/
接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。
loader
下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:
constloader=rule.loaders||rule.loader;
//单loader情况
if(typeofloader==="string"&&!rule.options&&!rule.query){
checkUseSource("loader");
newRule.use=RuleSet.normalizeUse(loader.split("!"),ident);
}
//loader配合options或query出现
elseif(typeofloader==="string"&&(rule.options||rule.query)){
checkUseSource("loader+options/query");
newRule.use=RuleSet.normalizeUse({
loader:loader,
options:rule.options,
query:rule.query
},ident);
}
//options与query同时出现报错
elseif(loader&&(rule.options||rule.query)){
thrownewError(RuleSet.buildErrorMessage(rule,newError("options/querycannotbeusedwithloaders(useoptionsforeacharrayitem)")));
}
/*
处理这种愚蠢用法时:
{
test:/\.css$/,
loader:[{loader:'less-loader'},{loader:'css-loader'}]
}
*/
elseif(loader){
checkUseSource("loaders");
newRule.use=RuleSet.normalizeUse(loader,ident);
}
//单独出现options或者query报错
elseif(rule.options||rule.query){
thrownewError(RuleSet.buildErrorMessage(rule,newError("options/queryprovidedwithoutloader(useloader+options)")));
}
之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。
首先normalizeUse方法如下:
staticnormalizeUse(use,ident){
//单loader字符串
if(Array.isArray(use)){
returnuse
//如果是单loader情况这里会返回[[loader1...],[loader2...]]
.map((item,idx)=>RuleSet.normalizeUse(item,`${ident}-${idx}`))
//扁平化后变成[loader1,loader2]
.reduce((arr,items)=>arr.concat(items),[]);
}
//对象或字符串
return[RuleSet.normalizeUseItem(use,ident)];
};
先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:
loader&&(options||query)
//indet=>'ref-'
staticnormalizeUseItem(item,ident){
if(typeofitem==="function")
returnitem;
if(typeofitem==="string"){
returnRuleSet.normalizeUseItemString(item);
}
constnewItem={};
if(item.options&&item.query)thrownewError("Providedoptionsandqueryinuse");
if(!item.loader)thrownewError("Noloaderspecified");
newItem.options=item.options||item.query;
//防止options:null的情况
if(typeofnewItem.options==="object"&&newItem.options){
//这里只是为了处理ident参数
if(newItem.options.ident)
newItem.ident=newItem.options.ident;
else
newItem.ident=ident;
}
//取出loader参数
constkeys=Object.keys(item).filter(function(key){
return["options","query"].indexOf(key)<0;
});
keys.forEach(function(key){
newItem[key]=item[key];
});
/*
newItem=
{
loader:'原字符串',
ident:'ref-',(或自定义)
options:{...}
}
*/
returnnewItem;
}
比起对test的处理,这里就简单的多,简述如下:
1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-
2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???
3、返回newItem对象
总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。
单loader
第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:
/*
Example:
{
test:/\.css$/,
loader:'css-loader?{opt:1}!style-loader'
}
返回:
[{loader:'css-loader',options:{opt:1}},
{loader:'style-loader'}]
*/
staticnormalizeUseItemString(useItemString){
//根据'?'切割获取loader的参数
constidx=useItemString.indexOf("?");
if(idx>=0){
return{
loader:useItemString.substr(0,idx),
//后面的作为options返回
options:useItemString.substr(idx+1)
};
}
return{
loader:useItemString
};
}
这种就是串行调用loader的处理方式,代码很简单。
Tips
这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!
这两种方式只是不同的使用方法,最后的结果都是一样的。
use
下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:
if(rule.use){
checkUseSource("use");
newRule.use=RuleSet.normalizeUse(rule.use,ident);
}
如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:
/*
{
test:/\.css$/,
user:['css-loader','style-loader]
}
*/
而对应options或query,需要这样写:
/*
{
test:/\.css$/,
user:{
loader:'css-loader',
options:'1'
}
}
*/
因为基本上不用了,所以这里简单看一下。
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`);
这两个用得少,也没啥难理解的。
下一步是过滤出没有处理的参数,添加到newRuls上:
constkeys=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];
});
基本上用到都是test、loader、options,暂时不知道有啥额外参数。
ident
//防止rules:[]的情况
if(Array.isArray(newRule.use)){
newRule.use.forEach((item)=>{
//ident来源于options/query的ident参数
if(item.ident){
refs[item.ident]=item.options;
}
});
}
最后这个地方是终于用到了传进来的纯净对象refs。
如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。
至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。
总结
以上所述是小编给大家介绍的细说webpack源码之compile流程-rules参数处理技巧(2),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!