zoukankan      html  css  js  c++  java
  • .8-浅析webpack源码之Tapable介绍

    Tapable工具

      

      完成webpack默认参数注入后,下一步虽然是 new Compiler() ,但是这东西不是一下可以讲完的,复杂的一批。

      不如先从工具入手,分块讲解compiler,首先来看看事件流执行器Tapable工具。

      tips:这里的Tapable源码来自于webpack内部自带的tapable,如果通过npm i tapable查看会发现完全不一样。

      出现地点如下:

    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);
    };

      非常简单,用一个小案例说明:

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

      通过mixin方法的调用,sourObj会变成:

      至于另外一个工具函数,单独讲没有任何意义,所以在用到的时候再做分析。

      

      接下来分析原型函数,其中有两个函数是基本操作函数,其余的都是用不同方式执行指定名字的事件流。

      先看基本的。

     基本操作函数

    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个比较复杂,干讲也不知道怎么解释,等到后面的代码有用到的时候再组具体分析。

      

  • 相关阅读:
    【json的处理】一、Gson处理
    【校验处理】三、Spring Validation 校验处理
    【校验处理】二、SpringBoot Validate 统一处理
    【校验处理】一、Java Bean Validation验证
    div 固定宽高 水平垂直居中方法
    vue 修改ElementUI样式(非全局修改)
    js --- execCommand('copy')复制文本到剪切板换行符不生效
    C#枚举(一)使用总结以及扩展类分享
    php执行时长
    关于PHP的编码格式导致的乱码
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8036962.html
Copyright © 2011-2022 走看看