This page was saved using WebZIP 7.0.3.1030 offline browser on 12/02/19 14:55:01.
Address: http://www.zhufengpeixun.cn/ahead/html/26.webpack-3.tapable.html
Title: 珠峰架构师成长计划  •  Size: 94239  •  Last Modified: Sun, 01 Dec 2019 11:37:00 GMT

1. webpack的插件机制 #

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
} = require('tapable');

2. tapable分类 #

tapable

类型 使用要点
Basic 不关心监听函数的返回值
Bail 保险式: 只要监听函数中有返回值(不为undefined),则跳过之后的监听函数
Waterfall 瀑布式: 上一步的返回值交给下一步使用
Loop 循环类型: 如果该监听函数返回true,则这个监听函数会反复执行,如果返回undefined则退出循环

3.SyncHook #

  1. 所有的构造函数都接收一个可选参数,参数是一个参数名的字符串数组
  2. 参数的名字可以任意填写,但是参数数组的长数必须要根实际接受的参数个数一致
  3. 如果回调函数不接受参数,可以传入空数组
  4. 在实例化的时候传入的数组长度长度有用,值没有用途
  5. 执行call时,参数个数和实例化时的数组长度有关
  6. 回调的时候是按先入先出的顺序执行的,先放的先执行
const {SyncHook} = require('tapable');
/*
const slice = Array.prototype.slice;
 class SyncHook{
    constructor(args) {
        this.args= args;
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    call() {
        this.taps.forEach(fn=>fn(...slice.call(arguments,0,this.args.length)));
    }
}
*/

const hook = new SyncHook(['name','age']);
hook.tap('1',(name,age)=>{
    console.log(1,name,age);
    return 1;
});
hook.tap('2',(name,age)=>{
    console.log(2,name,age);
    return 2;
});
hook.tap('3',(name,age)=>{
    console.log(3,name,age);
    return 3;
});

hook.call('zhufeng',10);

4.SyncBailHook #

  1. BailHook中的回调函数也是顺序执行的
  2. 调用call时传入的参数也可以传给回调函数
  3. 当回调函数返回非undefined值的时候会停止调用后续的回调
//const {SyncBailHook} = require('tapable');
const slice = Array.prototype.slice;
class SyncBailHook{
    constructor(args) {
        this.args= args;
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    call() {
        let args = slice.call(arguments,0,this.args.length);
        let result;
        let i=0;
        while(i<this.taps.length&&!result){
            result = this.taps[i++](...args);
        }
    }
}

const hook = new SyncBailHook(['name','age']);
hook.tap('1',(name,age)=>{
    console.log(1,name,age);
    //return 1;
});
hook.tap('2',(name,age)=>{
    console.log(2,name,age);
    return 2;
});
hook.tap('3',(name,age)=>{
    console.log(3,name,age);
    return 3;
});

hook.call('zhufeng',10);

5.SyncWaterfallHook #

  1. SyncWaterfallHook表示如果上一个回调函数的结果不为undefined,则可以作为下一个回调函数的第一个参数
  2. 回调函数接受的参数来自于上一个函数的结果
  3. 调用call传入的第一个参数,会被上一个函数的非undefined结果替换
  4. 当回调函数返回非undefined不会停止回调栈的调用
//const {SyncWaterfallHook} = require('tapable');
const slice = Array.prototype.slice;
class SyncWaterfallHook{
    constructor(args) {
        this.args= args;
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    call() {
        let args = slice.call(arguments,0,this.args.length);
        let first=args[0];
        let result;
        let i=0;
        while(i<this.taps.length){
            first = result||first;
            result = this.taps[i++](first,...args.slice(1));
        }
    }
}

const hook = new SyncWaterfallHook(['name','age']);
hook.tap('1',(name,age)=>{
    console.log(1,name,age);
    return 1;
});
hook.tap('2',(name,age)=>{
    console.log(2,name,age);
    return ;
});
hook.tap('3',(name,age)=>{
    console.log(3,name,age);
    return 3;
});

hook.call('zhufeng',10);

6.SyncLoopHook #

