资讯专栏INFORMATION COLUMN

深入理解Webpack核心模块Tapable钩子[异步版]

AlphaWallet / 3523人阅读

摘要:接上一篇文章深入理解核心模块钩子同步版中三个注册方法同步注册的是中对三个触发方法这一章节我们将分别实现异步的版本和版本异步钩子的版本的版本的版本异步的钩子分为并行和串行的钩子,并行是指等待所有并发的异步事件执行之后再执行最终的异步回调。

接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版)

tapable中三个注册方法

1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promise)

tapable中对三个触发方法

1 call 2 callAsync 3 promise

这一章节 我们将分别实现异步的Async版本和Promise版本

</>复制代码

  1. 异步钩子

AsyncParallelHook

AsyncParallelHook的Promise版本

AsyncSeriesHook

AsyncSeriesHook的Promise版本

AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook的Promise版本

异步的钩子分为并行和串行的钩子,并行是指 等待所有并发的异步事件执行之后再执行最终的异步回调。
而串行是值 第一步执行完毕再去执行第二步,以此类推,直到执行完所有回调再去执行最终的异步回调。

</>复制代码

  1. AsyncParallelHook

AsyncParallelHook是异步并行的钩子,上代码:

</>复制代码

  1. const { AsyncParallelHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncParallelHook(["name"]);
  5. }
  6. tap(){
  7. /** 异步的注册方法是tapAsync()
  8. * 并且有回调函数cb.
  9. */
  10. this.hooks.tapAsync("node",function(name,cb){
  11. setTimeout(()=>{
  12. console.log("node",name);
  13. cb();
  14. },1000);
  15. });
  16. this.hooks.tapAsync("react",function(name,cb){
  17. setTimeout(()=>{
  18. console.log("react",name);
  19. cb();
  20. },1000);
  21. });
  22. }
  23. start(){
  24. /** 异步的触发方法是callAsync()
  25. * 多了一个最终的回调函数 fn.
  26. */
  27. this.hooks.callAsync("call end.",function(){
  28. console.log("最终的回调");
  29. });
  30. }
  31. }
  32. let h = new Hook();
  33. h.tap();/** 类似订阅 */
  34. h.start();/** 类似发布 */
  35. /* 打印顺序:
  36. node call end.
  37. react call end.
  38. 最终的回调
  39. */

等待1s后,分别执行了node call end和react callend 最后执行了最终的回调fn.

手动实现:

</>复制代码

  1. class AsyncParallelHook{
  2. constructor(args){ /* args -> ["name"]) */
  3. this.tasks = [];
  4. }
  5. /** tap接收两个参数 name和fn */
  6. tap(name,fn){
  7. /** 订阅:将fn放入到this.tasks中 */
  8. this.tasks.push(fn);
  9. }
  10. start(...args){
  11. let index = 0;
  12. /** 通过pop()获取到最后一个参数
  13. * finalCallBack() 最终的回调
  14. */
  15. let finalCallBack = args.pop();
  16. /** 箭头函数绑定this */
  17. let done = () => {
  18. /** 执行done() 每次index+1 */
  19. index++;
  20. if(index === this.tasks.length){
  21. /** 执行最终的回调 */
  22. finalCallBack();
  23. }
  24. }
  25. this.tasks.forEach((task)=>{
  26. /** 执行每个task,传入我们给定的done回调函数 */
  27. task(...args,done);
  28. });
  29. }
  30. }
  31. let h = new AsyncParallelHook(["name"]);
  32. /** 订阅 */
  33. h.tap("react",(name,cb)=>{
  34. setTimeout(()=>{
  35. console.log("react",name);
  36. cb();
  37. },1000);
  38. });
  39. h.tap("node",(name,cb)=>{
  40. setTimeout(()=>{
  41. console.log("node",name);
  42. cb();
  43. },1000);
  44. });
  45. /** 发布 */
  46. h.start("end.",function(){
  47. console.log("最终的回调函数");
  48. });
  49. /* 打印顺序:
  50. react end.
  51. node end.
  52. 最终的回调函数
  53. */

</>复制代码

  1. AsyncParallelHook的Promise版本

