細(xì)說(shuō)webpack源碼之compile流程-rules參數(shù)處理技巧(2)
上篇文章給大家介紹了細(xì)說(shuō)webpack源碼之compile流程-rules參數(shù)處理技巧(1), 細(xì)說(shuō)webpack源碼之compile流程-入口函數(shù)run
大家可以點(diǎn)擊查看。
第一步處理rule為字符串,直接返回一個(gè)包裝類,很簡(jiǎn)單看注釋就好了。
test
然后處理test、include、exclude,如下:
if (rule.test || rule.include || rule.exclude) {
// 標(biāo)記使用參數(shù)
checkResourceSource("test + include + exclude");
// 沒(méi)有就是undefined
condition = {
test: rule.test,
include: rule.include,
exclude: rule.exclude
};
// 處理常規(guī)參數(shù)
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;
}
這個(gè)用于檢測(cè)配置來(lái)源的唯一性,后面會(huì)能看到作用,同樣作用的還有checkUseSource方法。
隨后將三個(gè)參數(shù)包裝成一個(gè)對(duì)象傳入normalizeCondition方法,該方法對(duì)常規(guī)參數(shù)進(jìn)行函數(shù)包裝:
class RuleSet {
constructor(rules) { /**/ };
static normalizeCondition(condition) {
// 假值報(bào)錯(cuò)
if (!condition) throw new Error("Expected condition but got falsy value");
// 檢測(cè)給定字符串是否以這個(gè)開(kāi)頭
if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
// 函數(shù)直接返回
if (typeof condition === "function") { return condition; }
// 正則表達(dá)式返回一個(gè)正則的test函數(shù)
if (condition instanceof RegExp) { return condition.test.bind(condition); }
// 數(shù)組map遞歸處理 有一個(gè)滿足返回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 = [];
// 對(duì)象會(huì)對(duì)每個(gè)值進(jìn)行函數(shù)包裝彈入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做案例,看這個(gè)方法的返回:
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,...] 的函數(shù)
if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
// test參數(shù)返回了 /\.js$/.test 函數(shù)
if (condition instanceof RegExp) { return condition.test.bind(condition); }
// include為數(shù)組
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為一個(gè)數(shù)組
case "include":
case "test":
if (value)
matchers.push(RuleSet.normalizeCondition(value));
break;
// undefined跳過(guò)
case "exclude":
if (value) { /**/ }
break;
default:
throw new Error("Unexcepted property " + key + " in condition");
}
});
return andMatcher(matchers);
}
}
這里繼續(xù)看orMatcher、andMatcher函數(shù)的處理:
function orMatcher(items) {
// 當(dāng)一個(gè)函數(shù)被滿足條件時(shí)返回true
return function(str) {
for (let i = 0; i < items.length; i++) {
if (items[i](str))
return true;
}
return false;
};
}
function andMatcher(items) {
// 當(dāng)一個(gè)條件不滿足時(shí)返回false
return function(str) {
for (let i = 0; i < items.length; i++) {
if (!items[i](str))
return false;
}
return true;
};
}
從字面意思也可以理解函數(shù)作用,比如說(shuō)這里的include被包裝成一個(gè)orMatcher函數(shù),傳入的字符串無(wú)論是以數(shù)組中任何一個(gè)元素開(kāi)頭都會(huì)返回true,andMatcher以及未出現(xiàn)的notMatcher同理。
resource
下面是對(duì)resource參數(shù)的處理,源碼如下:
if (rule.resource) {
// 如果前面檢測(cè)到了test || include || exclude參數(shù) 這里會(huì)報(bào)錯(cuò)
checkResourceSource("resource");
try {
newRule.resource = RuleSet.normalizeCondition(rule.resource);
} catch (error) {
throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
}
}
可以看出這個(gè)參數(shù)與前面那個(gè)是互斥的,應(yīng)該是老版API,下面兩種方式實(shí)現(xiàn)是一樣的:
/*
方式1:
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
}
]
*/
/*
方式2:
rules:[
{
resource:{
test: /\.js$/,
include: [resolve('src'), resolve('test')]
exclude: undefined
}
}
]
*/
接下來(lái)的resourceQuery、compiler、issuer先跳過(guò),腳手架沒(méi)有,不知道怎么寫(xiě)。
loader
下面是另一塊主要配置loader(s),這里loader與loaders作用一樣的,當(dāng)初還頭疼啥區(qū)別:
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出現(xiàn)
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同時(shí)出現(xiàn)報(bào)錯(cuò)
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)")));
}
/*
處理這種愚蠢用法時(shí):
{
test: /\.css$/,
loader: [{ loader: 'less-loader' }, { loader: 'css-loader' }]
}
*/
else if (loader) {
checkUseSource("loaders");
newRule.use = RuleSet.normalizeUse(loader, ident);
}
// 單獨(dú)出現(xiàn)options或者query報(bào)錯(cuò)
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情況這里會(huì)返回[[loader1...],[loader2...]]
.map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))
// 扁平化后變成[loader1,loader2]
.reduce((arr, items) => arr.concat(items), []);
}
// 對(duì)象或字符串
return [RuleSet.normalizeUseItem(use, ident)];
};
先講解有options或者query的模式,這里會(huì)把參數(shù)包裝一個(gè)對(duì)象傳入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參數(shù)
if (newItem.options.ident)
newItem.ident = newItem.options.ident;
else
newItem.ident = ident;
}
// 取出loader參數(shù)
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;
}
比起對(duì)test的處理,這里就簡(jiǎn)單的多,簡(jiǎn)述如下:
1、嘗試取出options(query)中的ident參數(shù),賦值給newItem的ident,沒(méi)有就賦值為默認(rèn)的ref-
2、取出對(duì)象中不是options、query的鍵,賦值給newItem,這里傳進(jìn)來(lái)的鍵只有三個(gè),剩下的就是loader,這個(gè)filter是逗我???
3、返回newItem對(duì)象
總之,不知道為什么防止什么意外情況而寫(xiě)出來(lái)的垃圾代碼,這段代碼其實(shí)十分簡(jiǎn)單。
單loader
第二種情況是單字符串,會(huì)對(duì)'!'進(jìn)行切割調(diào)用map方法處理,再調(diào)用reduce方法扁平化為一個(gè)數(shù)組,直接看normalizeUseItemString方法:
/*
Example:
{
test: /\.css$/,
loader: 'css-loader?{opt:1}!style-loader'
}
返回:
[{loader:'css-loader',options:{opt:1}},
{loader:'style-loader'}]
*/
static normalizeUseItemString(useItemString) {
// 根據(jù)'?'切割獲取loader的參數(shù)
const idx = useItemString.indexOf("?");
if (idx >= 0) {
return {
loader: useItemString.substr(0, idx),
// 后面的作為options返回
options: useItemString.substr(idx + 1)
};
}
return {
loader: useItemString
};
}
這種就是串行調(diào)用loader的處理方式,代碼很簡(jiǎn)單。
Tips
這里有一點(diǎn)要注意,一旦loader使用了串行調(diào)用方式,不要傳options或者query參數(shù),不然loader不會(huì)被切割解析?。?!
這兩種方式只是不同的使用方法,最后的結(jié)果都是一樣的。
use
下面是use參數(shù)的解析,估計(jì)因?yàn)檫@是老版的API,所以格式要求嚴(yán)格,處理比較隨便:
if (rule.use) {
checkUseSource("use");
newRule.use = RuleSet.normalizeUse(rule.use, ident);
}
如果不用loader(s),可以用use替代,但是需要按照格式寫(xiě),比如說(shuō)上述串行簡(jiǎn)寫(xiě)的loader,在use中就需要這樣寫(xiě):
/*
{
test:/\.css$/,
user:['css-loader','style-loader]
}
*/
而對(duì)應(yīng)options或query,需要這樣寫(xiě):
/*
{
test:/\.css$/,
user:{
loader:'css-loader',
options:'1'
}
}
*/
因?yàn)榛旧喜挥昧?,所以這里簡(jiǎn)單看一下。
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`);
這兩個(gè)用得少,也沒(méi)啥難理解的。
下一步是過(guò)濾出沒(méi)有處理的參數(shù),添加到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,暫時(shí)不知道有啥額外參數(shù)。
ident
// 防止rules:[]的情況
if (Array.isArray(newRule.use)) {
newRule.use.forEach((item) => {
// ident來(lái)源于options/query的ident參數(shù)
if (item.ident) {
refs[item.ident] = item.options;
}
});
}
最后這個(gè)地方是終于用到了傳進(jìn)來(lái)的純凈對(duì)象refs。
如果在options中傳了ident參數(shù),會(huì)填充這個(gè)對(duì)象,key為ident值,value為對(duì)應(yīng)的options。
至此,所有rules的規(guī)則已經(jīng)解析完畢,真是配置簡(jiǎn)單處理復(fù)雜。
總結(jié)
以上所述是小編給大家介紹的細(xì)說(shuō)webpack源碼之compile流程-rules參數(shù)處理技巧(2),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
關(guān)于javascript 回調(diào)函數(shù)中變量作用域的討論
關(guān)于回調(diào)函數(shù)中變量作用域的討論精品推薦,大家可以參考下。2009-09-09
JS實(shí)現(xiàn)左右拖動(dòng)改變內(nèi)容顯示區(qū)域大小的方法
這篇文章主要介紹了JS實(shí)現(xiàn)左右拖動(dòng)改變內(nèi)容顯示區(qū)域大小的方法,涉及JavaScript實(shí)時(shí)響應(yīng)鼠標(biāo)事件動(dòng)態(tài)改變頁(yè)面元素屬性的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
js獲取上傳文件的絕對(duì)路徑實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js獲取上傳文件的絕對(duì)路徑實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
JS實(shí)現(xiàn)動(dòng)態(tài)給標(biāo)簽控件添加事件的方法示例
這篇文章主要介紹了JS實(shí)現(xiàn)動(dòng)態(tài)給標(biāo)簽控件添加事件的方法,結(jié)合實(shí)例形式分析了javascript簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)添加事件的相關(guān)操作技巧,需要的朋友可以參考下2017-05-05
微信小程序的授權(quán)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了微信小程序的授權(quán)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

