不了解Tapable?那你咋写的Webpack插件
介绍
webapck中,在不同的编译阶段,可以调用不同的插件进行加工。
那么插件是怎么在不同阶段被调用的呢?我们在写插件的时候,必须在插件函数中添加一个apply
方法,apply
中会有一个参数compiler
,我们会在compiler
上注册钩子函数,像这样:
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tapAsync('myPlugin', () => {
// ...
});
}
}
这个compiler
对象就是扩展自Tapable
。对于Tapable
中的钩子函数,我们可以理解为像React
或者Vue
中组件的生命周期一样。
总的来说,Tapable
的作用主要是为插件在不同编译阶段提供钩子函数。
使用方法
Tapable
中常用的hook有9种,分为同步和异步hook。
同步hook:
SyncHook
SyncBailHook
SyncWaterfallHook
SyncLoopHook
异步hook:
AsyncParallelHook
AsyncParallelBailHook
AsyncSeriesHook
AsyncSeriesBailHook
AsyncSeriesWaterfallHook
关于Tapable
的使用方法,它的用法与我们使用订阅者模式一样,需要做一个监听,然后需要手动触发,回调监听事件。不同的是,Tapable
有多种监听方式,不同的监听方式对应了不同的触发方式。
Sync | Async |
---|---|
绑定事件:tap | tapAsync,tapPromise |
执行事件:call | callAsync,promise |
SyncHook
钩子函数会依次全部执行完成:
const syncHook = new SyncHook(['arg']);
syncHook.tap('listen1', (arg) => console.log('listen1:', arg));
syncHook.tap('listen2', (arg) => console.log('listen2:', arg));
syncHook.call('hello');
/**
输出:
listen1: hello
listen2: hello
*/
SyncBailHook
钩子函数会依次执行,但是如果某个钩子函数返回了一个非undefined
的值,那么剩下的钩子函数将不会执行。
const syncBailHook = SyncBailHook(['arg']);
syncBailHook.tap('listen1', (arg) => {
console.log('listen1:', arg);
return null;
});
syncBailHook.tap('listen2', (arg) => console.log('listen2:', arg));
syncBailHook.call('hello');
/**
输出:
listen1: hello
*/
SyncWaterfallHook
钩子函数会依次执行,前一个钩子函数返回的值会作为后一个钩子函数的参数。
const hook = SyncWaterfallHook(['arg']);
hook.tap('listen1', (arg) => {
console.log('listen1:', arg);
return `${arg} listen1`;
});
hook.tap('listen2', (arg) => console.log('listen2:', arg));
hook.call('hello');
/**
输出:
listen1: hello
listen2: hello listen1
*/
SyncLoopHook
钩子会依次执行,如果某个钩子中返回了一个非undefined
的值,那么则会从头执行,直到执行的钩子函数都返回undefined
的时,才会继续执行剩下的钩子函数。
const hook = SyncLoopHook(['arg']);
let num = 1;
hook.tap('listen1', (arg) => console.log('listen1'));
hook.tap('listen2', (arg) => {
console.log(`listen2 === num: ${num}`);
if(num === 2) {
return undefined;
}
num += 1;
return num;
});
hook.tap('listen3', (arg) => console.log('listen3'));
hook.call('hello');
/**
输出:
listen1
listen2 === num: 1
listen1
listen2 === num: 2
listen3
*/
AsyncParallelHook
钩子函数会异步并行执行,当所有钩子函数的回调函数都执行后,才会触发执行事件时注册的回调函数。
const hook = AsyncParallelHook(['arg']);
const start = Date.now();
hook.tapAsync('listen1', (arg, callback) => {
console.log('listen1', arg)
setTimeout(() => {
callback();
}, 1000);
});
hook.tapAsync('listen2', (arg, callback) => {
console.log('listen2', arg);
setTimeout(() => {
callback();
}, 2000);
});
hook.callAsync('hello', () => {
console.log(`回调函数执行,耗时:${Date.now() - start}`);
});
/**
输出:
listen1 hello
listen2 hello
回调函数执行,耗时:2013
*/
AsyncParallelBailHook
钩子函数会异步并行执行,当某个钩子函数中调用callback
时,传入了一个非undefined
的值,那么执行事件时注册的回调函数会立即执行。
注意:AsyncParallelBailHook
中的所有的钩子函数都会执行。
const hook = AsyncParallelBailHook(['arg']);
const start = Date.now();
hook.tapAsync('listen1', (arg, callback) => {
console.log('listen1', arg)
setTimeout(() => {
callback(true);
}, 1000);
});
hook.tapAsync('listen2', (arg, callback) => {
console.log('listen2', arg);
setTimeout(() => {
callback();
}, 2000);
});
hook.callAsync('hello', () => {
console.log(`回调函数执行,耗时:${Date.now() - start}`);
});
/**
输出:
listen1 hello
listen2 hello
回调函数执行,耗时:1015
*/
AsyncSeriesHook
钩子函数会串行执行,会保证执行的顺序,上一个钩子结束后,下一个钩子才会开始,执行事件注册的回调函数会最后执行。
const hook = AsyncSeriesHook(['arg']);
const start = Date.now();
hook.tapAsync('listen1', (arg, callback) => {
console.log(`listen1==耗时:${Date.now() - start}`)
setTimeout(() => {
callback();
}, 1000);
});
hook.tapAsync('listen2', (arg, callback) => {
console.log(`listen2==耗时:${Date.now() - start}`)
setTimeout(() => {
callback();
}, 2000);
});
hook.callAsync('hello', () => {
console.log(`回调函数执行,耗时:${Date.now() - start}`);
});
/**
输出:
listen1==耗时:1
listen2==耗时:1013
回调函数执行,耗时:3018
*/
AsyncSeriesBailHook
钩子函数会异步串行执行,但只要有一个钩子函数调用callback
时传入了一个非undefined
值,那么执行事件时注册的回调函数就会执行,剩下的钩子函数将不会执行。
const hook = AsyncSeriesBailHook(['arg']);
const start = Date.now();
hook.tapAsync('listen1', (arg, callback) => {
console.log('listen1')
setTimeout(() => {
callback(true);
}, 1000);
});
hook.tapAsync('listen2', (arg, callback) => {
console.log('listen2')
setTimeout(() => {
callback();
}, 2000);
});
hook.callAsync('hello', () => {
console.log(`回调函数执行,耗时:${Date.now() - start}`);
});
/**
输出:
listen1
回调函数执行,耗时:1014
*/
AsyncSeriesWaterfallHook
钩子函数会异步串行执行,前一个钩子函数通过调用callback
传入的参数会作为下一个钩子函数的参数,当所有钩子函数执行结束后,才会触发执行事件时注册的回调函数,该回调函数中接收了最后一个钩子函数返回的参数。
const hook = AsyncSeriesWaterfallHook(['arg']);
const start = Date.now();
hook.tapAsync('listen1', (arg, callback) => {
console.log('listen1', arg)
setTimeout(() => {
callback(null, `${arg} listen1`);
}, 1000);
});
hook.tapAsync('listen2', (arg, callback) => {
console.log('listen2', arg)
setTimeout(() => {
callback(null, `${arg} listen2`);
}, 2000);
});
hook.callAsync('hello', (_, arg) => {
console.log(`回调函数执行,耗时:${Date.now() - start}, arg:`, arg);
});
/**
输出:
listen1 hello
listen2 hello listen1
回调函数执行,耗时:3016, arg: hello listen1 listen2
*/
总结
- webpack中的
compiler
对象是扩展自Tapable
。 Tapable
的主要作用是在webpack编译过程的不同阶段为plugin提供钩子函数。Tapable
提供了异步
和同步
的方式来注册钩子函数与执行钩子函数:
Sync | Async |
---|---|
绑定事件:tap | tapAsync,tapPromise |
执行事件:call | callAsync,promise |
- 本文总结了9种常用的钩子函数:
SyncHook
:钩子函数会依次全部执行完成。SyncBailHook
:钩子函数会依次执行,但是如果某个钩子函数返回了一个非undefined的值,那么剩下的钩子函数将不会执行。SyncWaterfallHook
:钩子函数会依次执行,前一个钩子函数返回的值会作为后一个钩子函数的参数。SyncLoopHook
:钩子会依次执行,如果某个钩子中返回了一个非undefined
的值,那么则会从头执行,直到执行的钩子函数都返回undefined
的时,才会继续执行剩下的钩子函数。AsyncParallelHook
:钩子函数会异步并行执行,当所有钩子函数的回调函数都执行后,才会触发执行事件时注册的回调函数。AsyncParallelBailHook
:钩子函数会异步并行执行,当某个钩子函数中调用callback
时,传入了一个非undefined
的值,那么执行事件时注册的回调函数会立即执行。注意:AsyncParallelBailHook
中的所有的钩子函数都会执行。AsyncSeriesHook
:钩子函数会串行执行,会保证执行的顺序,上一个钩子结束后,下一个钩子才会开始,执行事件注册的回调函数会最后执行。AsyncSeriesBailHook
:钩子函数会异步串行执行,但只要有一个钩子函数调用callback
时传入了一个非undefined
值,那么执行事件时注册的回调函数就会执行,剩下的钩子函数将不会执行。AsyncSeriesWaterfallHook
:钩子函数会异步串行执行,前一个钩子函数通过调用callback
传入的参数会作为下一个钩子函数的参数,当所有钩子函数执行结束后,才会触发执行事件时注册的回调函数,该回调函数中接收了最后一个钩子函数返回的参数。
同步钩子函数主要是根据前一个钩子函数的处理结果来选择以什么样的方式执行下一个钩子函数。
异步钩子函数主要是根据前一个钩子函数的处理结果来选择以什么样的方式执行下一个钩子函数与执行事件时注册的回调函数。
关于webpack其它文章
转载自:https://juejin.cn/post/7034379770053787684