</>复制代码

  1. const { AsyncParallelHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncParallelHook(["name"]);
  5. }
  6. tap(){
  7. /** 这里是Promsie写法
  8. * 注册事件的方法为tapPromise
  9. */
  10. this.hooks.tapPromise("node",function(name){
  11. return new Promise((resolve,reject)=>{
  12. setTimeout(()=>{
  13. console.log("node",name);
  14. resolve();
  15. },1000);
  16. });
  17. });
  18. this.hooks.tapPromise("react",function(name){
  19. return new Promise((resolve,reject)=>{
  20. setTimeout(()=>{
  21. console.log("react",name);
  22. resolve();
  23. },1000);
  24. });
  25. });
  26. }
  27. start(){
  28. /**
  29. * promsie最终返回一个prosise 成功resolve时
  30. * .then即为最终回调
  31. */
  32. this.hooks.promise("call end.").then(function(){
  33. console.log("最终的回调");
  34. });
  35. }
  36. }
  37. let h = new Hook();
  38. h.tap();
  39. h.start();
  40. /* 打印顺序:
  41. node call end.
  42. react call end.
  43. 最终的回调
  44. */

这里钩子还是AsyncParallelHook钩子,只是写法变成了promise的写法,去掉了回调函数cb().变成了成功时去resolve().其实用Promise可以更好解决异步并行的问题,因为Promise的原型方法上有个all()方法,它的作用就是等待所有promise执行完毕后再去执行最终的promise。我们现在去实现它:

</>复制代码

  1. class SyncHook{
  2. constructor(args){
  3. this.tasks = [];
  4. }
  5. tapPromise(name,fn){
  6. this.tasks.push(fn);
  7. }
  8. promise(...args){
  9. /** 利用map方法返回一个新数组的特性 */
  10. let tasks = this.tasks.map((task)=>{
  11. /** 每一个task都是一个Promise */
  12. return task(...args);
  13. });
  14. /** Promise.all() 等待所有Promise都执行完毕 */
  15. return Promise.all(tasks);
  16. }
  17. }
  18. let h = new SyncHook(["name"]);
  19. /** 订阅 */
  20. h.tapPromise("react",(name)=>{
  21. return new Promise((resolve,reject)=>{
  22. setTimeout(()=>{
  23. console.log("react",name);
  24. resolve();
  25. },1000);
  26. });
  27. });
  28. h.tapPromise("node",(name)=>{
  29. return new Promise((resolve,reject)=>{
  30. setTimeout(()=>{
  31. console.log("node",name);
  32. resolve();
  33. },1000);
  34. });
  35. });
  36. /** 发布 */
  37. h.promise("end.").then(function(){
  38. console.log("最终的回调函数");
  39. });
  40. /* 打印顺序:
  41. react end.
  42. node end.
  43. 最终的回调函数
  44. */

</>复制代码

  1. AsyncSeriesHook

AsyncSeriesHook是异步串行的钩子, 串行,我们刚才说了, 它是一步步去执行的,下一步执行依赖上一步执行是否完成,手动实现:

</>复制代码

  1. const { AsyncSeriesHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncSeriesHook(["name"]);
  5. }
  6. tap(){
  7. /** 异步的注册方法是tapAsync()
  8. * 并且有回调函数cb.
  9. */
  10. this.hooks.tapAsync("node",function(name,cb){
  11. setTimeout(()=>{
  12. console.log("node",name);
  13. cb();
  14. },1000);
  15. });
  16. this.hooks.tapAsync("react",function(name,cb){
  17. /** 此回调要等待上一个回调执行完毕后才开始执行 */
  18. setTimeout(()=>{
  19. console.log("react",name);
  20. cb();
  21. },1000);
  22. });
  23. }
  24. start(){
  25. /** 异步的触发方法是callAsync()
  26. * 多了一个最终的回调函数 fn.
  27. */
  28. this.hooks.callAsync("call end.",function(){
  29. console.log("最终的回调");
  30. });
  31. }
  32. }
  33. let h = new Hook();
  34. h.tap();
  35. h.start();
  36. /* 打印顺序:
  37. node call end.
  38. react call end. -> 1s后打印
  39. 最终的回调 -> 1s后打印
  40. */

AsyncParallelHook和AsyncSeriesHook的区别是AsyncSeriesHook是串行的异步钩子,也就是说它会等待上一步的执行 只有上一步执行完毕了 才会开始执行下一步。而AsyncParallelHook是并行异步 AsyncParallelHook 是同时并发执行。 ok.手动实现 AsyncSeriesHook:

