Webpack 源码(一)—— Tapable 和 事宜流

1、Tapable

Tap 的英文单词诠释,除了最经常运用的 点击 手势以外,另有一个意义是 水龙头 —— 在 webpack 中指的是后一种;

Webpack 能够认为是一种基于事宜流的编程范例,内部的事情流程都是基于 插件 机制串接起来;

而将这些插件粘合起来的就是webpack本身写的基础类 Tapable 是,plugin要领就是该类暴露出来的;

背面我们将看到中心的对象 Compiler、Compilation 等都是继承于该对象

基于该类范例而其的 Webpack 体系保证了插件的有序性,使得全部体系异常有弹性,扩展性很好;但是有一个致命的瑕玷就是调试、看源码真是很痛楚,种种跳来跳去;(基于事宜流的写法,和程序语言中的 goto 语句很类似)

把这个堆栈下载,运用 Webstorm 举行调试,test 目次是很好的教程进口;

Tapable.plugin():相称于把对象归类到名为 name 的对象下,以array的情势;一切的插件都存在私有变量 _plugin 变量中;

《Webpack 源码(一)—— Tapable 和 事宜流》

接下来我们简朴节选几个函数剖析一下:

1.1、apply 要领

该要领最一般也是最经常运用的,看一下它的定义:


Tapable.prototype.apply = function apply() {
    for(var i = 0; i < arguments.length; i++) {
        arguments[i].apply(this);
    }
};

毫无牵挂,就是 挨个递次 实行传入到该函数要领中对象的 apply 要领;一般传入该函数的对象也是 Tapable 插件 对象,因而必定也存在 apply 要领;(Webpack 的插件就是Tapable对象,因而必需要供应 apply 要领 )

只是变动上下文为当前 this

因而当前这里最大的作用就是传入当前 Tapable 的上下文

1.2、 applyPluginsAsync(name,…other,callback)

// 模仿两个插件
var _plugins = {
    "emit":[
        function(a,b,cb){
            setTimeout(()=>{
              console.log('1',a,b);
              cb();
            },1000);
        },
        function(a,b,cb){
            setTimeout(()=>{
                console.log('2',a,b);
                cb();
            },500)
        }
    ]
}

applyPluginsAsync("emit",'aaaa','bbbbb',function(){console.log('end')});

// 输出效果:

// 1 aaaa bbbbb
// 2 aaaa bbbbb
//  end

我们看到,虽然第一个插件是延后 1000ms 实行,第二个则是延后 500ms,但在真正实行的时刻,是严厉根据递次实行的;每一个插件须要在末了显式挪用cb()关照下一个插件的运转;

这里须要注重每一个插件的形参的个数都要一致,且末了一个必需是cb()要领,用于唤起下一个插件的运转;cb的第一个参数是err,假如该参数不为空,就直接挪用末了callback,中缀后续插件的运转;

1.3、 applyPluginsParallel(name,…other,callback)

大部分代码和
applyPluginsAsync 有点儿类似

这个 applyPluginsParallel 主要功用和 最简朴的 applyPlugins 要领比较类似,无论怎样都邑让一切注册的插件运转一遍

只是比拟 applyPlugins 多了一个分外的功用,它末了 供应一个 callback 函数,这个 callback 的函数比较顽强,假如一切的插件x都一般实行,且末了都cb(),则会在末了实行callback里的逻辑;不过,一旦个中某个插件运转失足,就会挪用这个callback(err),以后就算插件有毛病也不会再挪用该callback函数;


var _plugins = {
"emit":[
    function(a,b,cb){
        setTimeout(()=>{
          console.log('1',a,b);
          cb(null,'e222','33333');
        },1000);
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('2',a,b);
            cb(null,'err');
        },500)
    }
]
}

applyPluginsParallel("emit",'aaaa','bbbbb',function(a,b){console.log('end',a,b)});

// 输出效果:

// 2 aaaa bbbbb
// 1 aaaa bbbbb
//  end undefined undefined

上面的两个插件都是挪用了 cb,且第一个参数是 null(示意没有毛病),所以末了能输出 callback 函数中的 console 内容;

假如解释两个插件中任何一个 cb() 挪用,你会发明末了的 callback 没有实行

假如让 第二个 cb()的第一个值不是 null,比方 cb(‘err’),则 callback 以后输出这个毛病,以后再也不会挪用此 callback:

var _plugins = {
"emit":[
    function(a,b,cb){
        setTimeout(()=>{
          console.log('1',a,b);
          cb('e222','33333');
        },1000);
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('2',a,b);
            cb('err');
        },500)
    }
]
}

// 输出效果:

// 2 aaaa bbbbb
// end err undefined
// 1 aaaa bbbbb

1.4、 applyPluginsWaterfall(name, init, callback)

望文生义,这个要领相称因而 瀑布式 挪用,给第一个插件传入初始对象 init,然后经由第一个插件挪用以后会取得一个效果对象,该效果对象会传给下一个插件 作为初始值,直到末了挪用终了,末了一个插件的直接效果传给 callback 作为初始值;

