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

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

Tips:寫到這裡,需要對當初的規則進行修改。在必要的地方,會在webpack.config.js中設置特殊的參數來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數均取自於vue腳手架(太複雜的刪掉)。 下麵兩節的主要流程圖如下: 在進入compile方法後,迎面 ...


  Tips:寫到這裡,需要對當初的規則進行修改。在必要的地方,會在webpack.config.js中設置特殊的參數來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數均取自於vue腳手架(太複雜的刪掉)。

  下麵兩節的主要流程圖如下:

  在進入compile方法後,迎面而來的就是這麼一行代碼:

const params = this.newCompilationParams();

  簡單看一下方法,源碼如下:

Compiler.prototype.newCompilationParams = () => {
    const params = {
        normalModuleFactory: this.createNormalModuleFactory(),
        contextModuleFactory: this.createContextModuleFactory(),
        compilationDependencies: []
    };
    return params;
}
// 工廠方法1
Compiler.prototype.createNormalModuleFactory = () => {
    const normalModuleFactory = new NormalModuleFactory(this.options.context, this.resolvers, this.options.module || {});
    this.applyPlugins("normal-module-factory", normalModuleFactory);
    return normalModuleFactory;
}
// 工廠方法2
Compiler.prototype.createContextModuleFactory = () => {
    const contextModuleFactory = new ContextModuleFactory(this.resolvers, this.inputFileSystem);
    this.applyPlugins("context-module-factory", contextModuleFactory);
    return contextModuleFactory;
}

  該方法生成了一個對象參數,而對象中的值又分別調用了各自的工廠方法。

  按順序,首先來看NormalModuleFactory工廠方法,首先是構造函數:

class NormalModuleFactory extends Tapable {
    // context預設為命令執行路徑
    // resolvers => {...}
    // options => options.module
    constructor(context, resolvers, options) {
        super();
        // 該參數在WebpackOptionsApply方法中被賦值
        this.resolvers = resolvers;
        // 處理module.rules或module.loaders
        // 兩者意義一樣
        this.ruleSet = new RuleSet(options.rules || options.loaders);
        // 誰會去設置這玩意???預設參數設置那會被置為true
        // 這裡會生成一個返回布爾值的函數 可以簡單理解為!!
        this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
        this.context = context || "";
        // 解析緩存
        this.parserCache = {};
        // 這兩個方法超級長
        this.plugin("factory", () => (result, callback) => { /**/ });
        this.plugin("resolver", () => (data, callback) => { /**/ })
    }
}

  也是一個繼承了Tapable框架的類,構造函數除去兩個事件流註入不看,剩餘的內容只有RuleSet那一塊,也是本節的主要內容。

 

  從參數可以看出,這裡是對module.rules處理的地方,本節中測試配置添加了rules方便測試:

const path = require('path');
// resolve => path.join(__dirname,'..',path)
module.exports = {
    entry: './input.js',
    output: {
        filename: 'output.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test')]
            },
            {
                test: /\.css/,
                loader: 'css-loader!style-loader'
            }
        ]
    }
};

  只針對配置中有或者常用且官網有解釋的參數進行。

 

RuleSet

  構造函數如下:

module.exports = class RuleSet {
    constructor(rules) {
        // 空對象
        this.references = Object.create(null);
        // 格式化
        this.rules = RuleSet.normalizeRules(rules, this.references, "ref-");
    }
}

  生成了一個純凈對象並調用了本地靜態方法進行格式化:

class RuleSet {
    constructor(rules) { /**/ }
        // rules => 傳進來的配置數組
        // refs => 純凈對象
        // ident => 'ref-'
    static normalizeRules(rules, refs, ident) {
        // 分數組與非數組
        if (Array.isArray(rules)) {
            return rules.map((rule, idx) => {
                return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`);
            });
        } else if (rules) {
            return [RuleSet.normalizeRule(rules, refs, ident)];
        }
        // 未配置rules直接返回空數組 
        else {
            return [];
        }
    }
}

  這裡也區分了數組參數與非數組參數,但是有個小bug

  看代碼可以很容易猜到,數組與非數組的情況理論上是這樣的:

// 數組
module.exports = {
    // ...
    module: {
        rules: [{ test: /\.vue$/, loader: 'vue-loader' }, /*...*/ ]
    }
};
// 非數組
module.exports = {
    // ...
    module: {
        rules: { test: /\.vue$/, loader: 'vue-loader' }
    }
};

  因為傳非數組,會被包裝成一個數組,所以這種情況屬於單loader配置。

  但是,這樣配置是會報錯的,因為過不了validateSchema的驗證,測試結果如圖:

  這就很尷尬了,提供了非數組形式的處理方式,但是又不通過非數組的校驗,所以這基本上是永遠不會被執行的代碼。

  管他的,估計源碼太大,開發者已經顧不上了。

  下麵正式進行格式化階段,源碼整理如下:

class RuleSet {
    constructor(rules) { /**/ };
    // ident => 'ref-${index}'
    static normalizeRule(rule, refs, ident) {
        // 傳入字元串
        // 'css-loader' => use:[{loader:'css-loader'}]
        if (typeof rule === "string")
            return {
                use: [{
                    loader: rule
                }]
            };
        // 非法參數
        if (!rule)
            throw new Error("Unexcepted null when object was expected as rule");
        if (typeof rule !== "object")
            throw new Error("Unexcepted " + typeof rule + " when object was expected as rule (" + rule + ")");

        const newRule = {};
        let useSource;
        let resourceSource;
        let condition;
        // test
        if (rule.test || rule.include || rule.exclude) { /**/ }
        if (rule.resource) { /**/ }
        // 官網doc都懶得解釋 估計是幾個很弱智的參數
        if (rule.resourceQuery) { /**/ }
        if (rule.compiler) { /**/ }
        if (rule.issuer) { /**/ }
        // loader、loaders只能用一個
        if (rule.loader && rule.loaders)
            throw new Error(RuleSet.buildErrorMessage(rule, new Error("Provided loader and loaders for rule (use only one of them)")));

        const loader = rule.loaders || rule.loader;
        // 處理loader
        if (typeof loader === "string" && !rule.options && !rule.query) {
            checkUseSource("loader");
            newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
        }
        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);
        } 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)")));
        } else if (loader) {
            checkUseSource("loaders");
            newRule.use = RuleSet.normalizeUse(loader, ident);
        } else if (rule.options || rule.query) {
            throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));
        }
        // 處理use
        if (rule.use) {
            checkUseSource("use");
            newRule.use = RuleSet.normalizeUse(rule.use, ident);
        }
        // 遞歸處理內部rules
        if (rule.rules)
            newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
        // 不知道是啥
        if (rule.oneOf)
            newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);
        //
        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];
        });

        function checkUseSource(newSource) { /**/ }

        function checkResourceSource(newSource) { /**/ }
        if (Array.isArray(newRule.use)) {
            newRule.use.forEach((item) => {
                if (item.ident) {
                    refs[item.ident] = item.options;
                }
            });
        }

        return newRule;
    }
}

  總體來看源碼內容如下:

1、生成newRules對象保存轉換後的rules

2、處理單字元串rule

3、處理test、include、exclude參數

4、處理resource、resourseQuery、compiler、issuer參數

5、處理loader、loaders、options、query參數

6、處理use參數

7、遞歸處理rules參數

8、處理oneOf參數

 

  內容比較多,還是分兩節講吧。


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

-Advertisement-
Play Games
更多相關文章
  • 前言 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 ...
  • 第一步處理rule為字元串,直接返回一個包裝類,很簡單看註釋就好了。 test 然後處理test、include、exclude,如下: checkResourceSource直接看源碼: 這個用於檢測配置來源的唯一性,後面會能看到作用,同樣作用的還有checkUseSource方法。 隨後將三個參 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...