一文先容 Tapable 的特性和使用,助力明确 webpack 插件架构! ...

打印 上一主题 下一主题

主题 517|帖子 517|积分 1551

一、引言

众所周知,webpack 的 plugins 非常机动,可以在编译的差别阶段注册事件回调,这个功能便是基于 Tapable 实现的。
Tapable 的使用步骤如下:

  • 创建钩子实例,如 SyncHook、SyncLoopHook 钩子;
  • 调用订阅接口注册事件回调,包括 tap、tapAsync、tapPromise;
  • 触发回调,包括 call、callAsync、promise。
Tapable 的钩子分为同步钩子和异步钩子,同步钩子都以 Sync 开头,通过 tap 注册回调,使用 call 触发回调。异步钩子则以 Async 开头,通过 tapAsync 和 callAsync,或者 tapPromise 和 promise 来注册和触发回调。
接下来便是各个钩子的先容和用法。
二、同步钩子

SyncHook

SyncHook 是一个基础的钩子。通过该钩子注册的事件会依次实行。
  1. const { SyncHook } = require('tapable')
  2. // 实例化钩子函数,可以在这里定义形参
  3. const syncHook = new SyncHook(['author', 'age'])
  4. syncHook.intercept({
  5.   // 每次调用 hook 实例的 tap() 方法注册回调函数时, 都会调用该方法,
  6.   // 并且接受 tap 作为参数, 还可以对 tap 进行修改;
  7.   register: (tapInfo) => {
  8.     console.log(`${tapInfo.name} is doing its job`)
  9.     return tapInfo // may return a new tapInfo object
  10.   },
  11.   // 通过hook实例对象上的call方法时候触发拦截器
  12.   call: (arg1, arg2, arg3) => {
  13.     console.log('Starting to calculate routes')
  14.   },
  15.   // 在调用被注册的每一个事件回调之前执行
  16.   tap: (tap) => {
  17.     console.log(tap, 'tap')
  18.   },
  19.   // loop类型钩子中 每个事件回调被调用前触发该拦截器方法
  20.   loop: (...args) => {
  21.     console.log(args, 'loop')
  22.   },
  23. })
  24. // 注册事件
  25. syncHook.tap('监听器1', (name, age) => {
  26.   console.log('监听器1:', name, age)
  27. })
  28. syncHook.tap('监听器2', (name) => {
  29.   console.log('监听器2:', name)
  30. })
  31. syncHook.tap('监听器3', (name, age) => {
  32.   console.log('监听器3:', name, age)
  33. })
  34. // 触发事件,这里传的是实参,会被每一个注册的函数接收到
  35. syncHook.call('wade', '25')
  36. // 执行结果
  37. /**
  38. 监听器1 is doing its job
  39. 监听器2 is doing its job
  40. 监听器3 is doing its job
  41. Starting to calculate routes
  42. { type: 'sync', fn: [Function (anonymous)], name: '监听器1' } tap
  43. 监听器1: wade 25
  44. { type: 'sync', fn: [Function (anonymous)], name: '监听器2' } tap
  45. 监听器2: wade
  46. { type: 'sync', fn: [Function (anonymous)], name: '监听器3' } tap
  47. 监听器3: wade 25
  48. */
复制代码
SyncBailHook

SyncBailHook 是一个具有熔断风格的钩子。只要其中一个事件回调有返回值,后面的事件回调就不实行了。
  1. const { SyncBailHook } = require('tapable')
  2. const hook = new SyncBailHook(['author', 'age'])
  3. hook.tap('测试1', (name, age) => {
  4.   console.log('测试1接收参数:', name, age)
  5. })
  6. hook.tap('测试2', (name, age) => {
  7.   console.log('测试2接收参数:', name, age)
  8.   return 'outer'
  9. })
  10. hook.tap('测试3', (name, age) => {
  11.   console.log('测试3接收参数:', name, age)
  12. })
  13. hook.call('wade', '25')
  14. // 执行结果
  15. /**
  16. 测试1接收参数: wade 25
  17. 测试2接收参数: wade 25
  18. */
