Webpack Tapable原理詳解

来源:https://www.cnblogs.com/qiqingfu/archive/2019/02/16/10387634.html
-Advertisement-
Play Games

directory "代碼已上傳github, 地址" Detailed Webpack 就像一條生產線, 要經過一系列的處理流程才能將源文件轉換成輸出結果。這條生產線上的每個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後才會轉交到下一個流程。 插件就像一個插入到生產線中的一個功能, ...


directory

    - src
        - sim    ---- 簡單的模擬實現
        - /.js$/ ---- 使用

代碼已上傳github, 地址

Detailed

Webpack 就像一條生產線, 要經過一系列的處理流程才能將源文件轉換成輸出結果。這條生產線上的每個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後才會轉交到下一個流程。

插件就像一個插入到生產線中的一個功能, 它會在特定的時機對生產線上的資源進行處理。

這條生產線很複雜, Webpack則是通過 tapable 核心庫來組織這條生產線。

Webpack 在運行中會通過 tapable 提供的鉤子進行廣播事件, 插件只需要監聽它關心的事件,就可以加入到這條生產線中,去改變生產線的運作。使得 Webpack整體擴展性很好。

Tapable Hook

Tapable 提供同步(Sync)和非同步(Async)鉤子類。而非同步又分為 非同步串列非同步並行鉤子類。

右鍵圖片,在新標簽中查看完整圖片

Tapable Hook Class

逐個分析每個鉤子類的使用及其原理

同步鉤子類

  • SyncHook
  • SyncBailHook
  • SyncWaterfallHook
  • SyncLoopHook

同步鉤子類通過實例的 tap 方法監聽函數, 通過 call發佈事件

SyncHook

同步串列不關心訂閱函數執行後的返回值是什麼。其原理是將監聽(訂閱)的函數存放到一個數組中, 發佈時遍曆數組中的監聽函數並且將發佈時的 arguments傳遞給監聽函數

class SyncHook {
    constructor(options) {
        this.options = options
        this.hooks = []  //存放監聽函數的數組
    }
    tap(name, callback) {
        this.hooks.push(callback)
    }
    call(...args) {
        for (let i = 0; i < this.hooks.length; i++) {
            this.hooks[i](...args)
        }
    }
}

const synchook = new SyncHook('name')
// 註冊監聽函數
synchook.tap('name', (data) => {
    console.log('name', data)
})
synchook.tap('age', (data) => {
    console.log('age', data)
})

// 發佈事件
synchook.call('qiqingfu')

列印結果:

name qiqingfu
age qiqingfu

SyncBailHook

同步串列, 但是如果監聽函數的返回值不為 null, 就終止後續的監聽函數執行

class SyncBailHook {
        constructor(options) {
          this.options = options
          this.hooks = []
    }
    tap(name, callback) {
        this.hooks.push(callback)
    }
    call(...args) {
        let ret, i = 0
        do {
            // 將第一個函數的返回結果賦值給ret, 在while中如果結果為 true就繼續執行do代碼塊
            ret = this.hooks[i++](...args)
        } while(!ret)
    }
}

const syncbailhook = new SyncBailHook('name')

syncbailhook.tap('name', (data) => {
    console.log('name', data)
    return '我的返回值不為null'
})
syncbailhook.tap('age', (data) => {
    console.log('age', data)
})

syncbailhook.call('qiqingfu')

執行結果

name qiqingfu

SyncWaterfallHook

同步串列瀑布流, 瀑布流指的是第一個監聽函數的返回值,做為第二個監聽函數的參數。第二個函數的返回值作為第三個監聽函數的參數,依次類推...

class SyncWaterfallHook {
    constructor(options) {
          this.options = options
          this.hooks = []
    }
    tap(name, callback) {
        this.hooks.push(callback)
    }
    call(...args) {
        let [firstHook, ...otherHooks] = this.hooks
        /**
         * 通過解構賦值先取出第一個監聽函數執行
         * 並且將第一個函數的執行結果傳遞給第二個, 第二個傳遞給第三個,迭代的過程 
         */
        let ret = firstHook(...args)
        otherHooks.reduce((f,n) => {
            return n(f)
        }, ret)
    }
}

const syncWaterfallHook = new SyncWaterfallHook('name')

syncWaterfallHook.tap('name', data => {
    console.log('name', data)
    return 23
})
syncWaterfallHook.tap('age', data => {
    console.log('age', data)
})

syncWaterfallHook.call('qiqingfu')

列印結果

name qiqingfu
age 23

SyncLoopHook

同步串列, 如果監聽函數的返回值為 true, 則反覆執行當前的監聽函數,直到返回指為 undefind則繼續執行下麵的監聽函數

