内容简介:为什么我们要学tapable,因为....webpack源码里面都是用的tapable来实现钩子挂载的,作为一个有点追求的code,webpack怎么能只满足于用呢?当然是要去看源码,写loader,plugin啦.在这之前,要是不清楚tapable的用法,源码那是更不用看了,看不懂.....所以,今天来讲一下tapable吧webpack本质上是一种事件流的机制,他的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建的bun
为什么我们要学tapable,因为....webpack源码里面都是用的tapable来实现钩子挂载的,作为一个有点追求的code,webpack怎么能只满足于用呢?当然是要去看源码,写loader,plugin啦.在这之前,要是不清楚tapable的用法,源码那是更不用看了,看不懂.....所以,今天来讲一下tapable吧
1. tapable
webpack本质上是一种事件流的机制,他的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建的bundles的Compilation都是Tapable的实例
tapable创建实例时传递的参数对于程序运行并没有任何作用,只是给源码阅读者提供帮助
同样的,在使用tap*注册监听时,传递的第一个参数,也只是一个标识,并不会在程序运行中产生任何影响。而第二个参数则是回调函数
2.tapable的用法
const { SyncHook, SyncBailHook, SyncWaterHook, SyncLoopHook AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable"); 复制代码
序号 | 钩子名称 | 执行方式 | 使用要点 |
---|---|---|---|
1 | SyncHook | 同步串行 | 不关心监听函数的返回值 |
2 | SyncBailHook | 同步串行 | 只要监听函数中有一个函数的返回值不为null,则跳过剩余逻辑 |
3 | SyncWaterfallHook | 同步串行 | 上一个监听函数的返回值将作为参数传递给下一个监听函数 |
4 | SyncLoopHook | 同步串行 | 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环 |
5 | AsyncParallelHook | 异步并行 | 不关心监听函数的返回值 |
6 | AsyncParallelBailHook | 异步并行 | 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数 |
7 | AsyncSeriesHook | 异步串行 | 不关心callback()的参数 |
8 | AsyncSeriesBailHook | 异步串行 | callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数 |
9 | AsyncSeriesWaterfallHook | 异步串行 | 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数 |
3. Sync*类型的钩子
- 注册在该钩子下面的插件的执行顺序都是顺序执行
- 只能使用tap注册,不能使用tapPromise和tapAsync注册
3.1 SyncHook
串行同步执行,不关心返回值 在SyncHook的实例上注册了tap之后,只要实例调用了call方法,那么这些tap的回掉函数一定会顺序执行一遍
let queue = new SyncHook(['没任何作用的参数']); queue.tap(1,(name,age)=>{ console.log(name,age) }) queue.tap(2,(name,age)=>{ console.log(name,age) }) queue.tap(3,(name,age)=>{ console.log(name,age) }) queue.call('bearbao',8) // 输出结果 // 'bearbao' 8 // 'bearbao' 8 // 'bearbao' 8 复制代码
3.1.1 SyncHook实现
class SyncHook { constructor(){ this.listeners = []; } tap(formal,listener){ this.listeners.push(listener) } call(...args){ this.listeners.forEach(l=>l(...args)) } } 复制代码
3.2 SyncBailHook
串行同步执行,有一个返回值不为null则跳过剩下的逻辑
let queue = new SyncBailHook(['name']) queue.tap(1,name=>{ console.log(name) }) queue.tap(1,name=>{ console.log(name) return '1' }) queue.tap(1,name=>{ console.log(name) }) queue.call('bearbao') // 输出结果,只执行前面两个回调,第三个不执行 // bearbao // bearbao 复制代码
实现
class SyncBailHook { constructor(){ this.listeners = []; } tap(formal,listener){ this.listeners.push(listener) } call(...args){ for(let i=0;i<this.listeners.length;i++){ if(this.listeners[i]()) break; } } } 复制代码
3.3 SyncWaterHook
串行同步执行,第一个注册的回调函数会接收call传进来的所有参数,之后的每个回调函数只接收到一个参数,就是上一个回调函数的返回值.
let queue = new SyncWaterHook(['name','age']); queue.tap(1,(name,age)=>{ console.log(name,age) return 1 }) queue.tap(2,(ret)=>{ console.log(ret) return 2 }) queue.tap(3,(ret)=>{ console.log(ret) return 3 }) queue.call('bearbao', 3) // 输出结果 // bearbao 3 // 1 // 2 复制代码
SyncWaterHook 实现. SyncWaterHook这个方法很像redux中的compose方法,都是将一个函数的返回值作为参数传递给下一个函数.
对下面实现的call方法如果有疑惑,看不大懂的同学可以移步我之前对于compose函数的解读,里面有详细的介绍,这里就不多加赘述了
class SyncWaterHook{ constructor(){ this.listeners = []; } tap(formal,listener){ this.listener.push(listener); } call(...args){ this.listeners.reduce((a,b)=>(...args)=>a(b(...args))) } } 复制代码
3.4 SyncLoopHook
串行同步执行, 监听函数返回true表示继续循环,返回undefined表示循环结束
let queue = new SyncLoopHook; let index = 0; queue.tap(1,_=>{ index++ if(index<3){ console.log(index); return true } }) queue.call(); // 输出结果 // 1 // 2 复制代码
SyncLoopHook实现
class SyncLoopHook{ constructor() { this.tasks=[]; } tap(name,task) { this.tasks.push(task); } call(...args) { this.tasks.forEach(task => { let ret=true; do { ret = task(...args); }while(ret) }); } } 复制代码
4. Async*类型的钩子
- 支持tap、tapPromise、tapAsync注册
- 每次都是调用tap、tapSync、tapPromise注册不同类型的插件钩子,通过调用call、callAsync 、promise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。
4.1 AsyncParallel
异步并行执行
4.1.1 AsyncParallelHook
不关心监听函数的返回值.
有三种注册/发布的模式,如下
异步订阅 | 调用方法 |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
- 通过tap来使用
触发函数的参数,出了最后一个参数是异步监听回调函数执行完成之后的回调,其他的参数都是传递给回调函数的参数
let queue = new AsyncParallelHook(['name']); console.time('cost'); queue.tap('1',function(name){ console.log(name,1); }); queue.tap('2',function(name){ console.log(name,2); }); queue.tap('3',function(name){ console.log(name,3); }); queue.callAsync('bearbao',err=>{ console.log(err); console.timeEnd('cost'); }); // 执行结果 /* bearbao 1 bearbao 2 bearbao 3 cost: 4.720ms */ 复制代码
实现
class AsyncParallelHook { constructor(){ this.listeners = []; } tap(name,listener){ this.listeners.push(listener); } callAsync(){ this.listeners.forEach(listener=>listener(...arguments)); Array.from(arguments).pop()(); } } 复制代码
- 通过tapAsync来注册
注意,这里有个特殊的地方,如何确认某个回调执行完了呢?,每个监听回调的最后一个参数是一个回调函数,当执行callback之后,会认为当前函数执行完毕
let queue = new AsyncParallelHook(['name']); console.time('cost'); queue.tapAsync('1',function(name,callback){ setTimeout(function(){ console.log(name, 1); callback(); },1000) }); queue.tapAsync('2',function(name,callback){ setTimeout(function(){ console.log(name, 2); callback(); },2000) }); queue.tapAsync('3',function(name,callback){ setTimeout(function(){ console.log(name, 3); callback(); },3000) }); queue.callAsync('bearbao',err=>{ console.log(err); console.timeEnd('cost'); }); // 输出结果 /* bearbao 1 bearbao 2 bearbao 3 cost: 3000.448974609375ms */ 复制代码
实现
class AsyncParallelHook { constructor(){ this.listeners = []; } tapAsync(name,listener){ this.listeners.push(listener); } callAsync(...arg){ let callback = arg.pop(); let i = 0; let done = ()=>{ if(++i==this.listeners.length){ callback() } } this.listeners.forEach(listener=>listener(...arg,done)); } } 复制代码
- 使用tapPromise
使用tapPromise注册监听时,每个回调函数的返回值必须是一个Promise的实例
let queue = new AsyncParallelHook(['name']); console.time('cost'); queue.tapPromise('1',function(name){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(1); resolve(); },1000) }); }); queue.tapPromise('2',function(name){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(2); resolve(); },2000) }); }); queue.tapPromise('3',function(name){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(3); resolve(); },3000) }); }); queue.promise('bearbao').then(()=>{ console.timeEnd('cost'); }) // 执行记过 /* 1 2 3 cost: 3000.448974609375ms */ 复制代码
实现
class AsyncParallelHook { constructor(){ this.listeners = []; } tapPromise(name,listener){ this.listeners.push(listener); } promise(...arg){ let i = 0; return Promise.all(this.listeners.map(l=>l(arg))) } } 复制代码
5. 好困好困
一不小心又到1点了,为了能够获得长寿成就,今天就先写到这里吧,后续几个方法,过两天再更新上来
结语
如果觉得还可以,能在诸君的编码之路上带来一点帮助,请点赞鼓励一下,谢谢!
以上所述就是小编给大家介绍的《webpack怎么能只是会用呢,核心中的核心tapable了解下?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ppk on JavaScript, 1/e
Peter-Paul Koch / New Riders Press / 2006-09-20 / USD 44.99
Whether you're an old-school scripter who needs to modernize your JavaScripting skills or a standards-aware Web developer who needs best practices and code examples, you'll welcome this guide from a J......一起来看看 《ppk on JavaScript, 1/e》 这本书的介绍吧!