手写wepback中核心模块tapable底层源码

172 阅读7分钟

Webpack本质上是一种事件流的机制,tapable是实现webpack将各个插件串联起来的最核心库,负责编译的 Compiler 和负责创建 bundles 的 Compilation 都是 tapable 构造函数的实例

本文将模拟tapable库 “钩子” 的事件处理机制,了解 tapable,可以为学习 Webpack 原理做一个铺垫。

在 Webpack 中,这些 “钩子” 的真正作用就是将通过配置文件读取的插件与插件、加载器与加载器之间进行连接,“并行” 或 “串行” 执行。

手写这些方法不仅熟悉tapable底层原理,也引起自己对于同步和异步分别对串行、并行的实现

tapable 核心库的类

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

Sync 类型的钩子

SyncHook:串行同步执行

// 串行同步执行
class SyncHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName插件名称,只是起到注释作用
	tap(taskName,task){
		this.tasks.push(task)
	}
	call(...args){
		// 也可在参数不足时抛出异常
		if (args.length < this.instanseArgs.length) throw new Error("参数不足");

		// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
		args = args.slice(0, this.instanseArgs.length);

		// 依次执行事件处理函数
		this.tasks.forEach(task => task(...args));
	}

}


// 创建实例
let syncHook = new SyncHook(["name", "age", 't']);
// let syncHook = new SyncHook(["name", "age", 't']); //会提示参数不足
// 注册事件
syncHook.tap("tast1", (name, age) => console.log("1", name, age));
syncHook.tap("tast2", (name, age) => console.log("2", name, age));
syncHook.tap("tast3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncHook.call("hannie", 18);

>
1 hannie 18
2 hannie 18
3 hannie 18

SyncBailHook:串行同步执行,如果事件处理函数执行时有一个返回值不为空(即返回值不为 undefined),则跳过剩下未执行的事件处理函数

/**
 * SyncBailHook 同样为串行同步执行,如果事件处理函数执行时有一个返回值不为空(即返回值不为 undefined),则跳过剩下未执行的事件处理函数
*/
// 串行同步执行
class SyncBailHook {
  constructor(instanseArgs) {
    this.instanseArgs = instanseArgs;
    this.tasks = [];
  }
  // taskName插件名称,只是起到注释作用
  tap(taskName, task) {
    this.tasks.push(task)
  }
  call(...args) {
    // 也可在参数不足时抛出异常
    if (args.length < this.instanseArgs.length) throw new Error("参数不足");

    // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
    args = args.slice(0, this.instanseArgs.length);

    // 依次执行事件处理函数
    let ret = undefined;
    for (let i = 0; i < this.tasks.length; i++) {
      ret = this.tasks[i](args)
      if (ret) {
        break;
      }
    }
    // forEach不能用break
    // this.tasks.forEach(function(task) { 
    //     ret = task(args)
    //     if(!ret){
    //       break;
    //     }

    // });

    // 依次执行事件处理函数,如果返回值不为空,则停止向下执行
    // let i = 0, ret;
    // do {
    //     ret = this.tasks[i++](...args);
    // } while (!ret);
  }

}

// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);

// 注册事件
syncBailHook.tap("1", (name, age) => console.log("1", name, age));

syncBailHook.tap("2", (name, age) => {
  console.log("2", name, age);
  return "2";
});
// 跳过执行
syncBailHook.tap("3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncBailHook.call("hannie", 18);

> 
 1 hannie 18
 2 hannie 18

SyncLoopHook:串行循环执行

/**
 * 事件处理函数返回 true 表示继续循环,即循环执行当前事件处理函数,返回 undefined 表示结束循环
 * SyncBailHook 只决定是否继续向下执行后面的事件处理函数,而 SyncLoopHook 的循环是指循环执行每一个事件处理函数,直到返回 undefined 为止,才会继续向下执行其他事件处理函数,执行机制同理。

*/

class SyncLoopHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 依次执行事件处理函数,如果返回值为 true,则继续执行当前事件处理函数
        // 直到返回 undefined,则继续向下执行其他事件处理函数
        this.tasks.forEach(task => {
            let ret;
            do {
                ret = task(...args);
            } while (ret === true || !(ret === undefined));
        });
    }
}



// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);

// 定义辅助变量
let total1 = 0;
let total2 = 0;

// 注册事件
syncLoopHook.tap("1", (name, age) => {
    console.log("1", name, age, total1);
    return total1++ < 2 ? true : undefined;
});

syncLoopHook.tap("2", (name, age) => {
    console.log("2", name, age, total2);
    return total2++ < 2 ? true : undefined;
});

syncLoopHook.tap("3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncLoopHook.call("hannie", 18);

> 
1 hannie 18 0
1 hannie 18 1
1 hannie 18 2
2 hannie 18 0
2 hannie 18 1
2 hannie 18 2
3 hannie 18

SyncWaterfallHook:串行同步执行

/**
 * SyncWaterfallHook 为串行同步执行,上一个事件处理函数的返回值作为参数传递给下一个事件处理函数,依次类推,正因如此,只有第一个事件处理函数的参数可以通过 call 传递,而 call 的返回值为最后一个事件处理函数的返回值
*/
// 串行同步执行
class SyncWaterfallHook {
	constructor(instanseArgs) {
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
	// taskName插件名称,只是起到注释作用
	tap(taskName, task) {
		this.tasks.push(task)
	}
	call(...args) {
		// 也可在参数不足时抛出异常
		if (args.length < this.instanseArgs.length) throw new Error("参数不足");

		// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
		args = args.slice(0, this.instanseArgs.length);

		let [first, ...others] = this.tasks;
		others.reduce((res, task) => {
			return task(res)
		}, first(...args))
	}

}

// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);

// 注册事件
syncWaterfallHook.tap("1", (name, age) => {
	console.log("1", name, age);
	return "1";
});

syncWaterfallHook.tap("2", data => {
	console.log("2", data);
	return "2";
});

syncWaterfallHook.tap("3", data => {
	console.log("3", data);
	return "3"
});

// 触发事件,让监听函数执行
let ret = syncWaterfallHook.call("hannie", 18);
console.log("call", ret);

>
1 hannie 18
2 1
3 2
call undefined

Async 类型的钩子

AsyncParallelHook(setTimeout):并行异步执行

/**
 * 并行异步执行
*/
class AsyncParallelHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName插件名称,只是起到注释作用
	tapAsync(taskName,task){
		this.tasks.push(task)
	}
	callAsync(...args){
    let finalCallback = args.pop();

		// 也可在参数不足时抛出异常
		if (args.length < this.instanseArgs.length) throw new Error("参数不足");

		// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
		args = args.slice(0, this.instanseArgs.length); //["name", "age"]
  
    // let i = 0;
    // let done = () => {
    //     if (++i === this.tasks.length) {
    //         finalCallback();
    //     }
    // };
// task是包含done回调函数的函数
    this.tasks.forEach((task,index)=>{
      // task(...args,done)
      task(...args,()=>{
        if(index===this.tasks.length){
          finalCallback();
        }
      })
      // if(index===this.tasks.length){
      //   finalCallback();
      // } //在此处也是可以的,就是为了循环完就执行
    })
	}

}

// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 注册事件
console.time("time");
asyncParallelHook.tapAsync("1", (name, age, done) => {
    setTimeout(() => {
        console.log("1", name, age, new Date());
        done();
    }, 1000);
});

asyncParallelHook.tapAsync("2", (name, age, done) => {
    setTimeout(() => {
        console.log("2", name, age, new Date());
        done();
    }, 2000);
});

asyncParallelHook.tapAsync("3", (name, age, done) => {
    setTimeout(() => {
        console.log("3", name, age, new Date());
        done();
        console.timeEnd("time");
    }, 3000);
});

// 触发事件,让监听函数执行
asyncParallelHook.callAsync("hannie", 18, () => {
    console.log("complete");
});


>
1 hannie 18 2020-08-03T06:13:14.007Z
2 hannie 18 2020-08-03T06:13:15.001Z
3 hannie 18 2020-08-03T06:13:16.004Z
time: 3005.195ms

AsyncParallelHook (Promise):并行异步执行

/**
 * 并行异步执行
*/

class AsyncParallelHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName插件名称,只是起到注释作用
  tapPromise(taskName,task){
		this.tasks.push(task)
	}
	promise(...args){

		// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
		args = args.slice(0, this.instanseArgs.length); //["name", "age"]
  
  
    return Promise.all(this.tasks.map((task)=>{
      return task(...args)
    }))
    // return Promise.all(this.tasks.map(task => task(...args)));
	}

}


// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 注册事件
console.time("time");
asyncParallelHook.tapPromise("1", (name, age) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncParallelHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 触发事件,让监听函数执行
asyncParallelHook.promise("hannie", 18).then(ret => {
    console.log(ret);
});

>
1 hannie 18 2020-08-03T06:22:47.198Z
2 hannie 18 2020-08-03T06:22:48.198Z
3 hannie 18 2020-08-03T06:22:49.197Z
time: 3004.218ms
[ '1', '2', '3' ]

AsyncSeriesHook (setTimeout):串行异步执行

/**
 * 串行异步执行
 * 一步一步执行
*/

class AsyncSeriesHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName插件名称,只是起到注释作用
    tapAsync(taskName,task){
        this.tasks.push(task)
    }
    callAsync(...args){
        let finalCallback = args.pop();
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.instanseArgs.length); //["name", "age"]

        // 递归next
        let i = 0;
        let next = () => {
            let task = this.tasks[i++];
            task ? task(...args, next) : finalCallback();
        }
        next()

    // this.tasks.forEach((task,index)=>{
    //   task(...args, next)
    // })

    }

}

// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 注册事件
console.time("time");
asyncSeriesHook.tapAsync("1", (name, age, next) => {
    setTimeout(() => {
        console.log("1", name, age, new Date());
        next();
    }, 1000);
});

asyncSeriesHook.tapAsync("2", (name, age, next) => {
    setTimeout(() => {
        console.log("2", name, age, new Date());
        next();
    }, 2000);
});

asyncSeriesHook.tapAsync("3", (name, age, next) => {
    setTimeout(() => {
        console.log("3", name, age, new Date());
        next();
        console.timeEnd("time");
    }, 3000);
});

// 触发事件,让监听函数执行
asyncSeriesHook.callAsync("hannie", 18, () => {
    console.log("complete");
});

> 
1 hannie 18 2020-08-03T06:32:51.094Z
2 hannie 18 2020-08-03T06:32:53.103Z
3 hannie 18 2020-08-03T06:32:56.106Z
complete
time: 6015.353ms

AsyncSeriesHook (Promise): 串行异步执行

/**
 * 串行异步执行
*/

class AsyncSeriesHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
    // taskName插件名称,只是起到注释作用
    tapPromise(taskName,task){
		this.tasks.push(task)
	}
	promise(...args){
		// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
		args = args.slice(0, this.instanseArgs.length); //["name", "age"]
        let [first, ...others] = this.tasks;
        return others.reduce((promise, task) => {    
        return promise.then(() => task(...args));
        }, first(...args));

	}

}

// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 注册事件
console.time("time");
asyncSeriesHook.tapPromise("1", (name, age) => {
    console.log( name, age);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncSeriesHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncSeriesHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 触发事件,让监听函数执行
asyncSeriesHook.promise("hannie", 18).then(ret => {
    console.log(ret);
});

> 
1 hannie 18 2020-08-03T06:42:05.558Z
2 hannie 18 2020-08-03T06:42:07.565Z
3 hannie 18 2020-08-03T06:42:10.567Z
time: 6016.385ms
3

www.jianshu.com/p/273e1c990…