细说webpack源码之compile流程-rules参数处理技巧(1)
上篇文章给大家介绍了
大家可以点击查看。
tips:写到这里,需要对当初的规则进行修改。在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}
来测试,基本上测试参数均取自于vue脚手架(太复杂的删掉)。
下面两节的主要流程图如下:
在进入compile方法后,迎面而来的就是这么一行代码:
const params = this.newcompilationparams();
简单看一下方法,源码如下:
compiler.prototype.newcompilationparams = () => { 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工厂方法,首先是构造函数:
class normalmodulefactory extends tapable { // context默认为命令执行路径 // resolvers => {...} // 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方便测试:
const path = require('path'); // resolve => 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
构造函数如下:
module.exports = class ruleset { constructor(rules) { // 空对象 this.references = object.create(null); // 格式化 this.rules = ruleset.normalizerules(rules, this.references, "ref-"); } }
生成了一个纯净对象并调用了本地静态方法进行格式化:
class ruleset { constructor(rules) { /**/ } // rules => 传进来的配置数组 // refs => 纯净对象 // ident => 'ref-' static normalizerules(rules, refs, ident) { // 分数组与非数组 if (array.isarray(rules)) { return rules.map((rule, idx) => { return ruleset.normalizerule(rule, refs, `${ident}-${idx}`); }); } else if (rules) { return [ruleset.normalizerule(rules, refs, ident)]; } // 未配置rules直接返回空数组 else { return []; } } }
这里也区分了数组参数与非数组参数,但是有个小bug。
看代码可以很容易猜到,数组与非数组的情况理论上是这样的:
// 数组 module.exports = { // ... module: { rules: [{ test: /\.vue$/, loader: 'vue-loader' }, /*...*/ ] } }; // 非数组 module.exports = { // ... module: { rules: { test: /\.vue$/, loader: 'vue-loader' } } };
因为传非数组,会被包装成一个数组,所以这种情况属于单loader配置。
但是,这样配置是会报错的,因为过不了validateschema的验证,测试结果如图:
这就很尴尬了,提供了非数组形式的处理方式,但是又不通过非数组的校验,所以这基本上是永远不会被执行的代码。
管他的,估计源码太大,开发者已经顾不上了。
下面正式进行格式化阶段,源码整理如下:
class ruleset { constructor(rules) { /**/ }; // ident => 'ref-${index}' static normalizerule(rule, refs, 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, refs, `${ident}-rules`); // 不知道是啥 if (rule.oneof) newrule.oneof = ruleset.normalizerules(rule.oneof, refs, `${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),希望对大家有所帮助