</>复制代码

  1. class AsyncParallelHook{
  2. constructor(args){ /* args -> ["name"]) */
  3. this.tasks = [];
  4. }
  5. /** tap接收两个参数 name和fn */
  6. tap(name,fn){
  7. /** 订阅:将fn放入到this.tasks中 */
  8. this.tasks.push(fn);
  9. }
  10. start(...args){
  11. let index = 0;
  12. let finalCallBack = args.pop();
  13. /** 递归执行next()方法 直到执行所有task
  14. * 最后执行最终的回调finalCallBack()
  15. */
  16. let next = () => {
  17. /** 直到执行完所有task后
  18. * 再执行最终的回调 finalCallBack()
  19. */
  20. if(index === this.tasks.length){
  21. return finalCallBack();
  22. }
  23. /** index++ 执行每一个task 并传入递归函数next
  24. * 执行完每个task后继续递归执行下一个task
  25. * next === cb,next就是每一步的cb回调
  26. */
  27. this.tasks[index++](...args,next);
  28. }
  29. /** 执行next() */
  30. next();
  31. }
  32. }
  33. let h = new AsyncParallelHook(["name"]);
  34. /** 订阅 */
  35. h.tap("react",(name,cb)=>{
  36. setTimeout(()=>{
  37. console.log("react",name);
  38. cb();
  39. },1000);
  40. });
  41. h.tap("node",(name,cb)=>{
  42. setTimeout(()=>{
  43. console.log("node",name);
  44. cb();
  45. },1000);
  46. });
  47. /** 发布 */
  48. h.start("end.",function(){
  49. console.log("最终的回调函数");
  50. });
  51. /* 打印顺序:
  52. react end.
  53. node end. -> 1s后打印
  54. 最终的回调函数 -> 1s后打印
  55. */

</>复制代码

  1. AsyncSeriesHook的Promise版本

</>复制代码

  1. const { AsyncSeriesHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncSeriesHook(["name"]);
  5. }
  6. tap(){
  7. /** 这里是Promsie写法
  8. * 注册事件的方法为tapPromise
  9. */
  10. this.hooks.tapPromise("node",function(name){
  11. return new Promise((resolve,reject)=>{
  12. setTimeout(()=>{
  13. console.log("node",name);
  14. resolve();
  15. },1000);
  16. });
  17. });
  18. this.hooks.tapPromise("react",function(name){
  19. /** 等待上一步 执行完毕之后 再执行 */
  20. return new Promise((resolve,reject)=>{
  21. setTimeout(()=>{
  22. console.log("react",name);
  23. resolve();
  24. },1000);
  25. });
  26. });
  27. }
  28. start(){
  29. /**
  30. * promsie最终返回一个prosise 成功resolve时
  31. * .then即为最终回调
  32. */
  33. this.hooks.promise("call end.").then(function(){
  34. console.log("最终的回调");
  35. });
  36. }
  37. }
  38. let h = new Hook();
  39. h.tap();
  40. h.start();
  41. /* 打印顺序:
  42. node call end.
  43. react call end. -> 1s后打印
  44. 最终的回调 -> 1s后打印
  45. */

手动实现AsyncSeriesHook的Promise版本

</>复制代码

  1. class AsyncSeriesHook{
  2. constructor(args){
  3. this.tasks = [];
  4. }
  5. tapPromise(name,fn){
  6. this.tasks.push(fn);
  7. }
  8. promise(...args){
  9. /** 1 解构 拿到第一个first
  10. * first是一个promise
  11. */
  12. let [first, ...others] = this.tasks;
  13. /** 4 利用reduce方法 累计执行
  14. * 它最终返回的是一个Promsie
  15. */
  16. return others.reduce((l,n)=>{
  17. /** 1 下一步的执行依赖上一步的then */
  18. return l.then(()=>{
  19. /** 2 下一步执行依赖上一步结果 */
  20. return n(...args);
  21. });
  22. },first(...args));
  23. }
  24. }
  25. let h = new AsyncSeriesHook(["name"]);
  26. /** 订阅 */
  27. h.tapPromise("react",(name)=>{
  28. return new Promise((resolve,reject)=>{
  29. setTimeout(()=>{
  30. console.log("react",name);
  31. resolve();
  32. },1000);
  33. });
  34. });
  35. h.tapPromise("node",(name)=>{
  36. return new Promise((resolve,reject)=>{
  37. setTimeout(()=>{
  38. console.log("node",name);
  39. resolve();
  40. },1000);
  41. });
  42. });
  43. /** 发布 */
  44. h.promise("end.").then(function(){
  45. console.log("最终的回调函数");
  46. });
  47. /* 打印顺序:
  48. react end.
  49. node end. -> 1s后打印
  50. 最终的回调函数 -> 1s后打印
  51. */

