tapable最详细源码分析及手写实现

tapable整体介绍

tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。

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

这些钩子有同步钩子和异步钩子之分。
Sync-开头的是同步钩子,
Async-开头的是异步钩子。
异步钩子又分为异步串行钩子(AsyncSeries)和异步并行钩子(AsyncParallel)。
*Hook:基础钩子,单纯的调用注册的事件回调,并不关心其内部的运行逻辑
*BailHook:熔断钩子,当一个事件回调运行时返回的值不为undefined时,停止后面事件回调的执行
*WaterfallHook: 瀑布钩子,如果当前执行的事件回调返回值不为undefined时,那么就把下一个事件回调的第一个参数替换成这个值
*LoopHook:循环钩子,如果当前执行的事件回调的返回值不是undefined,重新从第一个注册的事件回调处执行,直到当前执行的事件回调没有返回值

在这里插入图片描述
在这里插入图片描述

使用方法

1. install
npm install --save tapable
2. 使用
  1. 实例化hook,比如new SyncHook()
  2. 注册回调函数 .tap(‘vue’, () => {})
  3. 触发回调: .call()
const {
    SyncHook,
} = require("tapable");
// 实例化hook
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 注册回调事件
hook.tap("vue", function (name) {
	console.log("vue", name);
	return "vue-book";
});

hook.tap("react", function (name) {
	console.log("react:", name);
	return "react";
});
// 触发回调
hook.call('ac')

源码分析

  1. new SyncHook():
    首先看一下SyncHook类的源码
    ./lib/SyncHook.js
function SyncHook(args = [], name = undefined) {
	const hook = new Hook(args, name);
	hook.constructor = SyncHook;
	hook.tapAsync = TAP_ASYNC;
	hook.tapPromise = TAP_PROMISE;
	hook.compile = COMPILE;
	return hook;
}

SyncHook.prototype = null;

我们可以看到SyncHook是Hook的一种形式,可以把Hook看成抽象类,看一下基础Hook的实现:
./lib/Hook.js

...
class Hook {
	constructor(args = [], name = undefined) {
		this._args = args;
		this.name = name;
		this.taps = [];
		this.interceptors = [];
		this._call = CALL_DELEGATE;
		this.call = CALL_DELEGATE;
		this._callAsync = CALL_ASYNC_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this._promise = PROMISE_DELEGATE;
		this.promise = PROMISE_DELEGATE;
		this._x = undefined;

		this.compile = this.compile;
		this.tap = this.tap;
		this.tapAsync = this.tapAsync;
		this.tapPromise = this.tapPromise;
	}
	_createCall(type) {
		...
	}

	_tap(type, options, fn) {
		...
	}
	_insert(item) {
		...
	}
	...
}

Object.setPrototypeOf(Hook.prototype, null);

module.exports = Hook;

实例化之后hook实例具有如下属性和方法:
在这里插入图片描述
2. 注册回调函数

hook.tap("vue", function (name) {
	console.log("vue", name);
	return "vue-book";
});

hook.tap("react", function (name) {
	console.log("react:", name);
	return "react";
});

执行这一步的,会直接执行hook的tap方法,hook的tap方法我们看到在Hook类实现的
./lib/Hook.js

	_tap(type, options, fn) {
		if (typeof options === "string") {
			options = {
				name: options.trim()
			};
		} else if (typeof options !== "object" || options === null) {
			throw new Error("Invalid tap options");
		}
	    ...
		this._insert(options);
	}

	tap(options, fn) {
		this._tap("sync", options, fn);
	}

	tapAsync(options, fn) {
		this._tap("async", options, fn);
	}

	tapPromise(options, fn) {
		this._tap("promise", options, fn);
	}

tap方法会执行_insert方法

	_insert(item) {
		...
		this.taps[i] = item;
	}

至此,回调函数注册完毕,结果如下:
在这里插入图片描述
3. 调用回调函数 hook.call()

在这里插入图片描述
执行hook.call()会执行下面代码(./lib/Hook.js):

const CALL_DELEGATE = function(...args) {
	this.call = this._createCall("sync");
	return this.call(...args);
};

接着执行_createCall方法(./lib/Hook.js)

_createCall(type) {
	return this.compile({
		taps: this.taps,
		interceptors: this.interceptors,
		args: this._args,
		type: type
	});
}

然后会执行compile方法,compile方法在SyncHook里面定义的(./lib/SyncHook.js)

const COMPILE = function(options) {
	factory.setup(this, options);
	return factory.create(options);
};