  1. SyncLoopHook的特点是不停的循环执行回调函数,直到函数结果等于undefined
//const {SyncLoopHook} = require('tapable');
//当回调函数返回非undefined值的时候会停止调用后续的回调
class SyncLoopHook{
  constructor(args){
    this._args = args;//['name','age']
    this.taps = [];
  }
  tap(name,fn){
     this.taps.push(fn);
  }
  call(){
      let args = Array.from(arguments).slice(0,this._args.length);
      let loop = true;
      while(loop){
        for(let i=0;i<this.taps.length;i++){
          let fn = this.taps[i];
          let  result = fn(...args);
          loop = typeof result != 'undefined';
          if(loop)break;
        } 
      }
  }
}
let hook = new SyncLoopHook(['name','age']);
let counter1 = 0;
let counter2 = 0;
let counter3 = 0;
//不停的循环执行回调函数,直到函数结果等于undefined
hook.tap('1',(name,age)=>{
  console.log(1,name,age);
  if(++counter1==1){
      counter1 = 0
      return;
  }
  return true;
});
hook.tap('2',(name,age)=>{
  console.log(2,name,age);
  if(++counter2==2){
      counter2 = 0
      return;
  }
  return true;
});
hook.tap('3',(name,age)=>{
  console.log(3,name,age);
  if(++counter3==3){
      counter3 = 0
      return;
  }
  return true;
});
hook.call('zhufeng',10);


7. AsyncParallelHook #

异步并行执行钩子

7.1 tap #

//let {AsyncParallelHook}=require('tapable');
class AsyncParallelHook{
    constructor() {
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let callback=args.pop();
        this.taps.forEach(fn => fn(...args));
        callback();
    }
}
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tap('1',function(name){
    console.log(1);
});
queue.tap('2',function(name){
    console.log(2);
});
queue.tap('3',function(name){
    console.log(3);
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

7.2 tapAsync #

//let {AsyncParallelHook}=require('tapable');
class AsyncParallelHook{
    constructor() {
        this.taps=[];
    }
    tapAsync(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let callback=args.pop();
        let i=0,length = this.taps.length;
        function done(err) {
            if (++i == length) {
                callback(err);
            }
        }
        this.taps.forEach(fn => {
            fn(...args,done);
        });
    }
}
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
    setTimeout(function(){
        console.log(1);
        callback();
    },1000)
});
queue.tapAsync('2',function(name,callback){
    setTimeout(function(){
        console.log(2);
        callback();
    },2000)
});
queue.tapAsync('3',function(name,callback){
    setTimeout(function(){
        console.log(3);
        callback();
    },3000)
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

7.3 tapPromise #

//let {AsyncParallelHook}=require('tapable');
class AsyncParallelHook{
    constructor() {
        this.taps=[];
    }
    tapPromise(name,fn) {
        this.taps.push(fn);
    }
    promise() {
        let promises = this.taps.map(fn => fn());
        return Promise.all(promises);
    }
}
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(1);
            resolve();
        },1000)
    });

});
queue.tapPromise('2',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(2);
            resolve();
        },2000)
    });
});
queue.tapPromise('3',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(3);
            resolve();
        },3000)
    });
});
queue.promise('zfpx').then(()=>{
    console.timeEnd('cost');
})

8. AsyncParallelBailHook #

带保险的异步并行执行钩子

8.1 tap #