1.5、 applyPluginsParallelBailResult(name,…other,callback)

这个要领应该是一切要领中最难明白的;

起首它的行动和 applyPluginsParallel 异常类似,起首会 无论怎样都邑让一切注册的插件运转一遍(根据注册的递次)

为了让 callback 实行,其前提条件是每一个插件都须要挪用 cb();

但个中的 callback 只会实行一次(当传给cb的值不是undefined/null 的时刻),这一次实行递次是插件定义递次有关,而跟每一个插件中的 cb() 实行时间无关的


var _plugins = {
"emit":[
    function(a,b,cb){
        setTimeout(()=>{
          console.log('1',a,b);
          cb();
        },1000);
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('2',a,b);
            cb();
        },500)
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('3',a,b);
            cb();
        },1500)
    }
]
}

applyPluginsParallelBailResult("emit",'aaaa','bbbbb',function(a,b){console.log('end',a,b)});

// 运转效果

// 2 aaaa bbbbb
// 1 aaaa bbbbb
// 3 aaaa bbbbb
// end undefined undefined

这是最一般的运转状况,我们轻微调解一下(注重三个插件运转的递次2-1-3),分别给cb传入有用的值:


var _plugins = {
"emit":[
    function(a,b,cb){
        setTimeout(()=>{
          console.log('1',a,b);
          cb('1');
        },1000);
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('2',a,b);
            cb('2');
        },500)
    },
    function(a,b,cb){
        setTimeout(()=>{
            console.log('3',a,b);
            cb('3');
        },1500)
    }
]
}
applyPluginsParallelBailResult("emit",'aaaa','bbbbb',function(a,b){console.log('end',a,b)});
// 运转效果

// 2 aaaa bbbbb
// 1 aaaa bbbbb
// end 1 undefined
// 3 aaaa bbbbb

能够发明第1个插件 cb('1') 实行了,后续的 cb('2')cb('3') 都给疏忽了;

这是由于插件注册递次是 1-2-3,虽然运转的时刻递次是 2-1-3,但所运转的照样 1 对应的 cb;所以,就算1实行的速率最慢(比方把其setTimeout的值设置成 2000),运转的 cb 仍然是1对应的cb;

个中触及的魔法是
闭包,传入的
i就是和注册递次绑定了

如许一申明,你会发明 applyPluginsParallel 的 cb 实行机遇是和实行时间有关联的,你能够本身考证一下;

1.6、总结

总结一下,Tapable 就相称因而一个 事宜管家,它所供应的 plugin 要领类似于 addEventListen 监听事宜,apply 要领类似于事宜触发函数 trigger

《Webpack 源码(一)—— Tapable 和 事宜流》

2、Webpack 中的事宜流

既然 Webpack 是基于 Tapable 搭建起来的,那末我们看一下 Webpack 构建一个模块的基础事宜流是怎样的;

我们在 Webpack 库中的 Tapable.js 中每一个要领中新增 console 语句打出日记,就可以找出一切症结的事宜名字:

《Webpack 源码(一)—— Tapable 和 事宜流》