这里的factory指的是什么呢?接着向下看:

class SyncHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

const factory = new SyncHookCodeFactory();

factory是SyncHookCodeFactory的实例,SyncHookCodeFactory这类继承了HookCodeFactory,HookCodeFactory这个类就实现了根据不同的参数生成不同的函数体,所以我们执行compile中的factory.setup()方法走到了HookCodeFactory中的setup(./lib/HookCodeFactory.js)

setup(instance, options) {
	instance._x = options.taps.map(t => t.fn);
}

这个方法就是把我们注册的回调函数赋给了this._x,此时的hook实例如下:
在这里插入图片描述
接着执行factory.create(),这个方法通过new Function()根据不同的参数生成不同的函数体,

create(options) {
		this.init(options);
		let fn;
		switch (this.options.type) {
			case "sync":
				fn = new Function(
					this.args(),
					'"use strict";\n' +
						this.header() +
						this.contentWithInterceptors({
							onError: err => `throw ${err};\n`,
							onResult: result => `return ${result};\n`,
							resultReturns: true,
							onDone: () => "",
							rethrowIfPossible: true
						})
				);
				break;
			case "async":
				fn = new Function(
					this.args({
						after: "_callback"
					}),
					'"use strict";\n' +
						this.header() +
						this.contentWithInterceptors({
							onError: err => `_callback(${err});\n`,
							onResult: result => `_callback(null, ${result});\n`,
							onDone: () => "_callback();\n"
						})
				);

				break;
			case "promise":
				let errorHelperUsed = false;
				const content = this.contentWithInterceptors({
					onError: err => {
						errorHelperUsed = true;
						return `_error(${err});\n`;
					},
					onResult: result => `_resolve(${result});\n`,
					onDone: () => "_resolve();\n"
				});
				let code = "";
				code += '"use strict";\n';
				code += this.header();
				code += "return new Promise((function(_resolve, _reject) {\n";
				if (errorHelperUsed) {
					code += "var _sync = true;\n";
					code += "function _error(_err) {\n";
					code += "if(_sync)\n";
					code +=
						"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
					code += "else\n";
					code += "_reject(_err);\n";
					code += "};\n";
				}
				code += content;
				if (errorHelperUsed) {
					code += "_sync = false;\n";
				}
				code += "}));\n";
				fn = new Function(this.args(), code);
				break;
		}
		this.deinit();
		console.log(fn.toString())
		return fn;
	}

最终create方法返回的函数是:

function anonymous(arg1, arg2, arg3) {
	"use strict";
	var _context;
	var _x = this._x;
	var _fn0 = _x[0];
	_fn0(arg1, arg2, arg3);
	var _fn1 = _x[1];
	_fn1(arg1, arg2, arg3);
}

这个也就是this._createCall()的结果,然后执行this.call()调用我们这个函数。

const CALL_DELEGATE = function(...args) {
	this.call = this._createCall("sync");
	return this.call(...args);
};

最终得出结果:

vue ac
react: ac

至此源码解析完毕,下面我们看一下同步和异步怎么实现的

手写实现同步异步

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
手写一个webpack插件,你可以按照以下步骤进行操作: 1. 首先,了解webpack自身插件的调用流程。你可以参考webpack内部插件的实现方式来理解。其中一个例子是NodeEnvironmentPlugin插件。 2. 创建一个新的插件文件,比如TestPlugin.js,并在文件中定义一个插件类,比如TestPlugin。 3. 在插件类中使用tapable实例的方法,在webpack的emit钩子函数执行时触发我们定义的函数。你可以使用compiler.hooks.emit.tap方法来绑定钩子函数。 4. 在钩子函数内部,使用compiler.outputFileSystem.writeFile方法创建一个自定义的文件,并在文件中写入你想要的内容。比如你可以写入一句话,如"//我们的第一个webpack插件!"。 5. 在webpack配置文件中引入你的插件,并将它作为一个实例加入到plugins数组中。 以下是一个示例的webpack配置文件,展示了如何引入TestPlugin插件: ``` const path = require('path'); const TestPlugin = require('./TestPlugin'); module.exports = function() { return { mode: 'development', entry: ['./src/app.js'], output: { path: path.join(__dirname, 'out'), filename: 'out.[name].js' }, plugins: [ new TestPlugin() ] }; }; ``` 通过以上步骤,你就可以手写一个简单的webpack插件了。这个插件会在webpack输出的文件夹中创建一个自定义的文件,并在文件中写入一句话。你可以根据自己的需求,进一步扩展和定制插件的功能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值