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


Posted in Javascript onDecember 26, 2017

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

大家可以点击查看。

第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。

test

然后处理test、include、exclude,如下:

if (rule.test || rule.include || rule.exclude) {
 // 标记使用参数
 checkResourceSource("test + include + exclude");
 // 没有就是undefined
 condition = {
 test: rule.test,
 include: rule.include,
 exclude: rule.exclude
 };
 // 处理常规参数
 try {
 newRule.resource = RuleSet.normalizeCondition(condition);
 } catch (error) {
 throw new Error(RuleSet.buildErrorMessage(condition, error));
 }
}

checkResourceSource直接看源码:

let resourceSource;
// ...
function checkResourceSource(newSource) {
 // 第一次直接跳到后面赋值
 if (resourceSource && resourceSource !== newSource)
 throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")")));
 resourceSource = newSource;
}

这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。

随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:

class RuleSet {
 constructor(rules) { /**/ };
 static normalizeCondition(condition) {
 // 假值报错
 if (!condition) throw new Error("Expected condition but got falsy value");
 // 检测给定字符串是否以这个开头
 if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
 // 函数直接返回
 if (typeof condition === "function") { return condition; }
 // 正则表达式返回一个正则的test函数
 if (condition instanceof RegExp) { return condition.test.bind(condition); }
 // 数组map递归处理 有一个满足返回true
 if (Array.isArray(condition)) {
  const items = condition.map(c => RuleSet.normalizeCondition(c));
  return orMatcher(items);
 }
 if (typeof condition !== "object") throw Error("Unexcepted " + typeof condition + " when condition was expected (" + condition + ")");
 const matchers = [];
 // 对象会对每个值进行函数包装弹入matchers中
 Object.keys(condition).forEach(key => {
  const value = condition[key];
  switch (key) {
  case "or":
  case "include":
  case "test":
   if (value)
   matchers.push(RuleSet.normalizeCondition(value));
   break;
  case "and":
   if (value) {
   const items = value.map(c => RuleSet.normalizeCondition(c));
   matchers.push(andMatcher(items));
   }
   break;
  case "not":
  case "exclude":
   if (value) {
   const matcher = RuleSet.normalizeCondition(value);
   matchers.push(notMatcher(matcher));
   }
   break;
  default:
   throw new Error("Unexcepted property " + key + " in condition");
  }
 });
 if (matchers.length === 0)
  throw new Error("Excepted condition but got " + condition);
 if (matchers.length === 1)
  return matchers[0];
 return andMatcher(matchers);
 }
}

这里用js的rules做案例,看这个方法的返回:

class RuleSet {
 constructor(rules) { /**/ };
 /*
 Example:
 {
  test: /\.js$/,
  loader: 'babel-loader',
  include: [resolve('src'), resolve('test')]
 }
 */
 /*
 condition:
 {
  test: /\.js$/,
  include: ['d:\\workspace\\src', 'd:\\workspace\\test'],
  exclude: undefined
 }
 */
 static normalizeCondition(condition) {
 // include返回类似于 [(str) => str.indexOf('d:\\workspace\\src') === 0,...] 的函数
 if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
 // test参数返回了 /\.js$/.test 函数
 if (condition instanceof RegExp) { return condition.test.bind(condition); }
 // include为数组
 if (Array.isArray(condition)) {
  const items = condition.map(c => RuleSet.normalizeCondition(c));
  return orMatcher(items);
 }
 const matchers = [];
 // 解析出['test','include','exclude']
 Object.keys(condition).forEach(key => {
  const value = condition[key];
  switch (key) {
  // 此value为一个数组
  case "include":
  case "test":
   if (value)
   matchers.push(RuleSet.normalizeCondition(value));
   break;
  // undefined跳过
  case "exclude":
   if (value) { /**/ }
   break;
  default:
   throw new Error("Unexcepted property " + key + " in condition");
  }
 });
 return andMatcher(matchers);
 }
}

这里继续看orMatcher、andMatcher函数的处理:

function orMatcher(items) {
 // 当一个函数被满足条件时返回true
 return function(str) {
 for (let i = 0; i < items.length; i++) {
  if (items[i](str))
  return true;
 }
 return false;
 };
}

function andMatcher(items) {
 // 当一个条件不满足时返回false
 return function(str) {
 for (let i = 0; i < items.length; i++) {
  if (!items[i](str))
  return false;
 }
 return true;
 };
}