//let {AsyncParallelBailHook} = require('tapable');
class AsyncParallelBailHook{
    constructor() {
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let callback=args.pop();
        for (let i=0;i<this.taps.length;i++){
            let ret=this.taps[i](...args);
            if (ret) {
                return callback(ret);
            }
        }
    }
}
let queue=new AsyncParallelBailHook(['name']);
console.time('cost');
queue.tap('1',function(name){
    console.log(1);
    return "Wrong";
});
queue.tap('2',function(name){
    console.log(2);
});
queue.tap('3',function(name){
    console.log(3);
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

8.2 tapAsync #

//let {AsyncParallelBailHook} = require('tapable');

class AsyncParallelBailHook{
    constructor() {
        this.taps=[];
    }
    tapAsync(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let finalCallback=args.pop();
        let count=0,total=this.taps.length;
        function done(err) {
            if (err) {
                return finalCallback(err);
            } else {
                if (++count == total) {
                    return finalCallback();
                }
            }
        }
        for (let i=0;i<total;i++){
            let fn=this.taps[i];
            fn(...args,done);
        }
    }
}
let queue=new AsyncParallelBailHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
    console.log(1);
    callback('Wrong');
});
queue.tapAsync('2',function(name,callback){
    console.log(2);
    callback();
});
queue.tapAsync('3',function(name,callback){
    console.log(3);
    callback();
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

8.3 tapPromise #

//let {AsyncParallelBailHook} = require('tapable');

class AsyncParallelBailHook{
    constructor() {
        this.taps=[];
    }
    tapPromise(name,fn) {
        this.taps.push(fn);
    }
    promise() {
        let args=Array.from(arguments);
        let promises = this.taps.map(fn => fn(...arguments));
        return Promise.all(promises);
    }
}
let queue = new AsyncParallelBailHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(1);
            resolve();
        },1000)
    });
});
queue.tapPromise('2',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(2);
            reject();
        },2000)
    });
});

queue.tapPromise('3',function(name){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(3);
            resolve();
        },3000)
    });
});

queue.promise('zfpx').then(()=>{
    console.timeEnd('cost');
},err => {
    console.error(err);
    console.timeEnd('cost');
})

9. AsyncSeriesHook #

异步串行钩子

9.1 tap #

//let {AsyncSeriesHook} = require('tapable');
class AsyncSeriesHook{
    constructor() {
        this.taps=[];
    }
    tap(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let finalCallback=args.pop();
        let count=0,total=this.taps.length;
        function done() {
            if (++count == total) {
                    return finalCallback();
            }
        }
        for (let i=0;i<total;i++){
            let fn=this.taps[i];
            fn(...args,done);
        }
    }
}
let queue = new AsyncSeriesHook(['name']);
console.time('cost');
queue.tap('1',function(name){
    console.log(1);
});
queue.tap('2',function(name){
    console.log(2);
});
queue.tap('3',function(name){
    console.log(3);
});
queue.callAsync('zhufeng',err=>{
    console.log(err);
    console.timeEnd('cost');
});

9.2 tapAsync #

class AsyncSeriesBailHook{
    constructor() {
        this.taps=[];
    }
    tapAsync(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args = Array.from(arguments);
        let finalCallback = args.pop();
        let index = 0, length = this.taps.length;
        let next = () => {
            let fn = this.taps[index++];
            if (fn) {
                fn(...args, next);
            } else {
                finalCallback();
            }
        }
        next();
    }
}
let queue = new AsyncSeriesHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
   setTimeout(function(){
       console.log(1);
   },1000)
});
queue.tapAsync('2',function(name,callback){
    setTimeout(function(){
        console.log(2);
        callback();
    },2000)
});
queue.tapAsync('3',function(name,callback){
    setTimeout(function(){
        console.log(3);
        callback();
    },3000)
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

9.3 tapPromise #

class AsyncSeriesHook{
    constructor() {
        this.taps=[];
    }
    tapPromise(name,fn) {
        this.taps.push(fn);
    }
    promise() {
        let args=Array.from(arguments);
         //first是第一个函数, fns是剩下的函数
        let [first, ...fns] = this.taps;
        return fns.reduce((a, b) => {
            return a.then(b);
        }, first(...args));
    }
}
let queue=new AsyncSeriesHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
   return new Promise(function(resolve){
       setTimeout(function(){
           console.log(1);
           resolve();
       },1000)
   });
});
queue.tapPromise('2',function(name){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(2);
            resolve();
        },2000)
    });
});
queue.tapPromise('3',function(name){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(3);
            resolve();
        },3000)
    });
});
queue.promise('zfpx').then(data=>{
    console.log(data);
    console.timeEnd('cost');
});

10. AsyncSeriesBailHook #

10.1 tap #

