webpack中, module.rules 的默认值是一个空数组。
这个数组中的每个元素对应一个规则,这个规则可以是一个字符串,也可以是一个对象。实际应用中,往往会将其配置成一个对象。就像下面这样:
module.exports = {
module:{
rules:[
{
test:/\.(js|jsx)$/,
include:function(content){
return /src/.test(content);
},
exclude:[/node_modules/],
use:{
loader:'babel-loader',
options:{
cacheDirectory:true
}
}
}
]
}
}
假设文件路径是contentPath,这条规则的意思是:
/\.(js|jsx)$/.test(contentPath) && /src/.test(contentPath) && !(/node_modules/.test(contentPath))
返回true时,才会将该文件的代码内容交给babel-loader处理。
呃,那cacheDirectory
是用来干啥的??
我们知道,babel-loader是用来将ES6转译成ES5的,但说到底,它也是个函数fn
,所以转译的过程相当于执行fn.apply(context,args)
。
其中,args
就是我们从文件中读取的代码内容,context
是一个上下文对象,我们在这里配置的cacheDirectory
最终会成为是context
对象的的一个属性,即context.cacheDirectory
,且值为true。这样,babel-loader会将转译后的结果缓存起来,下次转译时就直接从缓存中取了。
接下来我们主要看下webpack是如何将一条规则中的test
、include
和exclude
转换成一个过滤函数,这个过滤函数又是怎么个“一夫当关万夫莫开”的。
webpack使用RuleSet
类解析module.rules,这个类有个静态方法static normalizeCondition(condition)
,这个方法的实现如下:
const andMatcher = items => {
return str => items.every(item => item(str));
}
const orMatcher = items => {
return str => items.some(item => item(str));
}
const notMatcher = matcher => {
return str=>!matcher(str);
}
function normalizeCondition(condition){
if(typeof condition === "string"){
return str=>condition.indexOf(str)===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 => normalizeCondition(c));
return orMatcher(items);
}
let matchers = [];
let value;
Object.keys(condition).forEach(key => {
let value = condition[key];
switch(key){
case "test":
case "include":
if(value){
matchers.push(normalizeCondition(value));
}
break;
case "exclude":
if(value){
const item = normalizeCondition(value);
matchers.push(notMatcher(item));
}
break;
default:throw new Error("Unexpected property " + key + " in condition" );
}
})
if(matchers.length===0){
throw new Error("Expected condition but got " + condition);
}
if(matchers.length===1){
return matchers[0];
}
return andMatcher(matchers);
}
源码逻辑很清晰,可以看到:
一条规则中的test
、include
和exclude
,可以配置成一个字符串、一个函数、一个正则表达式或者一个数组。如果是数组的话,会进行递归
,数组元素之间是或
的关系(orMatcher
)。
test:String | Function | RegExp | Array,
include:String | Function | RegExp | Array,
exclude:String | Function | RegExp | Array
test
、include
和exclude
三者之间是与
的关系(andMatcher
)。
下面是部分测试代码:
const _module = Object.create(null);
_module.exports = {
module:{
rules:[
{
test:/\.(js|jsx)$/,
include:function(content){
return /src/.test(content);
},
exclude:[/node_modules/],
use:{
loader:'babel-loader',
options:{
cacheDirectory:true
}
}
}
]
}
}
const rule = _module.exports.module.rules[0];
const condition = {
test:rule.test,
include:rule.include,
exclude:rule.exclude
}
const filterFn = normalizeCondition(condition);
let resource = "src/index.js";
let res = filterFn(resource);
console.log(res);//返回true
resource = "index.ts";
res = filterFn(resource);//返回false
console.log(res);