1、Tapable
Tap 的英文单词诠释,除了最经常运用的 点击 手势以外,另有一个意义是 水龙头 —— 在 webpack 中指的是后一种;
Webpack 能够认为是一种基于事宜流的编程范例,内部的事情流程都是基于 插件 机制串接起来;
而将这些插件粘合起来的就是webpack本身写的基础类 Tapable 是,plugin
要领就是该类暴露出来的;
背面我们将看到中心的对象 Compiler、Compilation 等都是继承于该对象
基于该类范例而其的 Webpack 体系保证了插件的有序性,使得全部体系异常有弹性,扩展性很好;但是有一个致命的瑕玷就是调试、看源码真是很痛楚,种种跳来跳去;(基于事宜流的写法,和程序语言中的 goto 语句很类似)
把这个堆栈下载,运用 Webstorm 举行调试,test 目次是很好的教程进口;
Tapable.plugin():相称于把对象归类到名为 name 的对象下,以array的情势;一切的插件都存在私有变量 _plugin 变量中;
接下来我们简朴节选几个函数剖析一下:
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
;
2、Webpack 中的事宜流
既然 Webpack 是基于 Tapable 搭建起来的,那末我们看一下 Webpack 构建一个模块的基础事宜流是怎样的;
我们在 Webpack 库中的 Tapable.js 中每一个要领中新增 console
语句打出日记,就可以找出一切症结的事宜名字:
打印效果:(这里只列举了简朴的事宜流程,打包差别的进口文件会有所差别,但 事宜涌现的先后递次是牢固的 )
范例 | 名字 | 事宜名 |
---|---|---|
[C] | applyPluginsBailResult | entry-option |
[A] | applyPlugins | after-plugins |
[A] | applyPlugins | after-resolvers |
[A] | applyPlugins | environment |
[A] | applyPlugins | after-environment |
[D] | applyPluginsAsyncSeries | run |
[A] | applyPlugins | normal-module-factory |
[A] | applyPlugins | context-module-factory |
[A] | applyPlugins | compile |
[A] | applyPlugins | this-compilation |
[A] | applyPlugins | compilation |
[F] | applyPluginsParallel | make |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[B] | applyPluginsWaterfall | resolver |
[A] | applyPlugins | resolve |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | file |
[G] | applyPluginsParallelBailResult | directory |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | result |
[E] | applyPluginsAsyncWaterfall | after-resolve |
[C] | applyPluginsBailResult | create-module |
[B] | applyPluginsWaterfall | module |
[A] | applyPlugins | build-module |
[A] | applyPlugins | normal-module-loader |
[C] | applyPluginsBailResult | program |
[C] | applyPluginsBailResult | statement |
[C] | applyPluginsBailResult | evaluate CallExpression |
[C] | applyPluginsBailResult | var data |
[C] | applyPluginsBailResult | evaluate Identifier |
[C] | applyPluginsBailResult | evaluate Identifier require |
[C] | applyPluginsBailResult | call require |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:amd:array |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:commonjs:item |
[C] | applyPluginsBailResult | statement |
[C] | applyPluginsBailResult | evaluate MemberExpression |
[C] | applyPluginsBailResult | evaluate Identifier console.log |
[C] | applyPluginsBailResult | call console.log |
[C] | applyPluginsBailResult | expression console.log |
[C] | applyPluginsBailResult | expression console |
[A] | applyPlugins | succeed-module |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[A] | applyPlugins | build-module |
[A] | applyPlugins | succeed-module |
[A] | applyPlugins | seal |
[A] | applyPlugins | optimize |
[A] | applyPlugins | optimize-modules |
[A] | applyPlugins | after-optimize-modules |
[A] | applyPlugins | optimize-chunks |
[A] | applyPlugins | after-optimize-chunks |
[D] | applyPluginsAsyncSeries | optimize-tree |
[A] | applyPlugins | after-optimize-tree |
[C] | applyPluginsBailResult | should-record |
[A] | applyPlugins | revive-modules |
[A] | applyPlugins | optimize-module-order |
[A] | applyPlugins | before-module-ids |
[A] | applyPlugins | optimize-module-ids |
[A] | applyPlugins | after-optimize-module-ids |
[A] | applyPlugins | record-modules |
[A] | applyPlugins | revive-chunks |
[A] | applyPlugins | optimize-chunk-order |
[A] | applyPlugins | before-chunk-ids |
[A] | applyPlugins | optimize-chunk-ids |
[A] | applyPlugins | after-optimize-chunk-ids |
[A] | applyPlugins | record-chunks |
[A] | applyPlugins | before-hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash-for-chunk |
[A] | applyPlugins | chunk-hash |
[A] | applyPlugins | after-hash |
[A] | applyPlugins | before-chunk-assets |
[B] | applyPluginsWaterfall | global-hash-paths |
[C] | applyPluginsBailResult | global-hash |
[B] | applyPluginsWaterfall | bootstrap |
[B] | applyPluginsWaterfall | local-vars |
[B] | applyPluginsWaterfall | require |
[B] | applyPluginsWaterfall | module-obj |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | require-extensions |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | startup |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | package |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | package |
[B] | applyPluginsWaterfall | modules |
[B] | applyPluginsWaterfall | render-with-entry |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | asset-path |
[A] | applyPlugins | chunk-asset |
[A] | applyPlugins | additional-chunk-assets |
[A] | applyPlugins | record |
[D] | applyPluginsAsyncSeries | additional-assets |
[D] | applyPluginsAsyncSeries | optimize-chunk-assets |
[A] | applyPlugins | after-optimize-chunk-assets |
[D] | applyPluginsAsyncSeries | optimize-assets |
[A] | applyPlugins | after-optimize-assets |
[D] | applyPluginsAsyncSeries | after-compile |
[C] | applyPluginsBailResult | should-emit |
[D] | applyPluginsAsyncSeries | emit |
[B] | applyPluginsWaterfall | asset-path |
[D] | applyPluginsAsyncSeries | after-emit |
[A] | applyPlugins | done |
内容较多,根据源码内容的编排,能够将上述举行分层;大粒度的事宜流以下:
而个中 make、 seal 和 emit 阶段比较中心(包含了许多小粒度的事宜),后续会继承睁开解说;
这里排列一下症结的事宜节点:
-
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 之流程篇 尤其凸起,引荐浏览;
下面的是我的民众号二维码图片,迎接关注。