class SyncLoopHook {
  constructor(options) {
    this.options = options
    this.hooks = []
    }
    tap(name, callback) {
    this.hooks.push(callback)
    }
    call(...args) {
        for (let i = 0; i < this.hooks.length; i++) {
            let hook = this.hooks[i], ret
            do{
                ret = hook(...args)
            }while(ret === true && ret !== undefined)
        }
    }
}

const syncLoopHook = new SyncLoopHook('name')

let n1 = 0
syncLoopHook.tap('name', data => {
    console.log('name', data)
    return n1 < 2 ? true : undefined
})
syncLoopHook.tap('end', data => {
    console.log('end', data)
})

syncLoopHook.call('qiqingfu')

執行結果

name qiqingfu
name qiqingfu
name qiqingfu  第三次列印的時候, n1的指為2, 返回值為 undefined則執行後面的監聽函數
end qiqingfu

非同步鉤子

  • 非同步並行 (Parallel)
    • AsyncParallelHook
    • AsyncParalleBailHook
  • 非同步串列 (Series)
    • AsyncSeriesHook
    • AsyncSeriesBailHook
    • AsyncSeriesWaterfallHook

凡有非同步,必有回調

同步鉤子是通過 tap來監聽函數的, call來發佈的。

非同步鉤子是通過 tapAsynctapPromise 來監聽函數,通過 callAsyncpromise來發佈訂閱的。

AsyncParallelHook

非同步並行, 監聽的函數會一塊執行, 哪個函數先執行完就先觸發。不需要關心監聽函數的返回值。

class AsyncParallelHook {
    constructor(options) {
        this.options = options
        this.asyncHooks = []
    }
    // 訂閱
    tapAsync(name, callback) {
        this.asyncHooks.push(callback)
    }
    // 發佈
    callAsync(...args) {
        /**
         * callAsync(arg1, arg2,..., cb)
         * 發佈的時候最後一個參數可以是回調函數
         * 訂閱的每一個函數的最後一個參數也是一個回調函數,所有的訂閱函數執行完
         * 且都調用了最後一個函數,才會執行cb 
         */
    const finalCallback = args.pop()
        let i = 0
        // 將這個作為最後一個參數傳過去,使用的時候選擇性調用
        const done = () => {
            ++i === this.asyncHooks.length && finalCallback()
        }
        this.asyncHooks.forEach(hook => {
            hook(...args, done)
        })
    }
}

const asyncParallelHook = new AsyncParallelHook('name')

asyncParallelHook.tapAsync('name', (data, done) => {
    setTimeout(() => {
    console.log('name', data)
    done()
  }, 2000)
})
asyncParallelHook.tapAsync('age', (data, done) => {
    setTimeout(() => {
    console.log('age', data)
    done()
  }, 3000)
})

console.time('time')
asyncParallelHook.callAsync('qiqingfu', () => {
  console.log('監聽函數都調用了 done')
  console.timeEnd('time')
})

列印結果

name qiqingfu
age qiqingfu
監聽函數都調用了 done
time: 3002.691ms

AsyncParalleBailHook

暫時不理解

AsyncSeriesHook

非同步串列鉤子類, 不關心 callback的參數。非同步函數一個一個的執行,但是必須調用 done函數。

class AsyncSeriesHook {
    constructor(options) {
        this.options = options
        this.asyncHooks = []
    }
    tapAsync(name, callback) {
        this.asyncHooks.push(callback)
    }
    callAsync(...args) {
        const finalCallback = args.pop()
        
        let i = 0
        const done = () => {
            let task = this.asyncHooks[i++]
            task ? task(...args, done) : finalCallback()
        }
        done()
    }
}

const asyncSeriesHook = new AsyncSeriesHook('name')

asyncSeriesHook.tapAsync('name', (data, done) => {
    setTimeout(() => {
        console.log('name', data)
        done()
    }, 1000)
})

asyncSeriesHook.tapAsync('age', (data, done) => {
    setTimeout(() => {
        console.log('age', data)
        done()
    }, 2000)
})

console.time('time')
asyncSeriesHook.callAsync('qiqingfu', () => {
    console.log('end')
    console.timeEnd('time')
})

執行結果

name qiqingfu
age qiqingfu
end
time: 3010.915ms

AsyncSeriesBailHook

同步串列鉤子類, callback的參數如果不是 null, 後面所有的非同步函數都不會執行,直接執行 callAsync方法的回調函數

class AsyncSeriesBailHook {
    constructor(options) {
        this.options = options
        this.asyncHooks = []
    }
    tapAsync(name, callback) {
        this.asyncHooks.push(callback)
    }
    callAsync(...args) {
        const finalCallback = args.pop()

        let i = 0
        const done = data => {
      if (data) return finalCallback()
      let task = this.asyncHooks[i++]
      task ? task(...args, done) : finalCallback()
        }
        done()
    }
}

