webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具, webpack 处理应用程序时会在内部构建一个依赖图, 此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle
plugin 是 webpack 生态的关键部分, 采用基于事件流的机制, 将各个插件串联起来完成相关的功能, compiler 模块是 webpack 的主要引擎, 它扩展(extend)自 Tapable 类, 用来注册和调用插件.
Tapable 是一个用于事件发布订阅执行的插件架构, 类似于 Node.js 的 EventEmitter 库.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const { SyncHook , SyncBailHook , SyncWaterfallHook , SyncLoopHook , AsyncParallelHook , AsyncParallelBailHook , AsyncSeriesHook , AsyncSeriesBailHook , AsyncSeriesWaterfallHook , AsyncSeriesLoopHook , HookMap , MultiHook , } = require ('tapable' );
钩子分类 参数 所有 Hook 构造函数都接收一个由参数名称为字符串组成的列表作为可选参数
执行方式 Basic Hook (基础) 钩子调用所在行中调用的每个钩子函数
WaterFall (瀑布) 与基础钩子不同, 它将一个返回值从每个函数传递到下一个函数
Bail (保证) 钩子函数执行中, 只要其中有一个钩子返回 非 undefined
时, 则剩余的钩子函数不再执行
Loop (循环) 循环执行钩子, 当循环钩子函数返回 非 undefined
时, 则从第一个钩子重新启动, 直到所有的钩子返回 undefined
时结束
定义类型 Sync 同步 同步钩子只能使用 tap
注册钩子, 使用 call
| callAsync
调用钩子
1 2 3 myHooks.tap ('x' , () => {}); myHooks.call ('x' ); myHooks.callAsync ('x' , () => {});
AsyncSeries 异步串行 异步串行钩子可以使用 tap
| tapAsync
| tapPromise
注册钩子, 使用 call
| callAsync
| promise
调用钩子
1 2 3 4 myHooks.tapAsync ('x' , (callback ) => { callback (); }); myHooks.callAsync ('x' , () => {});
AsyncParallel 异步并行 异步并行钩子可以使用 tap
| tapAsync
| tapPromise
注册钩子, 使用 call
| callAsync
| promise
调用钩子
1 2 3 4 5 6 myHooks.tapPromise ('x' , () => { return new Promise ((resolve, reject ) => { resolve (); }); }); myHooks.promise ('x' ).then ((res ) => {});
使用 同步钩子 SyncHook 钩子函数依次全部执行, 如果有 hook 回调, 则最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const { SyncHook } = require ('tapable' );var syncHook = new SyncHook (['name' , 'age' ]);syncHook.tap ('x' , (name, age ) => { console .log ('x done ' , name, age); }); syncHook.tap ('y' , (name, age ) => { console .log ('y done ' , name, age); }); syncHook.call ('call' , 18 , () => { console .log ( 'syncHook.call callback 此处不执行, call 传入参数个数大于构造函数参数列表长度的会被忽略' ); }); syncHook.callAsync ('callAsync' , 19 , () => { console .log ('syncHook.callAsync callback' ); }); class MySyncHook { constructor (args ) { this .args = args; this .tasks = []; } tap (name, task ) { this .tasks .push (task); } call (...args ) { if (args.length < this .args .length ) throw new Error ('参数不足' ); args = args.slice (0 , this .args .length ); this .tasks .forEach ((task ) => task (...args)); } }
SyncBailHook 钩子函数依次执行, 如果某个钩子函数的返回值为 非 undefined
, 则后面的钩子不再执行, 如果有 hook 回调, 则最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const { SyncBailHook } = require ('tapable' );const syncBailHook = new SyncBailHook (['name' ]);syncBailHook.tap ('x' , (name ) => { console .log ('x done ' , name); return 'undefined' ; }); syncBailHook.tap ('y' , (name ) => { console .log ('y done ' , name); }); syncBailHook.call ('call' ); syncBailHook.callAsync ('callAsync' , () => { console .log ('syncBailHook.callAsync callback' ); }); class MySyncBailHook { constructor (args ) { this .args = args; this .tasks = []; } tap (name, task ) { this .tasks .push (task); } call (...args ) { args = args.slice (0 , this .args .length ); let i = 0 , ret; do { ret = this .tasks [i++](...args); } while (!ret); } }
SyncWaterfallHook 钩子函数依次全部执行,上一个钩子函数的返回值作为下一个钩子函数的参数,如果有 hook 回调,则最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const { SyncWaterfallHook } = require ('tapable' );var syncWaterfallHook = new SyncWaterfallHook (['name' ]);syncWaterfallHook.tap ('x' , (name ) => { console .log ('x done ' , name); return `from x...${name} ` ; }); syncWaterfallHook.tap ('y' , (name ) => { console .log ('y done ' , name); }); syncWaterfallHook.call ('call' ); syncWaterfallHook.callAsync ('callAsync' , () => { console .log ('syncWaterfallHook.callAsync callback' ); }); class MySyncWaterfallHook { constructor (args ) { this .args = args; this .tasks = []; } tap (name, task ) { this .tasks .push (task); } call (...args ) { args = args.slice (0 , this .args .length ); let [first, ...others] = this .tasks ; return reduce ((ret, task ) => task (ret), first (...args)); } }
SyncLoopHook 钩子函数依次全部执行, 当循环钩子中回调函数返回 非 undefined
时, 钩子将从第一个重新启动, 直到所有的钩子返回 undefined
时结束 如果有 hook 回调, 则最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 const { SyncLoopHook } = require ('tapable' );const syncLoopHook = new SyncLoopHook (['name' ]);syncLoopHook.tap ('x' , (name ) => { let flag = Math .floor (Math .random () * 10 ); if (flag > 5 ) { console .log ('x done ' , name, flag); return undefined ; } else { console .log ('x loop ' , name, flag); return 'undefined' ; } }); syncLoopHook.tap ('y' , (name ) => { let flag = Math .floor (Math .random () * 15 ); if (flag > 10 ) { console .log ('y done ' , name, flag); return undefined ; } else { console .log ('y loop ' , name, flag); return 'undefined' ; } }); syncLoopHook.tap ('z' , (name ) => { console .log ('z done ' , name); return undefined ; }); syncLoopHook.call ('call' ); syncLoopHook.callAsync ('callAsync' , () => { console .log ('syncLoopHook.callAsync callback' ); }); class MySyncLoopHook { constructor (args ) { this .args = args; this .tasks = []; } tap (name, task ) { this .tasks .push (task); } call (...args ) { args = args.slice (0 , this .args .length ); this .tasks .forEach ((task ) => { let ret; do { ret = this .task (...args); } while (ret === true || !(ret === undefined )); }); } }
异步并行 AsyncParallelHook 钩子函数异步并行全部执行, 所有钩子回调执行完后, hook 回调执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 const { AsyncParallelHook } = require ('tapable' );const asyncParallelHook = new AsyncParallelHook (['name' ]);asyncParallelHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 5s ' , name); callback (); }, 5000 ); }); asyncParallelHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 2s ' , name); callback (); }, 2000 ); }); asyncParallelHook.tapAsync ('z' , (name, callback ) => { console .log ('z done ' , name); setTimeout (() => { console .log ('z done setTimeout 3s ' , name); callback (); }, 3000 ); }); asyncParallelHook.tapPromise ('w' , (name ) => { return new Promise ((resolve, reject ) => { console .log ('w done ' , name); setTimeout (() => { console .log ('w done setTimeout 8s' , name); resolve ('hello world' ); }, 8000 ); }); }); asyncParallelHook.callAsync ('callAsync' , () => { console .log ('asyncParallelHook.callAsync' ); }); class MyAsyncParallelHook { constructor (args ) { this .args = args; this .tasks = []; } tabAsync (name, task ) { this .tasks .push (task); } callAsync (...args ) { let finalCallback = args.pop (); args = args.slice (0 , this .args .length ); let i = 0 ; let done = ( ) => { if (++i === this .tasks .length ) { finalCallback (); } }; this .tasks .forEach ((task ) => task (...args, done)); } }
AsyncParallelBailHook 钩子函数全部异步并行执行, 只要有一个钩子返回了 非 undefined
值时, hook 回调会立即执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const { AsyncParallelBailHook } = require ('tapable' );const asyncParallelBailHook = new AsyncParallelBailHook (['name' ]);asyncParallelBailHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 1s' ); callback (); }, 1000 ); }); asyncParallelBailHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 2s' ); callback ('undefined' ); }, 2000 ); }); asyncParallelBailHook.tapAsync ('z' , (name, callback ) => { console .log ('z done ' , name); setTimeout (() => { console .log ('z done setTimeout 3s' ); callback (); }, 3000 ); }); asyncParallelBailHook.callAsync ('callAsync' , () => { console .log ('asyncParallelBailHook.callAsync' ); });
异步串行 AsyncSeriesHook 钩子函数全部异步串行执行, 执行顺序按照注册顺序执行, 上一个钩子执行结束后下一个执行开始, hook 回调最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const { AsyncSeriesHook } = require ('tapable' );const asyncSeriesHook = new AsyncSeriesHook (['name' ]);asyncSeriesHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 2s' ); callback (); }, 2000 ); }); asyncSeriesHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 1s' ); callback (); }, 1000 ); }); asyncSeriesHook.tapAsync ('z' , (name, callback ) => { console .log ('z done' , name); setTimeout (() => { console .log ('z done setTimeout 3s' ); callback (); }, 3000 ); }); asyncSeriesHook.callAsync ('callAsync' , () => { console .log ('asyncSeriesHook.callAsync' ); });
AsyncSeriesBailHook 钩子函数全部异步串行执行, 只要有一个钩子返回了 非 undefined
值时, hook 回调立即执行, 其他钩子有可能不再执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const { AsyncSeriesBailHook } = require ('tapable' );const asyncSeriesBailHook = new AsyncSeriesBailHook (['name' ]);asyncSeriesBailHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 2s' ); callback (); }, 2000 ); }); asyncSeriesBailHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 1s' ); callback ('undefined' ); }, 1000 ); }); asyncSeriesBailHook.tapAsync ('z' , (name, callback ) => { console .log ('z done ' , name); setTimeout (() => { console .log ('z done setTimeout 3s' ); callback (); }, 3000 ); }); asyncSeriesBailHook.callAsync ('callAsync' , () => { console .log ('asyncSeriesBailHook.callAsync' ); });
AsyncSeriesWaterfallHook 钩子函数全部异步串行执行, 上一个钩子的返回的结果作为下一个钩子的参数, hook 回调在所有钩子回调返回后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const { AsyncSeriesWaterfallHook } = require ('tapable' );const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook (['name' ]);asyncSeriesWaterfallHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 1s' ); callback (null , ' from x... ' ); }, 1000 ); }); asyncSeriesWaterfallHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 2s' ); callback (null , ' from y... ' ); }, 2000 ); }); asyncSeriesWaterfallHook.tapAsync ('z' , (name, callback ) => { console .log ('z done ' , name); callback (' from z...' ); }); asyncSeriesWaterfallHook.callAsync ('callAsync' , (...args ) => { console .log ('asyncSeriesWaterfallHook.callAsync' , args); });
AsyncSeriesLoopHook 钩子函数全部异步串行执行, 当循环钩子中回调函数返回 非 undefined
时, 钩子将从第一个重新启动, 直到所有的钩子返回 undefined
时结束, hook 回调最后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 const { AsyncSeriesLoopHook } = require ('tapable' );const asyncSeriesLoopHook = new AsyncSeriesLoopHook (['name' ]);asyncSeriesLoopHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 1s' ); let flag = Math .floor (Math .random () * 10 ); if (flag > 5 ) { console .log ('x done ' , name, flag); callback (null , undefined ); } else { console .log ('x loop ' , name, flag); callback (null , 'undefined' ); } }, 1000 ); }); asyncSeriesLoopHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 2s' ); let flag = Math .floor (Math .random () * 15 ); if (flag > 10 ) { console .log ('y done ' , name, flag); callback (null , undefined ); } else { console .log ('y loop ' , name, flag); callback (null , 'undefined' ); } }, 2000 ); }); asyncSeriesLoopHook.tapAsync ('z' , (name, callback ) => { console .log ('z done ' , name); setTimeout (() => { console .log ('z done setTimeout 3s' ); callback (); }, 3000 ); }); asyncSeriesLoopHook.callAsync ('callAsync' , (...args ) => { console .log ('asyncSeriesLoopHook.callAsync' , args); });
拦截器
拦截器使用 intercept 方法注册, 在钩子注册, 调用过程中触发
call: (…args) => void 向拦截器添加调用方法将在调用钩子时触发, 可以访问钩子参数
tap: (tap: Tap) => void 向拦截器添加点击方法将在执行钩子时触发, 可以访问 tap 对象, 无法更改对象
loop: (…args) => void 向拦截器添加循环方法将在每个循环钩子触发时触发
register: (tap: Tap)=> Tap | undefined 向拦截器添加一个注册器方法将在每次注册钩子时触发, 并可以修改 tap 对象
Context 插件和拦截器可以访问的上下文对象, 该对象可以将任意值传递给后续的插件和拦截器
1 2 3 4 5 6 myHooks.intercept ({ context : true , tap : (context, tapInfo ) => { console .log (context, tapInfo); }, });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const asyncParallelHook = new AsyncParallelHook (['name' ]);asyncParallelHook.intercept ({ call : (...args ) => { console .log ('asyncParallelHook intercept call ' , args); }, tap : (...args ) => { console .log ('asyncParallelHook intercept tap ' , args); }, }); asyncParallelHook.tapAsync ('x' , (name, callback ) => { console .log ('x done ' , name); setTimeout (() => { console .log ('x done setTimeout 2s' ); callback (); }, 2000 ); }); asyncParallelHook.tapAsync ('y' , (name, callback ) => { console .log ('y done ' , name); setTimeout (() => { console .log ('y done setTimeout 5s' ); callback (); }, 5000 ); }); asyncParallelHook.callAsync ('callAsync' , () => { console .log ('asyncParallelHook.callAsync' ); });
辅助函数 HookMap 将 hook 分组, 方便 hook 组批量调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const { HookMap , SyncHook } = require ('tapable' );const hookMap = new HookMap ((key ) => new SyncHook (['name' ]));hookMap.for ('key1' ).tap ('x' , (name ) => { console .log ('key1-x ' , name); }); hookMap.for ('key1' ).tap ('y' , (name ) => { console .log ('key1-y ' , name); }); hookMap.for ('key2' ).tap ('z' , (name ) => { console .log ('key2-z ' , name); }); hookMap.get ('key1' ).call ('call' );
MultiHook 向 hook 批量注册钩子函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const { MultiHook , SyncHook , SyncBailHook } = require ('tapable' );const multiHook = new MultiHook ([ new SyncHook (['name' ]), new SyncBailHook (['name' ]), ]); multiHook.tap ('plugin' , (name ) => { console .log ('multiHook plugin ' ); }); Array .prototype .forEach .call (multiHook.hooks , (hooks ) => { hooks.callAsync ('multiHook.hooks.call ' , () => { console .log ('hooks.callAsync ' ); }); });