.8-淺析webpack源碼之Tapable介紹

Tapable工具

  

  完成webpack默認參數注入後,下一步雖然是 new Compiler() ,可是這東西不是一下能夠講完的,複雜的一批。webpack

  不如先從工具入手,分塊講解compiler,首先來看看事件流執行器Tapable工具。web

  tips:這裏的Tapable源碼來自於webpack內部自帶的tapable,若是經過npm i tapable查看會發現徹底不同。npm

  出現地點以下:app

class Compiler extends Tapable {
    // ...
}
class Compilation extends Tapable {
    // ...  
}

  能夠看到核心對象基本上都繼承於該工具,用於處理事件流,Tapable源碼整理以下(用ES6的class複寫了一遍,看起來比較清晰):異步

// 原型方法混入
Tapable.mixin = function mixinTapable(pt) { /**/ };

function copyProperties(from, to) { /**/ }

// 服務於某些apply
function fastFilter(fun /*, thisArg*/ ) { /*...*/ }

class Tapable {
    constructor() {
        this._plugins = {};
    }
    plugin(name, fn) { /*...*/ }
    hasPlugins(name) { /*...*/ }
    apply() { /*...*/ }
    applyPlugins(name) { /*...*/ }
    applyPlugins0(name) { /*...*/ }
    applyPlugins1(name, param) { /*...*/ }
    applyPlugins2(name, param1, param2) { /*...*/ }
    applyPluginsWaterfall(name, init) { /*...*/ }
    applyPluginsWaterfall0(name, init) { /*...*/ }
    applyPluginsWaterfall1(name, init, param) { /*...*/ }
    applyPluginsWaterfall2(name, init, param1, param2) { /*...*/ }
    applyPluginsBailResult(name) { /*...*/ }
    applyPluginsBailResult1(name, param) { /*...*/ }
    applyPluginsBailResult2(name, param1, param2) { /*...*/ }
    applyPluginsBailResult3(name, param1, param2, param3) { /*...*/ }
    applyPluginsBailResult4(name, param1, param2, param3, param4) { /*...*/ }
    applyPluginsBailResult5(name, param1, param2, param3, param4, param5) { /*...*/ }
    applyPluginsAsyncSeries(name) { /*...*/ }
    applyPluginsAsyncSeries1(name, param, callback) { /*...*/ }
    applyPluginsAsyncSeriesBailResult(name) { /*...*/ }
    applyPluginsAsyncSeriesBailResult1(name, param, callback) { /*...*/ }
    applyPluginsAsyncWaterfall(name, init, callback) { /*...*/ }
    applyPluginsParallel(name) { /*...*/ }
    applyPluginsParallelBailResult(name) { /*...*/ }
    applyPluginsParallelBailResult1(name, param, callback) { /*...*/ }
}

module.exports = Tapable;

  構造函數只是簡單的聲明瞭一個_plugins對象,外部函數包括有一個混入函數、一個工具函數,原型上則是大量apply...函數

  先從簡單的入手,看看混入函數:工具

// 將Tapable原型方法複製到指定對象中
function copyProperties(from, to) {
    for (var key in from)
        to[key] = from[key];
    return to;
}
// 傳入對象
Tapable.mixin = function mixinTapable(pt) {
    copyProperties(Tapable.prototype, pt);
};

  很是簡單,用一個小案例說明:ui

const Tapable = require('./Tapable');
var sourObj = { ownKey: null };
Tapable.mixin(sourObj);

  經過mixin方法的調用,sourObj會變成:this

  至於另一個工具函數,單獨講沒有任何意義,因此在用到的時候再作分析。spa

  

  接下來分析原型函數,其中有兩個函數是基本操做函數,其他的都是用不一樣方式執行指定名字的事件流。

  先看基本的。

 

 基本操做函數

plugin

Tapable.prototype.plugin = function plugin(name, fn) {
    // 將函數注入多個事件流中
    if (Array.isArray(name)) {
        name.forEach(function(name) {
            this.plugin(name, fn);
        }, this);
        return;
    }
    // 若是不存在該事件流 新建並將函數插入
    if (!this._plugins[name]) this._plugins[name] = [fn];
    // 存在就添加執行函數
    else this._plugins[name].push(fn);
};

  這是Tapable最基本的操做,給指定的事件流注入新函數。

hasPlugins

Tapable.prototype.hasPlugins = function hasPlugins(name) {
    // 嘗試獲取對應事件流
    var plugins = this._plugins[name];
    // 存在事件流且有可執行函數
    return plugins && plugins.length > 0;
};

  has判斷,沒啥好講的。

 