const asyncSeriesBailHook = new AsyncSeriesBailHook('name')

asyncSeriesBailHook.tapAsync('1', (data, done) => {
    setTimeout(() => {
        console.log('1', data)
        done(null)
    }, 1000)
})

asyncSeriesBailHook.tapAsync('2', (data, done) => {
    setTimeout(() => {
        console.log('2', data)
        done(null)
    }, 2000)
})

console.time('times')
asyncSeriesBailHook.callAsync('qiqingfu', () => {
    console.log('end')
    console.timeEnd('times')
})

列印結果

1 qiqingfu
2 qiqingfu
end
times: 3012.060ms

AsyncSeriesWaterfallHook

同步串列鉤子類, 上一個監聽函數 callback(err, data)的第二個參數, 可以作為下一個監聽函數的參數

class AsyncSeriesWaterfallHook {
    constructor(options) {
        this.options = options
        this.asyncHooks = []
    }
    tapAsync(name, callback) {
        this.asyncHooks.push(callback)
    }
    callAsync(...args) {
        const finalCallback = args.pop()

        let i = 0, once
        const done = (err, data) => {
            let task = this.asyncHooks[i++]
            if (!task) return finalCallback()
            if (!once) {
                // 只執行一次
                task(...args, done)
                once = true
            } else {
                task(data, done)
            }
        }
        done()
    }
}

const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook('name')

asyncSeriesWaterfallHook.tapAsync('1', (data, done) => {
    setTimeout(() => {
        console.log('1', data)
        done(null, '第一個callback傳遞的參數')
    }, 1000)
})

asyncSeriesWaterfallHook.tapAsync('2', (data, done) => {
    setTimeout(() => {
        console.log('2', data)
        done(null)
    }, 1000)
})

console.time('timer')
asyncSeriesWaterfallHook.callAsync('qiqingfu', () => {
    console.log('end')
    console.timeEnd('timer')
})

列印結果

1 qiqingfu
2 第一個callback傳遞的參數
end
timer: 2015.445ms

END

如果理解有誤, 麻煩糾正!

參考文章

webpack4.0源碼分析之Tapable

webpack 4.0 Tapable 類中的常用鉤子函數源碼分析


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

-Advertisement-
Play Games
更多相關文章
  • Vue完成 TodoList 1.預設方式 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>TodoList</title> <script src="../../vue.js"></script> </he ...
  • 由使用request-promise-native想到的非同步處理方法 問題場景 因為js語言的特性,使用node開發程式的時候經常會遇到非同步處理的問題。對於之前專長App開發的我來說,會糾結node中實現客戶端API請求的“最佳實踐”。下麵以OAuth2.0為場景,需要處理的流程: 處理過程 一開始 ...
  • 封裝就是要具有靈活性,樣式自適應,調用的時候傳入props就可以變成自己想要的樣式。 效果展示網址:https://1963331542.github.io/ 源代碼: 1 <template> 2 <div :style="mainBoxStyle"> 3 <div :style="topLine ...
  • 修改|新增jquery-easyui icon圖標 by:授客 QQ:1033553122 測試環境 jquery-easyui-1.5.3 修改配置文件 打開jquery-easyui-1.5.3\themes\icon.css文件,文件頭部添加目標圖標樣式,例子: .icon{ backgrou ...
  • 修改jquery validatebox為英文校驗提示為中文提示 by:授客 QQ:1033553122 測試環境 jquery-easyui-1.5.3 問題描述: 如圖,想把校驗提示由英文改成中文 解決方案 編輯jquery.easyui.min.js 文件,搜索以下內容, missingMes ...
  • 1,打卡簽到 —— 500米範圍限制 a,getLocation 獲取gcj02 == 騰訊系坐標,可以直接用來打開騰訊地圖 (獲取wgs84則需轉換) b,百度坐標轉騰訊坐標,引入鏈接配置 <script charset="utf-8" src="http://map.qq.com/api/js? ...
  • 1.字體圖標 1.字體圖標都是用svg圖片 1.svg圖片不失真 2.svg圖標由設計師提供 3.為了減少網路請求,會把svg圖標轉換成字體圖標,放到字體文件中,通過字體庫的方式使用 1.svg圖片不失真 2.svg圖標由設計師提供 3.為了減少網路請求,會把svg圖標轉換成字體圖標,放到字體文件中 ...
  • javascript加密字元串,md5加密庫,sha1加密庫 下載鏈接: 鏈接:https://pan.baidu.com/s/10pkLUZAWr6_mRHEx51Z5Vg 提取碼:xz8e ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...