上篇文章给大家介绍了
大家可以点击查看。
Tips:写到这里,需要对当初的规则进行修改。在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}
来测试,基本上测试参数均取自于vue脚手架(太复杂的删掉)。
下面两节的主要流程图如下:
在进入compile方法后,迎面而来的就是这么一行代码:
简单看一下方法,源码如下:
{
const params = {
normalModuleFactory: this.createNormalModuleFactory(),contextModuleFactory: this.createContextModuleFactory(),compilationDependencies: []
};
return params;
}
// 工厂
方法1
Compiler.prototype.createNormalModuleFactory = () => {
const normalModuleFactory = new NormalModuleFactory(this.options.context,this.resolvers,this.options.module || {});
this.applyPlugins("normal-module-factory",normalModuleFactory);
return normalModuleFactory;
}
// 工厂
方法2
Compiler.prototype.createContextModuleFactory = () => {
const contextModuleFactory = new ContextModuleFactory(this.resolvers,this.inputFileSystem);
this.applyPlugins("context-module-factory",contextModuleFactory);
return contextModuleFactory;
}
该方法生成了一个对象参数,而对象中的值又分别调用了各自的工厂方法。
按顺序,首先来看NormalModuleFactory工厂方法,首先是构造函数:
{...}
// options => options.module
constructor(context,resolvers,options) {
super();
// 该参数在WebpackOptionsApply
方法中被赋值
this.resolvers = resolvers;
// 处理module.rules或module.loaders
// 两者意义一样
this.ruleSet = new RuleSet(options.rules || options.loaders);
// 谁会去设置这玩意???默认参数设置那会被置为true
// 这里会
生成一个返回布尔值的
函数 可以简单理解为!!
this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null,options.unsafeCache);
this.context = context || "";
// 解析缓存
this.parserCache = {};
// 这两个
方法超级长
this.plugin("factory",() => (result,callback) => { /**/ });
this.plugin("resolver",() => (data,callback) => { /**/ })
}
}
也是一个继承了Tapable框架的类,构造函数除去两个事件流注入不看,剩余的内容只有RuleSet那一块,也是本节的主要内容。
从参数可以看出,这里是对module.rules处理的地方,本节中测试配置添加了rules方便测试:
path.join(__dirname,'..',path)
module.exports = {
entry: './input.js',output: {
filename: 'output.js'
},module: {
rules: [
{
test: /\.js$/,loader: 'babel-loader',include: [resolve('src'),resolve('test')]
},{
test: /\.css/,loader: '
css-loader!style-loader'
}
]
}
};
只针对配置中有或者常用且官网有解释的参数进行。
RuleSet
构造函数如下:
生成了一个纯净对象并调用了本地静态方法进行格式化:
传进来的配置数组
// refs => 纯净对象
// ident => 'ref-'
static normalizeRules(rules,refs,ident) {
// 分数组与非数组
if (Array.isArray(rules)) {
return rules.map((rule,idx) => {
return RuleSet.normalizeRule(rule,`${ident}-${idx}`);
});
} else if (rules) {
return [RuleSet.normalizeRule(rules,ident)];
}
// 未配置rules直接返回空数组
else {
return [];
}
}
}
这里也区分了数组参数与非数组参数,但是有个小bug。
看代码可以很容易猜到,数组与非数组的情况理论上是这样的:
因为传非数组,会被包装成一个数组,所以这种情况属于单loader配置。
但是,这样配置是会报错的,因为过不了validateSchema的验证,测试结果如图:
这就很尴尬了,提供了非数组形式的处理方式,但是又不通过非数组的校验,所以这基本上是永远不会被执行的代码。
管他的,估计源码太大,开发者已经顾不上了。
下面正式进行格式化阶段,源码整理如下:
'ref-${index}'
static normalizeRule(rule,ident) {
// 传入字符串
// '
css-loader' => use:[{loader:'
css-loader'}]
if (typeof rule === "string")
return {
use: [{
loader: rule
}]
};
// 非法参数
if (!rule)
throw new Error("Unexcepted null when object was expected as rule");
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;
// test
if (rule.test || rule.include || rule.exclude) { /**/ }
if (rule.resource) { /**/ }
// 官网doc都懒得解释 估计是几个很弱智的参数
if (rule.resourceQuery) { /**/ }
if (rule.compiler) { /**/ }
if (rule.issuer) { /**/ }
// loader、loaders只能用一个
if (rule.loader && rule.loaders)
throw new Error(RuleSet.buildErrorMessage(rule,new Error("Provided loader and loaders for rule (use only one of them)")));
const loader = rule.loaders || rule.loader;
// 处理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)")));
}
// 处理use
if (rule.use) {
checkUseSource("use");
newRule.use = RuleSet.normalizeUse(rule.use,ident);
}
// 递归处理内部rules
if (rule.rules)
newRule.rules = RuleSet.normalizeRules(rule.rules,`${ident}-rules`);
// 不知道是啥
if (rule.oneOf)
newRule.oneOf = RuleSet.normalizeRules(rule.oneOf,`${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) { /**/ }
function checkResourceSource(newSource) { /**/ }
if (Array.isArray(newRule.use)) {
newRule.use.forEach((item) => {
if (item.ident) {
refs[item.ident] = item.options;
}
});
}
return newRule;
}
}
总体来看源码内容如下:
1、生成newRules对象保存转换后的rules
2、处理单字符串rule
3、处理test、include、exclude参数
4、处理resource、resourseQuery、compiler、issuer参数
5、处理loader、loaders、options、query参数
6、处理use参数
7、递归处理rules参数
8、处理oneOf参数
总结
以上所述是小编给大家介绍的细说webpack源码之compile流程-rules参数处理技巧(1)。编程之家 jb51.cc 收集整理的教程希望能对你有所帮助,如果觉得编程之家不错,可分享给好友!感谢支持。
原文链接:https://www.f2er.com/js/34491.html