复制代码
第二个事件返回了 outer,因此第三个事件不会触发。
SyncLoopHook

SyncLoopHook 是一个循环范例的钩子。循环范例的含义是不绝的循环实行事件回调,直到所有函数结果 result === undefined,不符合条件就返回从第一个事件回调开始实行。
  1. const { SyncLoopHook } = require('tapable')
  2. const hook = new SyncLoopHook([])
  3. let count = 5
  4. hook.tap('测试1', () => {
  5.   console.log('测试1里面的count:', count)
  6.   if ([1, 2, 3].includes(count)) {
  7.     return undefined
  8.   } else {
  9.     count--
  10.     return '123'
  11.   }
  12. })
  13. hook.tap('测试2', () => {
  14.   console.log('测试2里面的count:', count)
  15.   if ([1, 2].includes(count)) {
  16.     return undefined
  17.   } else {
  18.     count--
  19.     return '123'
  20.   }
  21. })
  22. hook.tap('测试3', () => {
  23.   console.log('测试3里面的count:', count)
  24.   if ([1].includes(count)) {
  25.     return undefined
  26.   } else {
  27.     count--
  28.     return '123'
  29.   }
  30. })
  31. //通过call方法触发事件
  32. hook.call()
  33. // 执行结果
  34. /**
  35. 测试1里面的count: 5
  36. 测试1里面的count: 4
  37. 测试1里面的count: 3
  38. 测试2里面的count: 3
  39. 测试1里面的count: 2
  40. 测试2里面的count: 2
  41. 测试3里面的count: 2
  42. 测试1里面的count: 1
  43. 测试2里面的count: 1
  44. 测试3里面的count: 1
  45. */
复制代码
SyncWaterfallHook

SyncWaterfallHook 是一个瀑布式范例的钩子。瀑布范例的钩子就是如果前一个事件回调的结果 result !== undefined,则 result 会作为后一个事件回调的第一个参数(也就是上一个函数的实行结果会成为下一个函数的参数)。
  1. const { SyncWaterfallHook } = require('tapable')
  2. const hook = new SyncWaterfallHook(['author', 'age'])
  3. hook.tap('测试1', (name, age) => {
  4.   console.log('测试1接收参数:', name, age)
  5. })
  6. hook.tap('测试2', (name, age) => {
  7.   console.log('测试2接收参数:', name, age)
  8.   return '另外的名字'
  9. })
  10. hook.tap('测试3', (name, age) => {
  11.   console.log('测试3接收参数:', name, age)
  12. })
  13. hook.call('wade', '25')
  14. // 执行结果
  15. /**
  16. 测试1接收参数: wade 25
  17. 测试2接收参数: wade 25
  18. 测试3接收参数: 另外的名字 25
  19. */
复制代码
三、异步钩子

异步钩子需要通过 tapAsync 函数注册事件,同时也会多一个 callback 参数,实行 callback 告诉 该注册事件已经实行完成。callback 函数可以传递两个参数** err** 和 result,一旦 err 不为空,则后续的事件回调都不再实行。异步钩子触发事件需要使用 callAsync
AsyncSeriesHook

AsyncSeriesHook 是一个串行的钩子。
  1. const { AsyncSeriesHook } = require('tapable')
  2. const hook = new AsyncSeriesHook(['author', 'age']) // 先实例化,并定义回调函数的形参
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   console.log('测试1接收的参数:', param1, param2)
  6.   setTimeout(() => {
  7.     callback()
  8.   }, 1000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   setTimeout(() => {
  13.     callback()
  14.   }, 2000)
  15. })
  16. hook.tapAsync('测试3', (param1, param2, callback) => {
  17.   console.log('测试3接收的参数:', param1, param2)
  18.   setTimeout(() => {
  19.     callback()
  20.   }, 3000)
  21. })
  22. hook.callAsync('wade', '25', (err, result) => {
  23.   // 等全部都完成了才会走到这里来
  24.   console.log('这是成功后的回调', err, result)
  25.   console.timeEnd('time')
  26. })
  27. // 执行结果
  28. /**
  29. 测试1接收的参数: wade 25
  30. 测试2接收的参数: wade 25
  31. 测试3接收的参数: wade 25
  32. 这是成功后的回调 undefined undefined
  33. time: 6.032
  34. */
