问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

webpack rules与loaders有什么区别

发布网友 发布时间:2022-04-21 23:58

我来回答

4个回答

懂视网 时间:2022-04-22 23:28

这篇文章主要介绍了webpack源码之compile流程-rules参数处理技巧的相关知识,需要的朋友参考下吧

上篇文章给大家介绍了细说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的规则已经解析完毕,真是配置简单处理复杂。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在nodejs中如何实现websocket通信功能

在js中如何实现绑定点击事件(详细教程)

通过Vue如何实现SSR(详细教程)

热心网友 时间:2022-04-22 20:36

1. 为什么用 webpack? 他像 Browserify, 但是将你的应用打包为多个文件. 如果你的单页面应用有多个页面, 那么用户只从对应页面的代码. 当他么访问到另一个页面, 他们不需要重新通用的代码. 他在很多地方能替代 Grunt 跟 Gulp 因为他能够.

热心网友 时间:2022-04-22 21:54

我不太懂但是后盾人是专业的,里面的一线讲师亲自录制高清视频来帮大家解决一些不懂的问题,含金量高

热心网友 时间:2022-04-22 23:29

webpack1.x ->2.x 中的更改:
mole.loaders 改成了 mole.rules
旧的 loader 配置被更强大的 rules 系统取代,后者允许配置 loader 以及其他更多项。为了兼容旧版,mole.loaders 语法被保留,旧的属性名依然可以被解析。新的命名约定更易于理解并且是升级配置使用 mole.rules 的好理由。
http://www.css88.com/doc/webpack2/guides/migrating/
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
这是真的黑米还是假的? 牡丹江市区好玩的地方 显卡问题,现在的显卡显存一般都为多少? 现在主流显存是多少?512M5年后会过时吗 主流家用电脑配置家用电脑买什么样配置比较好 现在的电脑显存一般是多少? 安顺万家领秀城怎么样?好不好?值不值得买? 请问下面一道题怎么加标点 花园里 牡丹 月季 玫瑰 芍药 开得美丽极了... 是什么把大地打扮的这么漂亮呢 是雪呀 这两句话怎么写标点符号? 通州有那些私立高中啊 《深入浅出Webpack》epub下载在线阅读全文,求百度... 如何开发一个 Webpack Loader 如果我只是想修改已经存在于基础配置中的某个 load... 怎样自己写webpack loader react webpack 的样式相关的 loader,以下两种有什... new webpack.LoaderOptionsPlugin是干什么用的 webpack对样式的处理 导入样式require和import的区别 乌克兰为何针对俄罗斯呢?是有何目的呢? 乌克兰为何要与俄罗斯分手,投入西欧的怀抱? 俄罗斯和乌克兰同为斯拉夫民族,却水火不容,这是... 现代中国名人故事大全 影响中国历史的100位名人 古今历史上出现了许多有才能的人,中国有哪些名人... 中国古代自强不息的名人故事有哪些? 中国历史名人故事? 中国古代名人小故事(100字左右) 中国历史名人故事 葛优的年龄? 葛优为什么没有孩子? 如何评价演员葛优 webpack中require和import的区别 webpack css-loader配置中的sourceMap是什么意思 如何使用WORD文档把单词按ABC顺序排列 word高手请进,如何对word文档中的多个单词进行排序 WORD如何进行英文排序 word里面怎样把同样字母的单词都排列在一起 怎样用WORD把单词排列整齐呢? 如何使用word将杂乱的单词象字典里一样按字母顺序... 如何使Word里面的单词按字母排? WORD如何按开头字母排列单词 word怎样将单词按ABC开头排列 请问在word中,如何将这些单词分成4个一行,按顺序... word文档中的单词是乱序的 我想像字典一样按照A-Z... 50分钻石多少钱?现在50分钻戒的价格贵不贵? 50分钻戒一般多少钱?50分钻戒有多大? 50分钻戒价格一般多少钱 请问五十分的钻戒现在多少钱? 50分钻石多少钱 50分男钻戒大概多少钱 有没有人知道50分钻戒大概需要多少钱?