细说webpack源码之compile流程-rules参数处理技巧(1)


Posted in Javascript onDecember 26, 2017

上篇文章给大家介绍了细说webpack源码之compile流程-rules参数处理技巧(2), 细说webpack源码之compile流程-入口函数run

大家可以点击查看。

Tips:写到这里,需要对当初的规则进行修改。在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}来测试,基本上测试参数均取自于vue脚手架(太复杂的删掉)。

下面两节的主要流程图如下:

细说webpack源码之compile流程-rules参数处理技巧(1)

在进入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的验证,测试结果如图:

细说webpack源码之compile流程-rules参数处理技巧(1)

细说webpack源码之compile流程-rules参数处理技巧(1)

这就很尴尬了,提供了非数组形式的处理方式,但是又不通过非数组的校验,所以这基本上是永远不会被执行的代码。

管他的,估计源码太大,开发者已经顾不上了。

下面正式进行格式化阶段,源码整理如下:

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),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
让人印象深刻的10个jQuery手风琴效果应用
May 08 Javascript
Javascript字符串拼接小技巧(推荐)
Jun 02 Javascript
Bootstrap 最常用的JS插件系列总结(图片轮播、标签切换等)
Jul 14 Javascript
js前端面试题及答案整理(一)
Aug 26 Javascript
微信小程序 页面传值详解
Mar 10 Javascript
JS实现线性表的链式表示方法示例【经典数据结构】
Apr 11 Javascript
Vue实现内部组件轮播切换效果的示例代码
Apr 07 Javascript
vue单页面在微信下只能分享落地页的解决方案
Apr 15 Javascript
vue中使用带隐藏文本信息的图片、图片水印的方法
Apr 24 Javascript
如何使用jQuery操作Cookies方法解析
Sep 08 jQuery
JS中一些高效的魔法运算符总结
May 06 Javascript
用React Native制作一个简单的游戏引擎
May 27 Javascript
Bootstrap 模态框多次显示后台提交多次BUG的解决方法
Dec 26 #Javascript
细说webpack源码之compile流程-入口函数run
Dec 26 #Javascript
Vue 进入/离开动画效果
Dec 26 #Javascript
node.js中路由,中间件,ge请求和post请求的参数详解
Dec 26 #Javascript
Angular实现可删除并计算总金额的购物车功能示例
Dec 26 #Javascript
浅谈React深度编程之受控组件与非受控组件
Dec 26 #Javascript
使用vue实现简单键盘的示例(支持移动端和pc端)
Dec 25 #Javascript
You might like
Uchome1.2 1.5 代码学习 common.php
2009/04/24 PHP
php写的AES加密解密类分享
2014/06/20 PHP
php5.3提示Function ereg() is deprecated Error问题解决方法
2014/11/12 PHP
PHP魔术方法__GET、__SET使用实例
2014/11/25 PHP
php判断表是否存在的方法
2015/06/18 PHP
ThinkPHP路由详解
2015/07/27 PHP
xml分页+ajax请求数据源+dom取结果实例代码
2008/10/31 Javascript
浏览器缩放检测的js代码
2014/09/28 Javascript
JavaScript中的this引用(推荐)
2016/08/05 Javascript
vue双向绑定简要分析
2017/03/23 Javascript
node.js学习之断言assert的使用示例
2017/09/28 Javascript
移动前端图片压缩上传的实例
2017/12/06 Javascript
关于express与koa的使用对比详解
2018/01/25 Javascript
微信小程序上传文件到阿里OSS教程
2019/05/20 Javascript
Vue 使用计时器实现跑马灯效果的实例代码
2019/07/11 Javascript
详解Python文本操作相关模块
2017/06/22 Python
Anaconda 离线安装 python 包的操作方法
2018/06/11 Python
Django实现支付宝付款和微信支付的示例代码
2018/07/25 Python
在Python 中同一个类两个函数间变量的调用方法
2019/01/31 Python
python利用跳板机ssh远程连接redis的方法
2019/02/19 Python
pandas 选取行和列数据的方法详解
2019/08/08 Python
python 实现return返回多个值
2019/11/19 Python
CSS3 media queries + jQuery实现响应式导航
2016/09/30 HTML / CSS
Algenist奥杰尼官网:微藻抗衰老护肤品牌
2017/07/15 全球购物
Hotels.com香港酒店网:你的自由行酒店订房专家
2018/01/22 全球购物
你所知道的集合类都有哪些?主要方法?
2012/12/31 面试题
大四学生找工作的自荐信
2014/03/27 职场文书
学校标语大全
2014/06/19 职场文书
个人主要事迹材料
2014/08/26 职场文书
党员对照检查材料思想汇报(党的群众路线)
2014/09/24 职场文书
2015年世界无车日活动总结
2015/03/23 职场文书
工伤调解协议书
2016/03/21 职场文书
感谢信的技巧及范例
2019/05/15 职场文书
入党申请书格式
2019/06/20 职场文书
Pytorch中Softmax与LogSigmoid的对比分析
2021/06/05 Python
数据设计之权限的实现
2022/08/05 MySQL