从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。

resource

下面是对resource参数的处理,源码如下:

if (rule.resource) {
 // 如果前面检测到了test || include || exclude参数 这里会报错
 checkResourceSource("resource");
 try {
 newRule.resource = RuleSet.normalizeCondition(rule.resource);
 } catch (error) {
 throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
 }
}

可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:

/*
方式1:
 rules:[
 {
  test: /\.js$/,
  loader: 'babel-loader',
  include: [resolve('src'), resolve('test')]
 }
 ]
*/
/*
方式2:
 rules:[
 {
  resource:{
  test: /\.js$/,
  include: [resolve('src'), resolve('test')]
  exclude: undefined
  }
 }
 ]
*/

接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。

loader

下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:

const loader = rule.loaders || rule.loader;
// 单loader情况
if (typeof loader === "string" && !rule.options && !rule.query) {
 checkUseSource("loader");
 newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
}
// loader配合options或query出现
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);
}
// options与query同时出现报错
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)")));
}
/*
 处理这种愚蠢用法时:
 {
 test: /\.css$/,
 loader: [{ loader: 'less-loader' }, { loader: 'css-loader' }]
 }
*/
else if (loader) {
 checkUseSource("loaders");
 newRule.use = RuleSet.normalizeUse(loader, ident);
}
// 单独出现options或者query报错
else if (rule.options || rule.query) {
 throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));
}

之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。

首先normalizeUse方法如下:

static normalizeUse(use, ident) {
 // 单loader字符串
 if (Array.isArray(use)) {
 return use
  // 如果是单loader情况这里会返回[[loader1...],[loader2...]]
  .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))
  // 扁平化后变成[loader1,loader2]
  .reduce((arr, items) => arr.concat(items), []);
 }
 // 对象或字符串
 return [RuleSet.normalizeUseItem(use, ident)];
};

先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:

loader && (options || query)

// indet => 'ref-'
static normalizeUseItem(item, ident) {
 if (typeof item === "function")
 return item;
 if (typeof item === "string") {
 return RuleSet.normalizeUseItemString(item);
 }
 const newItem = {};
 if (item.options && item.query) throw new Error("Provided options and query in use");
 if (!item.loader) throw new Error("No loader specified");
 newItem.options = item.options || item.query;
 // 防止options:null的情况
 if (typeof newItem.options === "object" && newItem.options) {
 // 这里只是为了处理ident参数
 if (newItem.options.ident)
  newItem.ident = newItem.options.ident;
 else
  newItem.ident = ident;
 }
 // 取出loader参数
 const keys = Object.keys(item).filter(function(key) {
 return ["options", "query"].indexOf(key) < 0;
 });
 keys.forEach(function(key) {
 newItem[key] = item[key];
 });
 /*
 newItem = 
 {
 loader:'原字符串',
 ident:'ref-', (或自定义)
 options:{...}
 }
 */
 return newItem;
}

比起对test的处理,这里就简单的多,简述如下:

1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-

2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???

3、返回newItem对象

总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。

单loader

第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:

/*
Example:
{
 test: /\.css$/,
 loader: 'css-loader?{opt:1}!style-loader'
}

返回:

[{loader:'css-loader',options:{opt:1}},
{loader:'style-loader'}]
*/
static normalizeUseItemString(useItemString) {
 // 根据'?'切割获取loader的参数
 const idx = useItemString.indexOf("?");
 if (idx >= 0) {
 return {
  loader: useItemString.substr(0, idx),
  // 后面的作为options返回
  options: useItemString.substr(idx + 1)
 };
 }
 return {
 loader: useItemString
 };
}

这种就是串行调用loader的处理方式,代码很简单。

Tips

这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!

这两种方式只是不同的使用方法,最后的结果都是一样的。

use

下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:

if (rule.use) {
 checkUseSource("use");
 newRule.use = RuleSet.normalizeUse(rule.use, ident);
}

如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:

/*
 {
 test:/\.css$/,
 user:['css-loader','style-loader]
 }
*/

而对应options或query,需要这样写:

/*
 {
 test:/\.css$/,
 user:{
  loader:'css-loader',
  options:'1'
 }
 }
*/

因为基本上不用了,所以这里简单看一下。

rules、oneOf
if (rule.rules)
 newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
if (rule.oneOf)
 newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);

这两个用得少,也没啥难理解的。