打印效果:(这里只列举了简朴的事宜流程,打包差别的进口文件会有所差别,但 事宜涌现的先后递次是牢固的

范例名字事宜名
[C]applyPluginsBailResultentry-option
[A]applyPluginsafter-plugins
[A]applyPluginsafter-resolvers
[A]applyPluginsenvironment
[A]applyPluginsafter-environment
[D]applyPluginsAsyncSeriesrun
[A]applyPluginsnormal-module-factory
[A]applyPluginscontext-module-factory
[A]applyPluginscompile
[A]applyPluginsthis-compilation
[A]applyPluginscompilation
[F]applyPluginsParallelmake
[E]applyPluginsAsyncWaterfallbefore-resolve
[B]applyPluginsWaterfallfactory
[B]applyPluginsWaterfallresolver
[A]applyPluginsresolve
[A]applyPluginsresolve-step
[G]applyPluginsParallelBailResultfile
[G]applyPluginsParallelBailResultdirectory
[A]applyPluginsresolve-step
[G]applyPluginsParallelBailResultresult
[E]applyPluginsAsyncWaterfallafter-resolve
[C]applyPluginsBailResultcreate-module
[B]applyPluginsWaterfallmodule
[A]applyPluginsbuild-module
[A]applyPluginsnormal-module-loader
[C]applyPluginsBailResultprogram
[C]applyPluginsBailResultstatement
[C]applyPluginsBailResultevaluate CallExpression
[C]applyPluginsBailResultvar data
[C]applyPluginsBailResultevaluate Identifier
[C]applyPluginsBailResultevaluate Identifier require
[C]applyPluginsBailResultcall require
[C]applyPluginsBailResultevaluate Literal
[C]applyPluginsBailResultcall require:amd:array
[C]applyPluginsBailResultevaluate Literal
[C]applyPluginsBailResultcall require:commonjs:item
[C]applyPluginsBailResultstatement
[C]applyPluginsBailResultevaluate MemberExpression
[C]applyPluginsBailResultevaluate Identifier console.log
[C]applyPluginsBailResultcall console.log
[C]applyPluginsBailResultexpression console.log
[C]applyPluginsBailResultexpression console
[A]applyPluginssucceed-module
[E]applyPluginsAsyncWaterfallbefore-resolve
[B]applyPluginsWaterfallfactory
[A]applyPluginsbuild-module
[A]applyPluginssucceed-module
[A]applyPluginsseal
[A]applyPluginsoptimize
[A]applyPluginsoptimize-modules
[A]applyPluginsafter-optimize-modules
[A]applyPluginsoptimize-chunks
[A]applyPluginsafter-optimize-chunks
[D]applyPluginsAsyncSeriesoptimize-tree
[A]applyPluginsafter-optimize-tree
[C]applyPluginsBailResultshould-record
[A]applyPluginsrevive-modules
[A]applyPluginsoptimize-module-order
[A]applyPluginsbefore-module-ids
[A]applyPluginsoptimize-module-ids
[A]applyPluginsafter-optimize-module-ids
[A]applyPluginsrecord-modules
[A]applyPluginsrevive-chunks
[A]applyPluginsoptimize-chunk-order
[A]applyPluginsbefore-chunk-ids
[A]applyPluginsoptimize-chunk-ids
[A]applyPluginsafter-optimize-chunk-ids
[A]applyPluginsrecord-chunks
[A]applyPluginsbefore-hash
[A]applyPluginshash
[A]applyPluginshash
[A]applyPluginshash
[A]applyPluginshash
[A]applyPluginshash-for-chunk
[A]applyPluginschunk-hash
[A]applyPluginsafter-hash
[A]applyPluginsbefore-chunk-assets
[B]applyPluginsWaterfallglobal-hash-paths
[C]applyPluginsBailResultglobal-hash
[B]applyPluginsWaterfallbootstrap
[B]applyPluginsWaterfalllocal-vars
[B]applyPluginsWaterfallrequire
[B]applyPluginsWaterfallmodule-obj
[B]applyPluginsWaterfallmodule-require
[B]applyPluginsWaterfallrequire-extensions
[B]applyPluginsWaterfallasset-path
[B]applyPluginsWaterfallstartup
[B]applyPluginsWaterfallmodule-require
[B]applyPluginsWaterfallrender
[B]applyPluginsWaterfallmodule
[B]applyPluginsWaterfallrender
[B]applyPluginsWaterfallpackage
[B]applyPluginsWaterfallmodule
[B]applyPluginsWaterfallrender
[B]applyPluginsWaterfallpackage
[B]applyPluginsWaterfallmodules
[B]applyPluginsWaterfallrender-with-entry
[B]applyPluginsWaterfallasset-path
[B]applyPluginsWaterfallasset-path
[A]applyPluginschunk-asset
[A]applyPluginsadditional-chunk-assets
[A]applyPluginsrecord
[D]applyPluginsAsyncSeriesadditional-assets
[D]applyPluginsAsyncSeriesoptimize-chunk-assets
[A]applyPluginsafter-optimize-chunk-assets
[D]applyPluginsAsyncSeriesoptimize-assets
[A]applyPluginsafter-optimize-assets
[D]applyPluginsAsyncSeriesafter-compile
[C]applyPluginsBailResultshould-emit
[D]applyPluginsAsyncSeriesemit
[B]applyPluginsWaterfallasset-path
[D]applyPluginsAsyncSeriesafter-emit
[A]applyPluginsdone

内容较多,根据源码内容的编排,能够将上述举行分层;大粒度的事宜流以下:

《Webpack 源码(一)—— Tapable 和 事宜流》

而个中 makesealemit 阶段比较中心(包含了许多小粒度的事宜),后续会继承睁开解说;

这里排列一下症结的事宜节点:

  • entry-option:初始化options
  • run:最先编译
  • make:从entry最先递归的剖析依靠,对每一个依靠模块举行build
  • before-resolve - after-resolve: 对个中一个模块位置举行剖析
  • build-module :最先构建 (build) 这个module,这里将运用文件对应的loader加载
  • normal-module-loader:对用loader加载完成的module(是一段js代码)举行编译,用 acorn 编译,天生ast笼统语法树。
  • program: 最先对ast举行遍历,当碰到require等一些挪用表达式时,触发 call require 事宜的handler实行,网络依靠,并。如:AMDRequireDependenciesBlockParserPlugin等
  • seal: 一切依靠build完成,下面将最先对chunk举行优化,比方兼并,抽取大众模块,加hash
  • optimize-chunk-assets:紧缩代码,插件 UglifyJsPlugin 就放在这个阶段
  • bootstrap: 天生启动代码
  • emit: 把各个chunk输出到效果文件

3、参考文章

本系列的源码浏览,以下几篇文章给了许多启示和思绪,个中 webpack 源码剖析细说 webpack 之流程篇 尤其凸起,引荐浏览;

下面的是我的民众号二维码图片,迎接关注。
《Webpack 源码(一)—— Tapable 和 事宜流》

    原文作者:JSCON简时空
    原文地址: https://segmentfault.com/a/1190000008060440
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