细说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 相关文章推荐
js控制淡入淡出示例代码
Nov 12 Javascript
使用js写的一个简易的投票
Nov 27 Javascript
jQuery 获取/设置/删除DOM元素的属性以a元素为例
May 23 Javascript
JavaScript中具名函数的多种调用方式总结
Nov 08 Javascript
jquery实现仿新浪微博评论滚动效果
Aug 06 Javascript
图解JavaScript中的this关键字
May 28 Javascript
JS实现图片平面旋转的方法
Mar 01 Javascript
图片懒加载插件实例分享(含解析)
Jan 09 Javascript
vue用addRoutes实现动态路由的示例
Sep 15 Javascript
小程序实现选择题选择效果
Nov 04 Javascript
分享Angular http interceptors 拦截器使用(推荐)
Nov 10 Javascript
Axios代理配置及封装响应拦截处理方式
Apr 07 Vue.js
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
星际争霸 Starcraft 秘技补丁
2020/03/14 星际争霸
PHP开发中常用的8个小技巧
2008/08/27 PHP
php更改目录及子目录下所有的文件后缀扩展名的代码
2010/10/12 PHP
php各种编码集详解和以及在什么情况下进行使用
2011/09/11 PHP
php进行支付宝开发中return_url和notify_url的区别分析
2014/12/22 PHP
php获取文件名称和扩展名的方法
2017/02/07 PHP
js 点击页面其他地方关闭弹出层(示例代码)
2013/12/24 Javascript
JS数组的遍历方式for循环与for...in
2014/07/31 Javascript
JavaScript实现向setTimeout执行代码传递参数的方法
2015/04/16 Javascript
JavaScript基础教程——入门必看篇
2016/05/20 Javascript
Vue.JS入门教程之事件监听
2016/12/01 Javascript
vuejs指令详解
2017/02/07 Javascript
Angular多选、全选、批量选择操作实例代码
2017/03/10 Javascript
基于bootstrap按钮式下拉菜单组件的搜索建议插件
2017/03/25 Javascript
es6学习笔记之Async函数基本教程
2017/05/11 Javascript
为什么使用koa2搭建微信第三方公众平台的原因
2018/05/16 Javascript
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧滑动,右侧不动)
2019/01/23 Javascript
vue-cli中使用高德地图的方法示例
2019/03/28 Javascript
Vue的编码技巧与规范使用详解
2019/08/28 Javascript
Vue根据条件添加click事件的方式
2019/11/09 Javascript
爬山算法简介和Python实现实例
2014/04/26 Python
pygame实现弹力球及其变速效果
2017/07/03 Python
python编写简易聊天室实现局域网内聊天功能
2018/07/28 Python
PyCharm下载和安装详细步骤
2019/12/17 Python
简述python Scrapy框架
2020/08/17 Python
python 实现汉诺塔游戏
2020/11/28 Python
css3 column实现卡片瀑布流布局的示例代码
2018/06/22 HTML / CSS
Stuart Weitzman美国官网:美国奢华鞋履品牌
2016/08/18 全球购物
学生上课迟到检讨书
2015/01/01 职场文书
西安导游词
2015/02/12 职场文书
停水通知
2015/04/16 职场文书
2015年小学教导处工作总结
2015/05/26 职场文书
2016高考感言
2015/08/01 职场文书
幼儿园奖惩制度范本
2015/08/05 职场文书
会计主管竞聘书
2015/09/15 职场文书
一文搞懂MySQL索引页结构
2022/02/28 MySQL