阅读 414
不了解Tapable?那你咋写的Webpack插件

不了解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

  1. SyncHook
  2. SyncBailHook
  3. SyncWaterfallHook
  4. SyncLoopHook

异步hook:

  1. AsyncParallelHook
  2. AsyncParallelBailHook
  3. AsyncSeriesHook
  4. AsyncSeriesBailHook
  5. AsyncSeriesWaterfallHook

关于Tapable的使用方法,它的用法与我们使用订阅者模式一样,需要做一个监听,然后需要手动触发,回调监听事件。不同的是,Tapable有多种监听方式,不同的监听方式对应了不同的触发方式。

SyncAsync
绑定事件:taptapAsync,tapPromise
执行事件:callcallAsync,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
 */
复制代码

总结

  1. webpack中的compiler对象是扩展自Tapable
  2. Tapable的主要作用是在webpack编译过程的不同阶段为plugin提供钩子函数。
  3. Tapable提供了异步同步的方式来注册钩子函数与执行钩子函数:
SyncAsync
绑定事件:taptapAsync,tapPromise
执行事件:callcallAsync,promise
  1. 本文总结了9种常用的钩子函数:
  • SyncHook:钩子函数会依次全部执行完成。
  • SyncBailHook:钩子函数会依次执行,但是如果某个钩子函数返回了一个非undefined的值,那么剩下的钩子函数将不会执行。
  • SyncWaterfallHook:钩子函数会依次执行,前一个钩子函数返回的值会作为后一个钩子函数的参数。
  • SyncLoopHook:钩子会依次执行,如果某个钩子中返回了一个非undefined的值,那么则会从头执行,直到执行的钩子函数都返回undefined的时,才会继续执行剩下的钩子函数。
  • AsyncParallelHook:钩子函数会异步并行执行,当所有钩子函数的回调函数都执行后,才会触发执行事件时注册的回调函数。
  • AsyncParallelBailHook:钩子函数会异步并行执行,当某个钩子函数中调用callback时,传入了一个非undefined的值,那么执行事件时注册的回调函数会立即执行。注意:AsyncParallelBailHook中的所有的钩子函数都会执行。
  • AsyncSeriesHook:钩子函数会串行执行,会保证执行的顺序,上一个钩子结束后,下一个钩子才会开始,执行事件注册的回调函数会最后执行。
  • AsyncSeriesBailHook:钩子函数会异步串行执行,但只要有一个钩子函数调用callback时传入了一个非undefined值,那么执行事件时注册的回调函数就会执行,剩下的钩子函数将不会执行。
  • AsyncSeriesWaterfallHook:钩子函数会异步串行执行,前一个钩子函数通过调用callback传入的参数会作为下一个钩子函数的参数,当所有钩子函数执行结束后,才会触发执行事件时注册的回调函数,该回调函数中接收了最后一个钩子函数返回的参数。

同步钩子函数主要是根据前一个钩子函数的处理结果来选择以什么样的方式执行下一个钩子函数。

异步钩子函数主要是根据前一个钩子函数的处理结果来选择以什么样的方式执行下一个钩子函数与执行事件时注册的回调函数。

关于webpack其它文章

面试官:你写过webpack插件吗?

Webpack5,了解从0到1搭建一个项目的细节

来吧!一起肝一个CLI工具

文章分类
前端
文章标签