最后一个AsyncSeriesWaterfallHook:

</>复制代码

  1. AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook 异步的串行的瀑布钩子,首先 它是一个异步串行的钩子,同时 它的下一步依赖上一步的结果返回:

</>复制代码

  1. const { AsyncSeriesWaterfallHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncSeriesWaterfallHook(["name"]);
  5. }
  6. tap(){
  7. this.hooks.tapAsync("node",function(name,cb){
  8. setTimeout(()=>{
  9. console.log("node",name);
  10. /** 第一次参数是err, 第二个参数是传递给下一步的参数 */
  11. cb(null,"第一步返回第二步的结果");
  12. },1000);
  13. });
  14. this.hooks.tapAsync("react",function(data,cb){
  15. /** 此回调要等待上一个回调执行完毕后才开始执行
  16. * 并且 data 是上一步return的结果.
  17. */
  18. setTimeout(()=>{
  19. console.log("react",data);
  20. cb();
  21. },1000);
  22. });
  23. }
  24. start(){
  25. this.hooks.callAsync("call end.",function(){
  26. console.log("最终的回调");
  27. });
  28. }
  29. }
  30. let h = new Hook();
  31. h.tap();
  32. h.start();
  33. /* 打印顺序:
  34. node call end.
  35. react 第一步返回第二步的结果
  36. 最终的回调
  37. */

我们可以看到 第二步依赖了第一步返回的值, 并且它也是串行的钩子,实现它:

</>复制代码

  1. class AsyncParallelHook{
  2. constructor(args){ /* args -> ["name"]) */
  3. this.tasks = [];
  4. }
  5. /** tap接收两个参数 name和fn */
  6. tap(name,fn){
  7. /** 订阅:将fn放入到this.tasks中 */
  8. this.tasks.push(fn);
  9. }
  10. start(...args){
  11. let index = 0;
  12. /** 1 拿到最后的最终的回调 */
  13. let finalCallBack = args.pop();
  14. let next = (err,data) => {
  15. /** 拿到每个task */
  16. let task = this.tasks[index];
  17. /** 2 如果没传task 或者全部task都执行完毕
  18. * return 直接执行最终的回调finalCallBack()
  19. */
  20. if(!task) return finalCallBack();
  21. if(index === 0){
  22. /** 3 执行第一个task
  23. * 并传递参数为原始参数args
  24. */
  25. task(...args, next);
  26. }else{
  27. /** 4 执行处第二个外的每个task
  28. * 并传递的参数 data
  29. * data ->‘传递给下一步的结果’
  30. */
  31. task(data, next);
  32. }
  33. index++;
  34. }
  35. /** 执行next() */
  36. next();
  37. }
  38. }
  39. let h = new AsyncParallelHook(["name"]);
  40. /** 订阅 */
  41. h.tap("react",(name,cb)=>{
  42. setTimeout(()=>{
  43. console.log("react",name);
  44. cb(null,"传递给下一步的结果");
  45. },1000);
  46. });
  47. h.tap("node",(name,cb)=>{
  48. setTimeout(()=>{
  49. console.log("node",name);
  50. cb();
  51. },1000);
  52. });
  53. /** 发布 */
  54. h.start("end.",function(){
  55. console.log("最终的回调函数");
  56. });
  57. /* 打印顺序:
  58. react end.
  59. node 传递给下一步的结果
  60. 最终的回调函数
  61. */

</>复制代码

  1. AsyncSeriesWaterfallHook的Promise版本

</>复制代码

  1. const { AsyncSeriesWaterfallHook } = require("tapable");
  2. class Hook{
  3. constructor(){
  4. this.hooks = new AsyncSeriesWaterfallHook(["name"]);
  5. }
  6. tap(){
  7. this.hooks.tapPromise("node",function(name){
  8. return new Promise((resolve,reject)=>{
  9. setTimeout(()=>{
  10. console.log("node",name);
  11. /** 在resolve中把结果传给下一步 */
  12. resolve("返回给下一步的结果");
  13. },1000);
  14. });
  15. });
  16. this.hooks.tapPromise("react",function(name){
  17. return new Promise((resolve,reject)=>{
  18. setTimeout(()=>{
  19. console.log("react",name);
  20. resolve();
  21. },1000);
  22. });
  23. });
  24. }
  25. start(){
  26. this.hooks.promise("call end.").then(function(){
  27. console.log("最终的回调");
  28. });
  29. }
  30. }
  31. let h = new Hook();
  32. h.tap();
  33. h.start();
  34. /* 打印顺序:
  35. node call end.
  36. react 返回给下一步的结果
  37. 最终的回调
  38. */

