.26-淺析webpack源碼之事件流make(1)

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/01/09/8250774.html
-Advertisement-
Play Games

compilation事件流中,依然只是針對細節步驟做事件流註入,代碼流程如圖: 觸發完compilation事件流後,會直接返回一個compilation對象,然後觸發下一個事件流make。 make的來源在EntryOptionPlugin插件中,無論entry參數是單入口字元串、單入口數組、多 ...


  compilation事件流中,依然只是針對細節步驟做事件流註入,代碼流程如圖:

// apply => this-compilation
// apply => compilation 
// return compialtion
const compilation = this.newCompilation(params);
this.applyPluginsParallel("make", compilation, err => {
    // callback...
});

  觸發完compilation事件流後,會直接返回一個compilation對象,然後觸發下一個事件流make。

  make的來源在EntryOptionPlugin插件中,無論entry參數是單入口字元串、單入口數組、多入口對象還是動態函數,都會在引入對應的入口插件後,註入一個make事件。

  這裡先以最簡單的單入口字元串為例,開始跑make事件流:

// SingleEntryPlugin
compiler.plugin("make", (compilation, callback) => {
    // 生成一個類型為single entry的依賴類
    // dep.loc = name
    const dep = SingleEntryPlugin.createDependency(this.entry, this.name);
    compilation.addEntry(this.context, dep, this.name, callback);
});

 

Compilation.addEntry

  這裡回到了compilation類中,調用原型函數addEntry。

class Compilation extends Tapable {
    // ...
    // context => 預設為process.cwd()
    // entry => dep => SingleEntryDependency
    // name => 單入口預設為main
    // callback => 後面的流程
    addEntry(context, entry, name, callback) {
        const slot = {
            name: name,
            module: null
        };
        // 初始為[]
        this.preparedChunks.push(slot);
        this._addModuleChain(context, entry, (module) => { /**/ }, (err, module) => { /**/ });
    }
}

 

Compilation._addModuleChain

  沒什麼好講的,直接進入_addModuleChain函數:

class Compilation extends Tapable {
    // ...
    _addModuleChain(context, dependency, onModule, callback) {
        // profile => options.profile 
        // 不傳則start為undefined
        const start = this.profile && Date.now();
        // bail => options.bail
        const errorAndCallback = this.bail ? (err) => {
            callback(err);
        } : (err) => {
            err.dependencies = [dependency];
            this.errors.push(err);
            callback();
        };

        if (typeof dependency !== "object" || dependency === null || !dependency.constructor) {
            throw new Error("Parameter 'dependency' must be a Dependency");
        }
        // dependencyFactories包含了所有的依賴集合
        const moduleFactory = this.dependencyFactories.get(dependency.constructor);
        if (!moduleFactory) {
            throw new Error(`No dependency factory available for this dependency type: ${dependency.constructor.name}`);
        }

        this.semaphore.acquire(() => { /**/ });
    }
}

  profile和bail參數大概不會有人傳吧,所有直接忽視。

  接下來就是嘗試從dependencyFactories中獲取依賴類對應的值,這個Map對象所有值的設置都在compilation事件流中,詳情可見24節中的流程圖,傳送門:http://www.cnblogs.com/QH-Jimmy/p/8183840.html

  在這裡,依賴類來源於SingleEntryDependency,鍵值對設置地點同樣來源於SingleEntryPlugin:

// SingleEntryPlugin
compiler.plugin("compilation", (compilation, params) => {
    const normalModuleFactory = params.normalModuleFactory;
    // 這裡
    compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory);
});

  所以很簡單,這裡調用get後取出來的是normalModuleFactory。

  而這個normalModuleFactory,在18節中有簡單介紹並分析了其中RuleSet對module.rules的處理,傳送門:http://www.cnblogs.com/QH-Jimmy/p/8109903.html

  

semaphore

  獲取對應的moduleFactory後,調用的this.semaphore其中是生成一個新類:

this.semaphore = new Semaphore(options.parallelism || 100);

  (類的名字英文翻譯是信號機)

  內容比較簡單,過一下源碼:

class Semaphore {
    // 一個數字 預設為100
    constructor(available) {
        this.available = available;
        this.waiters = [];
    };
    // 當available大於0時執行callback並減1
    // 否則將callback彈入waiters
    acquire(callback) {
        if (this.available > 0) {
            this.available--;
            callback();
        } else {
            this.waiters.push(callback);
        }
    };
    // 嘗試取出最近彈入的callback併在事件流末尾執行
    release() {
        if (this.waiters.length > 0) {
            const callback = this.waiters.pop();
            process.nextTick(callback);
        } else {
            this.available++;
        }
    }
}

  設計看起來確實像個信號機,構造函數中有一個初始的使用次數以及一個待執行callback數組。

  每次調用acquire時會傳入一個callback,如果次數為正就執行callback並將使用次數減1,如果次數用完了,就把這個callback彈入waiters數組中。

  每次調用release時,為嘗試取出最新的callback並儘快執行,如果不存在待執行的callback,就將使用次數加1。

 

NormalModuleFactory.create

  也就是說,以下代碼可以理解成簡單的函數調用:

this.semaphore.acquire(() => {
    moduleFactory.create({
        contextInfo: {
            issuer: "",
            compiler: this.compiler.name
        },
        context: context,
        dependencies: [dependency]
    }, (err, module) => { /* fn... */ });
});

  這樣,流程跑到了normalModuleFactory的原型方法create上。

class NormalModuleFactory extends Tapable {
    /*
        data => 
        {
            contextInfo:{
                issuer: '',
                compiler: this.compiler.name // undefined
            },
            context: context, // process.cwd()
            dependencies: [SingleEntryDependency]
        }
    */
    create(data, callback) {
        const dependencies = data.dependencies;
        // 嘗試取緩存
        const cacheEntry = dependencies[0].__NormalModuleFactoryCache;
        if (cacheEntry) return callback(null, cacheEntry);
        // 上下文 => process.cwd()
        const context = data.context || this.context;
        // 入口文件字元串 => ./input.js
        const request = dependencies[0].request;
        const contextInfo = data.contextInfo || {};
        this.applyPluginsAsyncWaterfall("before-resolve", {
            contextInfo,
            context,
            request,
            dependencies
        }, (err, result) => { /**/ });
    }
}

  這裡將上下文、入口文件、入口模塊依賴類整合,然後開始觸發normalModuleFactory類上的事件流。

  關於normalModuleFactory的事件流註入,全部都在24節中有介紹,再來一個傳送門:http://www.cnblogs.com/QH-Jimmy/p/8183840.html

  這裡的事件流before-resolve是沒有的,所以按照Tapable的中applyPluginsAsyncWaterfall的執行方式:

Tapable.prototype.applyPluginsAsyncWaterfall = function applyPluginsAsyncWaterfall(name, init, callback) {
    if (!this._plugins[name] || this._plugins[name].length === 0) return callback(null, init);
    // more...
}

  這裡會直接調用callback,分別傳入null與第二個參數,如下所示:

this.applyPluginsAsyncWaterfall("before-resolve", {
        contextInfo,
        context,
        request,
        dependencies
    },
    // err => null
    // result => 上面的對象 
    (err, result) => {
        if (err) return callback(err);
        if (!result) return callback();
        // 觸發factory事件流
        const factory = this.applyPluginsWaterfall0("factory", null);

        // Ignored
        if (!factory) return callback();

        factory(result, (err, module) => { /**/ });
    }
);

  這裡會接著觸發factory事件流,這個是在構造函數中直接plugin的。

class NormalModuleFactory extends Tapable {
    constructor(context, resolvers, options) {
        super();
        // ...other property
        this.plugin("factory", () => (result, callback) => {
            let resolver = this.applyPluginsWaterfall0("resolver", null);
            if (!resolver) return callback();
            resolver(result, (err, data) => { /**/ });
        });
    }
}

  這裡又觸發了resolver事件流,同樣是構造函數中另外一個plugin的事件。

 

  這節先到這裡。


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

-Advertisement-
Play Games
更多相關文章
  • 一、vue 在nginx下頁面刷新出現404 在網上翻遍了所有這樣問題的解決辦法,全都是一個解決辦法也是正確的解決辦法,(後來在vue官網上關於history方式出現404解決方法也是這樣說的),只是沒有表達完整,可能會讓比較急於解決這個問題的人簡單複製卻始終解決不了問題 nginx正確的配置: 1 ...
  • 完整版下載地址:https://gitee.com/dgx/diVuePagination 完整版演示地址:http://dgx.gitee.io/divuepagination/#/ 一.準備工總 利用vue-cli和webpack如何快速搭建一個項目我們已經有過介紹: 為vue開發準備的一份es ...
  • 當二級聯動比如選擇國家的時候,希望選中一個國家的時候後面城市預設選中第一個城市,則給國家的select加一個@change事件就可以了 methods: ...
  • FCC-學習筆記 Spinal Tap Case 1>最近在學習和練習FCC的題目。這個真的比較的好,推薦給大家。 2>中文版的地址:https://www.freecodecamp.cn/;英文版的地址:https://www.freecodecamp.org 3>這次寫關於一個JS的問題,名為S ...
  • 作者原文: Var聲明和提升機制 在函數作用域和全局作用域中通過關鍵字var聲明變數,無論是在哪裡聲明,都會被當成在當前作用頂部聲明的變數,這就是我們常說的變數提升機制。廢話不多說,來看一個例子: function getValue(flag){ if(flag){ var value = 'abc ...
  • scrollBy可以相對當前位置移動滾動條,而不是移動到絕對位置 ...
  • FCC-學習筆記 Convert HTML Entities 1>最近在學習和練習FCC的題目。這個真的比較的好,推薦給大家。 2>中文版的地址:https://www.freecodecamp.cn/;英文版的地址:https://www.freecodecamp.org 3>這次寫關於一個JS的 ...
  • 例如: 1. $("#txt").trigger("click");//預設觸發點擊搜索按鈕 2. $(".aaa .bbb").eq(0).click();//預設第一個點擊(例如UL的LI有多個可以點擊的用於預設點擊第一個用) 註意: 要放在函數外面, 不然不起效果的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...