事件流執行

  接下來看看全部的事件流執行方式。(源碼中儘可能使用ES6進行改寫以加強可讀性,留個註釋在那)

  首先是一個比較特殊的原型函數:

apply

Tapable.prototype.apply = function apply(...fns) {
    // 遍歷全部參數並執行
    for (var i = 0; i < fns.length; i++) {
        fns[i].apply(this);
    }
};

  該函數並不直接關聯於_plugins對象,而是按照參數傳入順序依次執行。

 

applyPlugins

  這個方式很是簡單暴力,依次遍歷指定name的事件流,不一樣名字的函數可接受參數數量不同。

// 不接受傳參
Tapable.prototype.applyPlugins0 = function applyPlugins0(name) {
    var plugins = this._plugins[name];
    if (!plugins) return;
    for (var i = 0; i < plugins.length; i++)
        plugins[i].call(this);
};
// 接受一個參數
Tapable.prototype.applyPlugins1 = function applyPlugins1(name, param) {
    var plugins = this._plugins[name];
    if (!plugins) return;
    for (var i = 0; i < plugins.length; i++)
        plugins[i].call(this, param);
};
// 接受兩個參數
Tapable.prototype.applyPlugins2 = function applyPlugins2(name, param1, param2) {
    var plugins = this._plugins[name];
    if (!plugins) return;
    for (var i = 0; i < plugins.length; i++)
        plugins[i].call(this, param1, param2);
};
// 接受任意數量參數
Tapable.prototype.applyPlugins = function applyPlugins(name, ...args) {
    if (!this._plugins[name]) return;
    // var args = Array.prototype.slice.call(arguments, 1);
    var plugins = this._plugins[name];
    for (var i = 0; i < plugins.length; i++)
        plugins[i].apply(this, args);
};

  語義化滿分,0表明不接受參數,1表明1個...而s表明任意數量的參數。

 

applyPluginsWaterfall

  這種方式的特色是:事件流執行過程當中,每一次執行的返回值會做爲下一次的參數(僅限於第一個參數)。

Tapable.prototype.applyPluginsWaterfall0 = function applyPluginsWaterfall0(name, init) {
    var plugins = this._plugins[name];
    if (!plugins) return init;
    var current = init;
    for (var i = 0; i < plugins.length; i++)
        current = plugins[i].call(this, current);
    return current;
};

// ...1
// ...2

Tapable.prototype.applyPluginsWaterfall = function applyPluginsWaterfall(name, init, ...args) {
    if (!this._plugins[name]) return init;
    // var args = Array.prototype.slice.call(arguments, 1);
    var plugins = this._plugins[name];
    var current = init;
    for (var i = 0; i < plugins.length; i++) {
        current = plugins[i].call(this, current, ...args);
    }
    return current;
};

 

applyPluginsBailResult

  這種方式的特色是:事件流執行過程當中,返回第一個不是undefined的值,後續函數不執行。

Tapable.prototype.applyPluginsBailResult = function applyPluginsBailResult(name, ...args) {
    if (!this._plugins[name]) return;
    // var args = Array.prototype.slice.call(arguments, 1);
    var plugins = this._plugins[name];
    for (var i = 0; i < plugins.length; i++) {
        var result = plugins[i].apply(this, args);
        if (typeof result !== "undefined") {
            return result;
        }
    }
};

// 1,2,3,4,5

 

applyPluginsAsync...

  帶有Async的均爲異步調用方式,特色是事件流會在回調中依次進行,區別主要在於回調函數的參數處理,具體的使用方式還須要在實際應用中來看。

Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsyncSeries(name, ...args) {
    // var args = Array.prototype.slice.call(arguments, 1);
    // 最後一個參數爲回調函數 其他爲普通參數
    var callback = args.pop();
    var plugins = this._plugins[name];
    if (!plugins || plugins.length === 0) return callback();
    var i = 0;
    // var _this = this;
    // 包裝
    args.push(copyProperties(callback, (err) => {
        if (err) return callback(err);
        i++;
        if (i >= plugins.length) {
            return callback();
        }
        plugins[i].apply(this, args);
    }));
    // 內部繼續使用此方式可依次執行事件流
    plugins[0].apply(this, args);
};

// ..1

// applyPluginsAsyncSeriesBailResult => 回調函數傳了參數就直接執行回調並返回終止事件流

// ..1

// applyPluginsAsyncWaterfall => 回調函數每次取給定的參數

  剩下的3個比較複雜,幹講也不知道怎麼解釋,等到後面的代碼有用到的時候再組具體分析。

相關文章
相關標籤/搜索