.19-淺析webpack源碼之compile流程-rules參數處理(2)

来源:https://www.cnblogs.com/QH-Jimmy/archive/2017/12/26/8112520.html
-Advertisement-
Play Games

第一步處理rule為字元串,直接返回一個包裝類,很簡單看註釋就好了。 test 然後處理test、include、exclude,如下: checkResourceSource直接看源碼: 這個用於檢測配置來源的唯一性,後面會能看到作用,同樣作用的還有checkUseSource方法。 隨後將三個參 ...


  第一步處理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的規則已經解析完畢,真是配置簡單處理複雜。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目的: 創建簡單和複雜視圖 視圖復取數據 創建、維護和使用序列 創建和維護索引 創建私有和公有同義詞 資料庫對象: 視圖【View】 什麼是視圖? 視圖呈現邏輯子集或數據的組合。視圖是基於表或其他視圖的邏輯表。一個視圖不包含自己的數據,而是像一個視窗,通過視窗可以查看或更改表中的數據。視圖所基於的表 ...
  • 前言 DataGrip :Jet Brains出品的一款資料庫管理工具(沒錯,是Jet Brains出品,必屬精品)。DataGrip整合集成了當前主流資料庫(如:SQL Server, MySQL, Oracle, PostgreSQL, Sybase, Sqlite, DB2, Azure等)的 ...
  • 靜態創建自定義視圖就是以拖動的方法來創建。 動態創建自定義視圖可以理解為使用代碼來創建自定義視圖。 參考資料:《iOS7開發快速入門》 ...
  • 一.發展史 1G 模擬制式手機,只能進行語音通話2G 數字制式手機,增加接收數據等功能3G 智能手機,它已經成了集語音通信和多媒體通信相結合,並且包括圖像、音樂、網頁瀏覽、電話會議以及其它一些信息服務等增值服務的新一代移動通信系統4G 第四代移動通信技術, 4G是集3G與WLAN於一體,並能夠快速傳 ...
  • 分享一個開源的項目 share device 項目地址:https://github.com/sunshine4me/ShareDevicePublish/tree/win7-x64 首先選擇對應系統的下載包,如下圖: 註意: 運行 ShareDevice.exe 運行 ShareDevice.ex ...
  • 2015 年是銘心刻骨的一年,這一年,股市崩盤,千古跌停,手裡的兩個票更是挨了腰斬,寶寶心裡苦。同是這一年,ES6 標準落地,作為一名前端開發,寶寶心裡又樂開了花。 ...
  • 廢話少說 上鏈接 angular 升級指南 ...
  • 前言 在JSBridge實現後,前端網頁與原生的交互已經通了,接下來就要開始規劃API,明確需要提供哪一些功能來供前端調用。 但是在這之前,還有一點重要工作需要做: __明確H5與Native的職責劃分,確定哪一些功能可以由H5實現,哪一些功能只能由原生實現__ Native與H5職責劃分 使用Hy ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...