下一步是过滤出没有处理的参数,添加到newRuls上:

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];
});

基本上用到都是test、loader、options,暂时不知道有啥额外参数。

ident

// 防止rules:[]的情况
if (Array.isArray(newRule.use)) {
 newRule.use.forEach((item) => {
 // ident来源于options/query的ident参数
 if (item.ident) {
  refs[item.ident] = item.options;
 }
 });
}

最后这个地方是终于用到了传进来的纯净对象refs。

如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。

至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。

总结

以上所述是小编给大家介绍的细说webpack源码之compile流程-rules参数处理技巧(2),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jQuery $.data()方法使用注意细节
Dec 31 Javascript
Jquery实现页面加载时弹出对话框代码
Apr 19 Javascript
javascript实现Table排序的方法
May 15 Javascript
详解JavaScript ES6中的Generator
Jul 28 Javascript
javascript判断复选框是否选中的方法
Oct 16 Javascript
通过sails和阿里大于实现短信验证
Jan 04 Javascript
详解JS中的立即执行函数
Feb 24 Javascript
JavaScript仿微信(电话)联系人列表滑动字母索引实例讲解(推荐)
Aug 16 Javascript
浅谈JavaScript面向对象--继承
Mar 20 Javascript
JS开发常用工具函数(小结)
Jul 04 Javascript
AngularJs的$http发送POST请求,php无法接收Post的数据问题及解决方案
Aug 13 Javascript
react中的DOM操作实现
Jun 30 Javascript
AngularJS实现的根据数量与单价计算总价功能示例
Dec 26 #Javascript
细说webpack源码之compile流程-rules参数处理技巧(1)
Dec 26 #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
You might like
利用PHP制作简单的内容采集器的原理分析
2008/10/01 PHP
destoon实现会员商铺中指定会员或会员组投放广告的方法
2014/08/21 PHP
php实现的Curl封装类Curl.class.php用法实例分析
2015/09/25 PHP
Symfony2学习笔记之控制器用法详解
2016/03/17 PHP
功能强大的PHP发邮件类
2016/08/29 PHP
Svg.js实例教程及使用手册详解(一)
2016/05/16 Javascript
jquery ajax结合thinkphp的getjson实现跨域的方法
2016/06/06 Javascript
AngularJS实现单独作用域内的数据操作
2016/09/05 Javascript
微信小程序 Storage API实例详解
2016/10/02 Javascript
微信小程序 生命周期详解
2016/10/12 Javascript
浅谈vuex 闲置状态重置方案
2018/01/04 Javascript
ES6 中可以提升幸福度的小功能
2018/08/06 Javascript
jQuery pagination分页示例详解
2018/10/23 jQuery
JavaScript中concat复制数组方法浅析
2019/01/20 Javascript
vue.js+ElementUI实现进度条提示密码强度效果
2020/01/18 Javascript
JavaScript基于用户照片姓名生成海报
2020/05/29 Javascript
JavaScript实现与web通信的方法详解
2020/08/07 Javascript
[04:59]DOTA2-DPC中国联赛 正赛 Ehome vs iG 选手采访
2021/03/11 DOTA
Python利用Beautiful Soup模块修改内容方法示例
2017/03/27 Python
python机器学习之决策树分类详解
2017/12/20 Python
Python smtplib实现发送邮件功能
2018/05/22 Python
Python3.6日志Logging模块简单用法示例
2018/06/14 Python
Pandas+Matplotlib 箱式图异常值分析示例
2019/12/09 Python
Pytorch模型转onnx模型实例
2020/01/15 Python
Django model重写save方法及update踩坑详解
2020/07/27 Python
Django自带用户认证系统使用方法解析
2020/11/12 Python
Reebok俄罗斯官方网上商店:购买锐步运动服装和鞋子
2016/09/26 全球购物
自荐信的五个重要部分
2013/10/29 职场文书
学习型党组织建设经验材料
2014/05/26 职场文书
2014年学校国庆主题活动方案
2014/09/16 职场文书
2015年办公室个人工作总结
2015/04/20 职场文书
节约用电倡议书
2015/04/28 职场文书
2015年人力资源部工作总结
2015/04/30 职场文书
欠款证明
2015/06/24 职场文书
行政后勤人员工作计划应该怎么写?
2019/08/16 职场文书
JAVA SpringMVC实现自定义拦截器
2022/03/16 Python