复制代码
AsyncSeriesBailHook

AsyncSeriesBailHook 是一个串行、熔断范例的钩子。一旦 callback 函数传递了参数,则不再实行后续的事件回调。
  1. const { AsyncSeriesBailHook } = require('tapable')
  2. const hook = new AsyncSeriesBailHook(['author', 'age'])
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   console.log('测试1接收的参数:', param1, param2)
  6.   setTimeout(() => {
  7.     callback()
  8.   }, 1000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   setTimeout(() => {
  13.     callback(false, '123')
  14.   }, 2000)
  15. })
  16. hook.tapAsync('测试3', (param1, param2, callback) => {
  17.   console.log('测试3接收的参数:', param1, param2)
  18.   setTimeout(() => {
  19.     callback()
  20.   }, 3000)
  21. })
  22. hook.callAsync('wade', '25', (err, result) => {
  23.   // 等全部都完成了才会走到这里来
  24.   console.log('这是成功后的回调', err, result)
  25.   console.timeEnd('time')
  26. })
  27. // 执行结果
  28. /**
  29. 测试1接收的参数: wade 25
  30. 测试2接收的参数: wade 25
  31. 这是成功后的回调 null 123
  32. time: 3.013s
  33. */
复制代码
AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook 是一个串行、瀑布式范例的钩子。如果前一个事件回调的 callback 的参数 result !== undefined,则 result 会作为后一个事件回调的第一个参数(也就是上一个函数的实行结果会成为下一个函数的参数)。
  1. const { AsyncSeriesHook } = require('tapable')
  2. const hook = new AsyncSeriesHook(['author', 'age'])
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   console.log('测试1接收的参数:', param1, param2)
  6.   setTimeout(() => {
  7.     callback()
  8.   }, 1000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   setTimeout(() => {
  13.     callback(null, 2441)
  14.   }, 2000)
  15. })
  16. hook.tapAsync('测试3', (param1, param2, callback) => {
  17.   console.log('测试3接收的参数:', param1, param2)
  18.   setTimeout(() => {
  19.     callback()
  20.   }, 3000)
  21. })
  22. hook.callAsync('wade', '25', (err, result) => {
  23.   // 等全部都完成了才会走到这里来
  24.   console.log('这是成功后的回调', err, result)
  25.   console.timeEnd('time')
  26. })
  27. // 执行结果
  28. /**
  29. 测试1接收的参数: wade 25
  30. 测试2接收的参数: 新名字A 25
  31. 测试3接收的参数: 新名字A 25
  32. 这是成功后的回调 null 新名字C
  33. time: 6.043s
  34. */
复制代码
AsyncParallelHook

AsyncParallelHook 是一个并行的钩子。通过这个钩子注册的回调不需要等候上一个事件回调实行竣事便可以实行下一个事件回调。
  1. const { AsyncParallelHook } = require('tapable')
  2. const hook = new AsyncParallelHook(['author', 'age'])
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   setTimeout(() => {
  6.     console.log('测试1接收的参数:', param1, param2)
  7.     callback()
  8.   }, 2000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   callback()
  13. })
  14. hook.tapAsync('测试3', (param1, param2, callback) => {
  15.   console.log('测试3接收的参数:', param1, param2)
  16.   callback()
  17. })
  18. // call 方法只有同步钩子才有,异步钩子得使用 callAsync
  19. hook.callAsync('wade', '25', (err, result) => {
  20.   // 等全部都完成了才会走到这里来
  21.   console.log('这是成功后的回调', err, result)
  22.   console.timeEnd('time')
  23. })
  24. // 执行结果
  25. /**
  26. 测试2接收的参数: wade 25
  27. 测试3接收的参数: wade 25
  28. 测试1接收的参数: wade 25
  29. 这是成功后的回调 undefined undefined
  30. time: 2.015s
  31. */
