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

test/include/exclude

  然后处理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$/,
use:['css-loader','style-loader]
}
*/

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

/*
{
test:/\.css$/,
use:{
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的规则已经解析完毕,真是配置简单处理复杂。

.19-浅析webpack源码之compile流程-rules参数处理(2)的更多相关文章

  1. .18-浅析webpack源码之compile流程-rules参数处理(1)

    Tips:写到这里,需要对当初的规则进行修改.在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}来测试,基本上测试参数 ...

  2. .17-浅析webpack源码之compile流程-入口函数run

    本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now( ...

  3. .20-浅析webpack源码之compile流程-Template模块

    这里的编译前指的是开始触发主要的事件流this-compilaiton.compilation之前,由于还有一些准备代码,这一节全部弄出来. 模块基本上只走构造函数,具体的方法调用的时候再具体讲解. ...

  4. 从Webpack源码探究打包流程,萌新也能看懂~

    简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...

  5. .29-浅析webpack源码之Resolver.prototype.resolve

    在上一节中,最后返回了一个resolver,本质上就是一个Resolver对象: resolver = new Resolver(fileSystem); 这个对象的构造函数非常简单,只是简单的继承了 ...

  6. .29-浅析webpack源码之doResolve事件流(1)

    在上一节中,最后返回了一个resolver,本质上就是一个Resolver对象: resolver = new Resolver(fileSystem); 这个对象的构造函数非常简单,只是简单的继承了 ...

  7. .30-浅析webpack源码之doResolve事件流(1)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

  8. .34-浅析webpack源码之事件流make(3)

    新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...

  9. .30-浅析webpack源码之doResolve事件流(2)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

随机推荐

  1. [LintCode] Permutations II

    Given a collection of numbers that might contain duplicates, return all possible unique permutations ...

  2. Java虚拟机7:垃圾收集(GC)-2(并行和并发的区别)

    1.并发编程下 这两个名词都是并发编程中的概念,在并发编程的模型下的定义: 并发:是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序. 并行:是多个或同 ...

  3. 小猴打架(luogu4430)(数论+生成树计数)

    一开始森林里面有\(N\)只互不相识的小猴子,它们经常打架,但打架的双方都必须不是好朋友.每次打完架后,打架的双方以及它们的好朋友就会互相认识,成为好朋友.经过\(N-1\)次打架之后,整个森林的小猴 ...

  4. 07_python_集合深浅拷贝

    一.join li = ["李嘉诚", "麻花藤", "林海峰", "刘嘉玲"] s = "_".j ...

  5. jQuery基础(3)- ajax

    一.jQuery的ajax 1.什么是ajax AJAX = 异步的javascript和XML(Asynchronous Javascript and XML). 简言之,在不重载整个网页的情况下, ...

  6. mysql查询语句分析 explain/desc用法

    explain或desc显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. explain 数据表 或 desc 数据表 显示数据表各字段含义 ...

  7. java爬虫学习

    一.java爬取数据 示例:爬取网站中的所有古风网名:http://www.oicq88.com/gufeng/,并储存入数据库(mysql) jdk版本:jdk1.8 编辑器:idea 项目构建:m ...

  8. Smart/400开发上手4: 调试Cobol代码 (DEBUG with QBATCH)

    Step1:Compile Cobol source CB TIM07 using *SRCDBG option例如:CB MEMBER(TIM07) OPTION(*SRCDBG) WORKU(TI ...

  9. webstorm无法显示左边文件夹目录的解决方法

    webstorm无法显示左边文件夹目录的解决方法 方法一 view-->Tool Windows-->Project 就可以显示或者关闭 方法二 1.删除webstorm的配置文件夹 2. ...

  10. dubbo源码阅读之SPI

    dubbo SPI SPI,全程Service Provider interface, java中的一种借口扩展机制,将借口的实现类注明在配置文件中,程序在运行时通过扫描这些配置文件从而获取全部的实现 ...