let {AsyncSeriesBailHook} = require('tapable');
let queue = new AsyncSeriesBailHook(['name']);
console.time('cost');
queue.tap('1',function(name){
    console.log(1);
    return "Wrong";
});
queue.tap('2',function(name){
    console.log(2);
});
queue.tap('3',function(name){
    console.log(3);
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

10.2 tabAsync #

//let {AsyncSeriesBailHook}=require('tapable');
class AsyncSeriesBailHook{
    constructor() {
        this.taps=[];
    }
    tapAsync(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let callback=args.pop();
        let i=0,size = this.taps.length;
        let next=(err) => {
            if (err) return  callback(err);
            let fn=this.taps[i++];
            fn?fn(...args,next):callback();
        }
        next();
    }
}
let queue = new AsyncSeriesBailHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
   setTimeout(function(){
       console.log(1);
       callback('wrong');
   },1000)
});
queue.tapAsync('2',function(name,callback){
    setTimeout(function(){
        console.log(2);
        callback();
    },2000)
});
queue.tapAsync('3',function(name,callback){
    setTimeout(function(){
        console.log(3);
        callback();
    },3000)
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

10.3 tapPromise #

//let {AsyncSeriesBailHook} = require('tapable');
class AsyncSeriesBailHook{
    constructor() {
        this.taps=[];
    }
    tapPromise(name,fn) {
        this.taps.push(fn);
    }
    promise() {
        let args=Array.from(arguments);
        let [first, ...fns] = this.taps;
        let promise =  fns.reduce((a, b) => {
            return a.then(() => b(),(err)=>Promise.reject(err));
        }, first(...args));
        return promise;
    }
}
let queue = new AsyncSeriesBailHook(['name']);
console.time('cost');
queue.tapPromise('1',function(name){
   return new Promise(function(resolve){
       setTimeout(function(){
           console.log(1);
           resolve();
       },1000)
   });
});
queue.tapPromise('2',function(name,callback){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log(2);
            reject('失败了');
        },2000)
    });
});
queue.tapPromise('3',function(name,callback){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(3);
            resolve();
        },3000)
    });
});
queue.promise('zfpx').then(data=>{
    console.log(data);
    console.timeEnd('cost');
},error=>{
    console.log(error);
    console.timeEnd('cost');
});

11. AsyncSeriesWaterfallHook #

11.1 tap #

let {AsyncSeriesWaterfallHook} = require('tapable');
let queue = new AsyncSeriesWaterfallHook(['name']);
console.time('cost');
queue.tap('1',function(name,callback){
    console.log(1);
});
queue.tap('2',function(data){
    console.log(2,data);
});
queue.tap('3',function(data){
    console.log(3,data);
});
queue.callAsync('zfpx',err=>{
    console.log(err);
    console.timeEnd('cost');
});

11.2 tapAsync #

//let {AsyncSeriesBailHook}=require('tapable');
class AsyncSeriesWaterfallHook{
    constructor() {
        this.taps=[];
    }
    tapAsync(name,fn) {
        this.taps.push(fn);
    }
    callAsync() {
        let args=Array.from(arguments);
        let callback=args.pop();
        let i=0,size = this.taps.length;
        let next=(err,data) => {
            if (err) return  callback(err);
            let fn=this.taps[i++];
            if (fn) {
                if (i==0) {
                    fn(...args,next);
                } else {
                    fn(data,next);
                }

            } else {
                callback(err,data);
            }
        }
        next();
    }
}
let queue = new AsyncSeriesWaterfallHook(['name']);
console.time('cost');
queue.tapAsync('1',function(name,callback){
   setTimeout(function(){
       console.log(1);
       callback(null,1);
   },1000)
});
queue.tapAsync('2',function(data,callback){
    setTimeout(function(){
        console.log(2);
        callback(null,2);
    },2000)
});
queue.tapAsync('3',function(data,callback){
    setTimeout(function(){
        console.log(3);
        callback(null,3);
    },3000)
});
queue.callAsync('zfpx',(err,data)=>{
    console.log(err,data);
    console.timeEnd('cost');
});

11.3 tapPromise #