用Promsie实现很简单,手动实现它吧:

</>复制代码

  1. class AsyncSeriesHook{
  2. constructor(args){
  3. this.tasks = [];
  4. }
  5. tapPromise(name,fn){
  6. this.tasks.push(fn);
  7. }
  8. promise(...args){
  9. /** 1 解构 拿到第一个first
  10. * first是一个promise
  11. */
  12. let [first, ...others] = this.tasks;
  13. /** 2 利用reduce方法 累计执行
  14. * 它最终返回的是一个Promsie
  15. */
  16. return others.reduce((l,n)=>{
  17. return l.then((data)=>{
  18. /** 3 将data传给下一个task 即可 */
  19. return n(data);
  20. });
  21. },first(...args));
  22. }
  23. }
  24. let h = new AsyncSeriesHook(["name"]);
  25. /** 订阅 */
  26. h.tapPromise("react",(name)=>{
  27. return new Promise((resolve,reject)=>{
  28. setTimeout(()=>{
  29. console.log("react",name);
  30. resolve("promise-传递给下一步的结果");
  31. },1000);
  32. });
  33. });
  34. h.tapPromise("node",(name)=>{
  35. return new Promise((resolve,reject)=>{
  36. setTimeout(()=>{
  37. console.log("node",name);
  38. resolve();
  39. },1000);
  40. });
  41. });
  42. /** 发布 */
  43. h.promise("end.").then(function(){
  44. console.log("最终的回调函数");
  45. });
  46. /* 打印顺序:
  47. react end.
  48. node promise-传递给下一步的结果
  49. 最终的回调函数
  50. */

ok.至此,我们把tapable的钩子全部解析并手动实现完毕。写文章不易,喜欢的话给个赞或者start~
代码在github上:mock-webpack-tapable

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102161.html

相关文章

  • 深入理解Webpack核心模块Tapable钩子[同步]

    摘要:本文将根据以下章节分别梳理每个钩子同步钩子首先安装是简单的同步钩子,它很类似于发布订阅。至此,我们把的所有同步钩子都解析完毕异步钩子比同步钩子麻烦些,我们会在下一章节开始解析异步的钩子传送门深入理解核心模块钩子异步版代码 记录下自己在前端路上爬坑的经历 加深印象,正文开始~ tapable是webpack的核心依赖库 想要读懂webpack源码 就必须首先熟悉tapableok.下面是...

    cangck_X 评论0 收藏0
  • 手写一个webpack插件

    摘要:引入定义一个自己的插件。一个最基础的的代码是这样的在构造函数中获取用户给该插件传入的配置会调用实例的方法给插件实例传入对象导出在使用这个时,相关配置代码如下和在开发时最常用的两个对象就是和,它们是和之间的桥梁。 本文示例源代码请戳github博客,建议大家动手敲敲代码。 webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,w...

    cnio 评论0 收藏0
  • webpack系列-插件机制杂记

    摘要:系列文章系列第一篇基础杂记系列第二篇插件机制杂记系列第三篇流程杂记前言本身并不难,他所完成的各种复杂炫酷的功能都依赖于他的插件机制。的插件机制依赖于一个核心的库,。是什么是一个类似于的的库主要是控制钩子函数的发布与订阅。 系列文章 Webpack系列-第一篇基础杂记 Webpack系列-第二篇插件机制杂记 Webpack系列-第三篇流程杂记 前言 webpack本身并不难,他所完成...

    Neilyo 评论0 收藏0
  • webpack详解

    摘要:但也是最复杂的一个。中当一旦某个返回值结果不为便结束执行列表中的插件中上一个插件执行结果当作下一个插件的入参调用并行执行插件流程篇本文关于的流程讲解是基于的。 webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢? webpack的配置 const path = requir...

    lcodecorex 评论0 收藏0
  • 浅尝webpack

    摘要:用于对模块的源代码进行转换。将基础模块打包进动态链接库,当依赖的模块存在于动态链接库中时,无需再次打包,而是直接从动态链接库中获取。负责打包出动态链接库,负责从主要配置文件中引入插件打包好的动态链接库文件。告一段落,浅尝辄止。 吐槽一下 webpack 自出现时,一直备受青睐。作为强大的打包工具,它只是出现在项目初始或优化的阶段。如果没有参与项目的构建,接触的机会几乎为零。即使是参与了...

    villainhr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<