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);//返回trueresource = "index.ts";
res = filterFn(resource);//返回false
console.log(res);