let {AsyncSeriesWaterfallHook} = require('tapable');
class AsyncSeriesWaterfallHook {
    constructor() {
        this.taps = [];
    }
    tapPromise(name, fn) {
        this.taps.push(fn);
    }
    promise(...args) {
        //first是第一个函数, fns是剩下的函数
        let [first, ...fns] = this.taps;
        return fns.reduce((a, b) => {
            return a.then((data) => b(data));
        }, first(...args));
    }
}
let queue = new AsyncSeriesWaterfallHook(['name']);
console.time('cost');
queue.tapPromise('1', function (name) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(name, 1);
            resolve(1);
        }, 1000);
    });
});
queue.tapPromise('2', function (data) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(data, 2);
            resolve(2);
        }, 2000);
    });
});
queue.tapPromise('3', function (data) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(data, 3);
            resolve(3);
        }, 3000);
    });
});
queue.promise('zfpx').then(err => {
    console.timeEnd('cost');
});

12.intercept #

const {SyncHook} = require("tapable");
const hook = new SyncHook(["name"]);
hook.tap("1", (name) => {
  console.log(1, name);
});
hook.tap("2", (name) => {
  console.log(2, name);
});

hook.intercept({
    //call:(...args) => void当你的钩子触发之前,(就是call()之前),就会触发这个函数,你可以访问钩子的参数.多个钩子执行一次
    call: (source, target, name) => {
      console.log('call');
    },
    //tap: (tap: Tap) => void 每个钩子执行之前(多个钩子执行多个),就会触发这个函数
    tap(){
      console.log('tap');
    },
    // 每添加一个Tap都会触发 你interceptor上的register,
    //你下一个拦截器的register 函数得到的参数 取决于你上一个register返回的值,所以你最好返回一个 tap 钩子.
    register: (tapInfo) => {
        console.log('register',tapInfo);
        return tapInfo;
    }
}) 
hook.call('zhufeng');

13. Context(上下文) #

const {SyncLoopHook} = require("tapable");
const hook = new SyncLoopHook(["name"]);
let counter=0;
hook.tap({context: true,name:"1"}, (context,name) => {
  context[counter] = counter;
  console.log(1, context,name);
  if(++counter >= 2){
    return;
  }
  return true;
});


hook.intercept({
    context: true,
    loop(context){//每次循环执行此拦截器
       console.log('loop',context);
    }
}) 
hook.call('zhufeng');

14. hook原理 #

14.1 index.js #

index.js

const SyncHook = require("./SyncHook");
//const {SyncHook} = require('tapable');
let syncHook = new SyncHook(["name"]);
syncHook.tap("1", name => {
  console.log(name, 1);
});
syncHook.tap("2", name => {
  console.log(name, 2);
});
syncHook.call("zhufeng");
/* 
(function anonymous(name) {
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  _fn0(name);
  var _fn1 = _x[1];
  _fn1(name);
}) 
*/

14.2 SyncHook.js #

SyncHook.js

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
const factory = new HookCodeFactory();
class SyncHook extends Hook {
  compile(options) {
    factory.setup(this, options); //把回调函数数组赋给_x
    return factory.create(options);
  }
}
module.exports = SyncHook;

14.3 Hook.js #

class Hook {
  constructor(args) {
    if (!Array.isArray(args)) args = []; //参数
    this._args = args; // 这里存入初始化的参数
    this.taps = []; //这里就是回调栈用到的数组
    this._x = undefined; //这个比较重要,后面拼代码会用
  }
  tap(options, fn) {
    if (typeof options === "string") options = { name: options };
    options.fn = fn;
    this._insert(options); //参数处理完之后,调用_insert,这是关键代码
  }
  _insert(item) {
    this.taps[this.taps.length] = item;
  }
  call(...args) {
    let callMethod = this._createCall();
    return callMethod.apply(this, args);
  }
  _createCall(type) {
    return this.compile({
      taps: this.taps,
      args: this._args
    });
  }
}

module.exports = Hook;

14.4 HookCodeFactory.js #

class HookCodeFactory {
  args() {
    return this.options.args.join(",");
  }
  setup(instance, options) {
    this.options = options;
    instance._x = options.taps.map(t => t.fn);
  }
  header() {
    return "var _x = this._x;\n";
  }
  content() {
    let code = "";
    for (let idx = 0; idx < this.options.taps.length; idx++) {
      code += `var _fn${idx} = _x[${idx}];\n
               _fn${idx}(${this.args()});\n`; 
    }
    return code;
  }
  create(options) {
    return new Function(this.args(), this.header() + this.content());
  }
}
module.exports = HookCodeFactory;

15.参考 #