复制代码
第一个事件设置了 2 秒的定时器再实行,此时会先实行后续的事件回调,比及 2 秒后再实行第一个事件回调。
AsyncParallelBailHook

AsyncParallelBailHook 是一个串行、熔断范例的钩子。若当前的事件回调的 callback 的参数 result !== undefined,则后续的事件都不再实行。
  1. const { AsyncParallelBailHook } = require('tapable')
  2. const hook = new AsyncParallelBailHook(['author', 'age'])
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   console.log('测试1接收的参数:', param1, param2)
  6.   setTimeout(() => {
  7.     callback()
  8.   }, 1000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   callback(null, '测试2有返回值啦')
  13. })
  14. hook.tapAsync('测试3', (param1, param2, callback) => {
  15.   console.log('测试3接收的参数:', param1, param2)
  16.   setTimeout(() => {
  17.     callback(null, '测试3有返回值啦')
  18.   }, 3000)
  19. })
  20. hook.callAsync('wade', '25', (err, result) => {
  21.   // 等全部都完成了才会走到这里来
  22.   console.log('这是成功后的回调', err, result)
  23.   console.timeEnd('time')
  24. })
  25. // 执行结果
  26. /**
  27. 测试1接收的参数: wade 25
  28. 测试2接收的参数: wade 25
  29. 这是成功后的回调 null 测试2有返回值啦
  30. time: 3.015s
  31. */
复制代码
由于第二个事件回调有返回值,因此第三个事件回调不会实行。若第一个事件回调也有返回值,尽管是第二个事件回调先实行完成,但是 callAsync 拿到的结果依然是第一个事件回调的返回值,示比方下:
  1. const { AsyncParallelBailHook } = require('tapable')
  2. const hook = new AsyncParallelBailHook(['author', 'age'])
  3. console.time('time')
  4. hook.tapAsync('测试1', (param1, param2, callback) => {
  5.   console.log('测试1接收的参数:', param1, param2)
  6.   setTimeout(() => {
  7.     callback(null, '测试1有返回值啦')
  8.   }, 3000)
  9. })
  10. hook.tapAsync('测试2', (param1, param2, callback) => {
  11.   console.log('测试2接收的参数:', param1, param2)
  12.   callback(null, '测试2有返回值啦')
  13. })
  14. hook.tapAsync('测试3', (param1, param2, callback) => {
  15.   console.log('测试3接收的参数:', param1, param2)
  16.   setTimeout(() => {
  17.     callback(null, '测试3有返回值啦')
  18.   }, 1000)
  19. })
  20. hook.callAsync('wade', '25', (err, result) => {
  21.   // 等全部都完成了才会走到这里来
  22.   console.log('这是成功后的回调', err, result)
  23.   console.timeEnd('time')
  24. })
  25. // 执行结果
  26. /**
  27. 测试1接收的参数: wade 25
  28. 测试2接收的参数: wade 25
  29. 这是成功后的回调 null 测试1有返回值啦
  30. time: 3.009s
  31. */
复制代码
四、总结

Tapable 提供的这些钩子,支持同步、异步、熔断、循环、waterfall 等功能特性,以此支持起 webpack 复杂的编译功能,在明确这些内容之后,我们对 webpack 插件架构的设计会有进一步的明确和使用。
参考资料

https://juejin.cn/post/6955421936373465118
https://github.com/webpack/tapable

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我爱普洱茶

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表