溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

細說webpack源碼之compile流程-rules參數(shù)處理技巧(2)

發(fā)布時間:2020-08-30 07:51:30 來源:腳本之家 閱讀:460 作者:書生小龍 欄目:web開發(fā)

上篇文章給大家介紹了細說webpack源碼之compile流程-rules參數(shù)處理技巧(1),    細說webpack源碼之compile流程-入口函數(shù)run

大家可以點擊查看。

第一步處理rule為字符串,直接返回一個包裝類,很簡單看注釋就好了。

test

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

if (rule.test || rule.include || rule.exclude) {
 // 標記使用參數(shù)
 checkResourceSource("test + include + exclude");
 // 沒有就是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;
}

  這個用于檢測配置來源的唯一性,后面會能看到作用,同樣作用的還有checkUseSource方法。

  隨后將三個參數(shù)包裝成一個對象傳入normalizeCondition方法,該方法對常規(guī)參數(shù)進行函數(shù)包裝:

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; }
 // 函數(shù)直接返回
 if (typeof condition === "function") { return condition; }
 // 正則表達式返回一個正則的test函數(shù)
 if (condition instanceof RegExp) { return condition.test.bind(condition); }
 // 數(shù)組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 = [];
 // 對象會對每個值進行函數(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做案例,看這個方法的返回:

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為一個數(shù)組
  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);
 }
}

  這里繼續(xù)看orMatcher、andMatcher函數(shù)的處理:

function orMatcher(items) {
 // 當一個函數(shù)被滿足條件時返回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;
 };
}

  從字面意思也可以理解函數(shù)作用,比如說這里的include被包裝成一個orMatcher函數(shù),傳入的字符串無論是以數(shù)組中任何一個元素開頭都會返回true,andMatcher以及未出現(xiàn)的notMatcher同理。

resource

  下面是對resource參數(shù)的處理,源碼如下:

if (rule.resource) {
 // 如果前面檢測到了test || include || exclude參數(shù) 這里會報錯
 checkResourceSource("resource");
 try {
 newRule.resource = RuleSet.normalizeCondition(rule.resource);
 } catch (error) {
 throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
 }
}

  可以看出這個參數(shù)與前面那個是互斥的,應(yīng)該是老版API,下面兩種方式實現(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
  }
 }
 ]
*/

  接下來的resourceQuery、compiler、issuer先跳過,腳手架沒有,不知道怎么寫。

loader

  下面是另一塊主要配置loader(s),這里loader與loaders作用一樣的,當初還頭疼啥區(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同時出現(xiàn)報錯
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);
}
// 單獨出現(xiàn)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的模式,這里會把參數(shù)包裝一個對象傳入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;
}

  比起對test的處理,這里就簡單的多,簡述如下:

1、嘗試取出options(query)中的ident參數(shù),賦值給newItem的ident,沒有就賦值為默認的ref-

2、取出對象中不是options、query的鍵,賦值給newItem,這里傳進來的鍵只有三個,剩下的就是loader,這個filter是逗我???

3、返回newItem對象

  總之,不知道為什么防止什么意外情況而寫出來的垃圾代碼,這段代碼其實十分簡單。

單loader

  第二種情況是單字符串,會對'!'進行切割調(diào)用map方法處理,再調(diào)用reduce方法扁平化為一個數(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的處理方式,代碼很簡單。

Tips

  這里有一點要注意,一旦loader使用了串行調(diào)用方式,不要傳options或者query參數(shù),不然loader不會被切割解析?。?!

  這兩種方式只是不同的使用方法,最后的結(jié)果都是一樣的。

use

  下面是use參數(shù)的解析,估計因為這是老版的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]
 }
*/

  而對應(yīng)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`);

  這兩個用得少,也沒啥難理解的。

  下一步是過濾出沒有處理的參數(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ù)。

ident

// 防止rules:[]的情況
if (Array.isArray(newRule.use)) {
 newRule.use.forEach((item) => {
 // ident來源于options/query的ident參數(shù)
 if (item.ident) {
  refs[item.ident] = item.options;
 }
 });
}

  最后這個地方是終于用到了傳進來的純凈對象refs。

  如果在options中傳了ident參數(shù),會填充這個對象,key為ident值,value為對應(yīng)的options。

  至此,所有rules的規(guī)則已經(jīng)解析完畢,真是配置簡單處理復(fù)雜。

總結(jié)

以上所述是小編給大家介紹的細說webpack源碼之compile流程-rules參數(shù)處理技巧(2),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI