Tapable 0.2.8 入门

【原文:Tapable 0.2.8 入门

tapable是webpack的核心框架(4.0以上版本的API已经发生了变化),是一个基于事件流的框架,或者叫做发布订阅模式,或观察者模式,webpack的整个生命周期及其开放的自定义插件系统都离不开tapable的支持,研究其运行原理是阅读webpack源代码的第一步。

Tapable是一个小型库,允许你添加和应用插件到一个javascript模块。它可以被继承或混入其他模块。除可以定制事件发射和操作,还可以通过回调参数访问事件的“排放者”或“生产者”。

Tapable 有四组成员函数:

  • plugin(name:string, handler:function):这允许自定义插件注册到Tapable实例的事件中。这起到类似on()的方法EventEmitter,这是用于注册一个处理程序/侦听器当信号/事件发生做一些事情。
  • apply(…pluginInstances: (AnyPlugin|function)[]):AnyPlugin应该是一个具有方法的类(或者很少,是一个对象)apply,或者只是一个带有一些注册码的函数。此方法仅适用于插件的定义,以便真正的事件侦听器可以注册到Tapable实例的注册表中。
  • applyPlugins*(name:string, …):Tapable实例可以使用这些函数将特定哈希下的所有插件应用。这组方法就像使用各种策略精心控制事件发射的emit()方法EventEmitter。
  • mixin(pt: Object):一种简单的方法来将Tapable原型扩展为mixin而不是继承。

不同的applyPlugins*方法涵盖以下用例:

  • 插件可以串行运行。
  • 插件可以并行运行。
  • 插件可以一个接一个地运行,但从前一个插件(瀑布)获取输入。
  • 插件可以异步运行。
  • 放弃保释运行插件:也就是说,一旦一个插件返回非插件undefined,跳出运行流程并返回该插件的返回。这听起来像once()的EventEmitter,但是是完全不同的。

Tapable 介绍

var Tapable = require("tapable");

Tapable 是一个库,用于绑定和执行插件。

在使用上,你仅仅需要继承它

function MyClass() {
    Tapable.call(this)
}

MyClass.prototype = Object.create(Tapable.prototype)

MyClass.prototype.method = function() {}

或者复制它的属性到你的类中

function MyClass2() {
    EventEmitter.call(this);
    Tapable.call(this);
}

MyClass2.prototype = Object.create(EventEmitter.prototype);
Tapable.mixin(MyClass2.prototype);

MyClass2.prototype.method = function() {};

公开方法(Public functions)

apply

void apply(plugins: Plugin...)

Tapable.prototype.apply = function apply() {
    for(var i = 0; i < arguments.length; i++) {
        arguments[i].apply(this); // 遍历所有参数并执行
    }
};

通过arguments获得所有传入的插件对象,并调用插件对象的apply方法,更改上下文为当前this,执行插件。(Webpack的插件就是Tapable对象,因此必须要提供 apply 方法)

plugin

void plugin(names: string|string[], handler: Function)

Tapable.prototype.plugin = function plugin(name, fn) {
	if(Array.isArray(name)) {
		name.forEach(function(name) {
			this.plugin(name, fn);
		}, this);
		return;
	}
	if(!this._plugins[name]) this._plugins[name] = [fn];
	else this._plugins[name].push(fn);
};

names: 需要监听的事件名称,可以传入事件名称集合,也可以传入单个事件名称 handler: 事件的处理函数

tapable通过原型方法Tapable.prototype.plugin来注册事件监听。将回调函数按照事件名称进行归类存储,在tapable实例中统一调度管理。

受保护的方法 (Protected functions)

  • applyPlugins
  • applyPluginsWaterfall
  • applyPluginsBailResult
  • applyPluginsAsync
  • applyPluginsAsyncWaterfall
  • applyPluginsAsyncSeries
  • applyPluginsParallel
  • applyPluginsParallelBailResult
  • hasPlugins

tapable中的事件触发方式可以按命名分为如下几个大组:

  • waterfall方法会将上一个监听的执行结果传给下一个
  • bailResult方法只会执行到第一个返回结果不是undefined的事件流
  • Series方法会线性执行异步事件流,上一个结束后下一个才会开始
  • Parallel方法会并行执行所有异步监听

applyPlugins

void applyPlugins(name: string, args: any...)

Tapable.prototype.applyPlugins = function applyPlugins(name) {
	if(!this._plugins[name]) return;
	var args = Array.prototype.slice.call(arguments, 1); // 除名称以外的其他参数
	var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组
	for(var i = 0; i < plugins.length; i++)
		plugins[i].apply(this, args); // 依次执行指定name事件流的apply方法
};

触发事件name,传入参数args,并行的调用所有注册在事件name上的处理函数

applyPluginsWaterfall

any applyPluginsWaterfall(name: string, init: any, args: any...)

Tapable.prototype.applyPluginsWaterfall = function applyPlugins(name, init) {
	if(!this._plugins[name]) return init; // 如果指定事件没有注册事件流,则返回第2个参数(init)
	var args = Array.prototype.slice.call(arguments, 1); // 除name以外的其他参数
	var plugins = this._plugins[name]; // 查找事件流数组
	var current = init;
	for(var i = 0; i < plugins.length; i++)
		args[0] = current;
		current = plugins[i].apply(this, args); // 依次执行事件流的apply()方法, 传入的args是前执行返回值替换init初始值的参数
	return current;
};

触发事件name,串行的调用注册在事件name上的处理函数(先入先出),最先执行的处理函数传入init和args,后续的处理函数传入前一个处理函数的返回值和args,函数最终返回最后一个处理函数的返回结果

applyPluginsBailResult

any applyPluginsBailResult(name: string, args: any...)

Tapable.prototype.applyPluginsBailResult = function applyPluginsBailResult(name) {
	if(!this._plugins[name]) return;
	var args = Array.prototype.slice.call(arguments, 1); // 除名称以外的其他参数
	var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组
	for(var i = 0; i < plugins.length; i++) {
		var result = plugins[i].apply(this, args); // 依次执行事件流的apply()方法并取得返回值
		if(typeof result !== "undefined") { // 如果返回一个不为undefined的结果
			return result; // 则停止执行并将这个结果返回。
		}
	}
};

触发事件name,串行的调用注册在事件name上的处理函数(先入先出),传入参数args,如果其中一个处理函数返回值!== undefined,直接返回这个返回值,后续的处理函数将不被执行

applyPluginsAsync

void applyPluginsAsync(
	name: string,
	args: any...,
	callback: (err?: Error) -> void
)

// 异步执行监听回调的方法。这个方法是顺序执行,等到第一个插件执行结束后才会执行下一个插件
Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsync(name) {
	var args = Array.prototype.slice.call(arguments, 1); // 除名称以外的其他参数
	var callback = args.pop(); // 参数数组最后一个弹出,回调函数赋给callback
	var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组
	if(!plugins || plugins.length === 0) return callback(); // 监听事件name为空或没有没有注册事件流,执行回调函数
	var i = 0;
	var _this = this
	args.push(copyProperties(callback, function next(err) { // copyProperties将callback原型方法复制到next中并返回next
		if(err) return callback(err);
		i++;
		if(i >= plugins.length) {
			return callback();
		}
		plugins[i].apply(_this, args); // 将下一个插件当做回调函数传入第一个插件
	})); // 利用闭包实现了一个迭代器,变量i记录在applyPluginsAsync()方法中,并在回调中函数next( )中保持了对i的引用。
	plugins[0].apply(this, args);
};

function copyProperties(from, to) { // 将from原型方法复制到指定对象to中
	for(var key in from)
		to[key] = from[key];
	return to;
}

触发事件name,串行的调用注册在事件name上的处理函数(先入先出),倘若某一个处理函数报错,则执行传入的callback(err),后续的处理函数将不被执行,否则最后一个处理函数调用callback。

applyPluginsAsyncSeries

applyPluginsAsyncSeries(
	name: string,
	args: any...,
	callback: (err: Error, result: any) -> void
)

同applyPluginsAsync

applyPluginsAsyncWaterfall

applyPluginsAsyncWaterfall(
	name: string,
	init: any,
	callback: (err: Error, result: any) -> void
)
Tapable.prototype.applyPluginsAsyncWaterfall = function applyPluginsAsyncWaterfall(name, init, callback) {
	if(!this._plugins[name] || this._plugins[name].length === 0) return callback(null, init); // 监听事件name为空或没有没有注册事件流,执行回调函数
	var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组
	var i = 0;
	var _this = this
	var next = copyProperties(callback, function(err, value) {// copyProperties函数将callback函数加入了参数函数并返回参数函数
		if(err) return callback(err);
		i++;
		if(i >= plugins.length) {
			return callback(null, value);
		}
		plugins[i].call(_this, value, next);
	});
	plugins[0].call(this, init, next);
};

触发事件name,串行的调用注册在name上的处理函数(先入先出),第一个处理函数传入参数init,后续的函数依赖于前一个函数执行回调的时候传入的参数nextValue,倘若某一个处理函数报错,则执行传入的callback(err),后续的处理函数将不被执行,否则最后一个处理函数调用callback(value)

applyPluginsParallel

applyPluginsParallel(
	name: string,
	args: any...,
	callback: (err?: Error) -> void
)

Tapable.prototype.applyPluginsParallel = function applyPluginsParallel(name) {
	var args = Array.prototype.slice.call(arguments, 1);
	var callback = args.pop(); // 参数数组最后一个弹出,回调函数赋给callback
	if(!this._plugins[name] || this._plugins[name].length === 0) return callback();// 监听事件name为空或没有没有注册事件流,执行回调函数
	var plugins = this._plugins[name];
	var remaining = plugins.length;
	args.push(copyProperties(callback, function(err) {
		if(remaining < 0) return; // ignore
		if(err) {
			remaining = -1;
			return callback(err);
		}
		remaining--;
		if(remaining === 0) {
			return callback();
		}
	}));
	for(var i = 0; i < plugins.length; i++) {
		plugins[i].apply(this, args);
		if(remaining < 0) return;
	}
};

触发事件name,传入参数args,并行的调用所有注册在事件name上的处理函数,倘若任一处理函数执行报错,则执行callback(‘err‘),否则当所有的处理函数都执行完的时候调用callback()

applyPluginsParallel 主要功能和最简单的 applyPlugins 方法比较相似,无论如何都会让所有注册的插件运行一遍;只是相比 applyPlugins 多了一个额外的功能,它最后提供一个 callback 函数,这个 callback 的函数比较倔强,如果所有的插件x都正常执行,且最后都cb(),则会在最后执行callback里的逻辑;不过,一旦其中某个插件运行出错,就会调用这个callback(err),之后就算插件有错误也不会再调用该callback函数;

applyPluginsParallelBailResult

applyPluginsParallelBailResult(
	name: string,
	args: any...,
	callback: (err: Error, result: any) -> void
)
Tapable.prototype.applyPluginsParallelBailResult = function applyPluginsParallelBailResult(name) {
	var args = Array.prototype.slice.call(arguments, 1);
	var callback = args[args.length - 1];
	if(!this._plugins[name] || this._plugins[name].length === 0) return callback();
	var plugins = this._plugins[name];
	var currentPos = plugins.length;
	var currentError, currentResult;
	var done = [];
	for(var i = 0; i < plugins.length; i++) {
		args[args.length - 1] = (function(i) {
			return copyProperties(callback, function(err, result) {
				if(i >= currentPos) return; // ignore
				done.push(i);
				if(err || result) {
					currentPos = i + 1;
					done = done.filter(function(item) {
						return item <= i;
					});
					currentError = err;
					currentResult = result;
				}
				if(done.length === currentPos) {
					callback(currentError, currentResult);
					currentPos = 0;
				}
			});
		}(i));
		plugins[i].apply(this, args);
	}
};

触发事件name,串行的执行注册在事件name上的处理函数(先入先出),每个处理函数必须调用callback(err, result),倘若任一处理函数在调用callback(err, result)的时候,err!==undefined || result!==undefined,则callback将真正被执行,后续的处理函数则不会再被执行。

它的行为和 applyPluginsParallel 非常相似,首先无论如何都会让所有注册的插件运行一遍(根据注册的顺序);为了让 callback 执行,其前提条件是每个插件都需要调用 cb();但其中的 callback 只会执行一次(当传给cb的值不是undefined/null 的时候),这一次执行顺序是插件定义顺序有关,而跟每个插件中的 cb() 执行时间无关的;

hasPlugins

hasPlugins(
	name: string
)

Tapable.prototype.hasPlugins = function hasPlugins(name) {
	var plugins = this._plugins[name];
	return plugins && plugins.length > 0;
};

如果事件name已经被注册了,则返回true

使用案例

webpack的Tapable实例之一编译器负责编译webpack配置对象并返回一个编译实例。编译实例运行时,将创建所需的捆绑包。

node_modules/webpack/lib/Compiler.js

var Tapable = require("tapable"); // 引入Tapable

function Compiler() {
    Tapable.call(this); // 将Tapable中的this指向Compiler
}

Compiler.prototype = Object.create(Tapable.prototype); // Compiler 继承 Tapable

现在在编译器上编写一个插件,my-custom-plugin.js

function CustomPlugin() {} // 定义一个CustomPlugin类

CustomPlugin.prototype.apply = function(compiler) { // CustomPlugin的原型对象上添加apply入口
  compiler.plugin(‘emit‘, pluginFunction); // 传入compiler,并注册事件流pluginFunction的事件名称emit
}

编译器在其生命周期的适当位置通过执行插件, node_modules/webpack/lib/Compiler.js

this.apply*("emit",options) // will fetch all plugins under ‘emit‘ name and run them.


参考资料

原文地址:https://www.cnblogs.com/lilicat/p/10438261.html

时间: 2024-10-05 04:59:05

Tapable 0.2.8 入门的相关文章

NSIS 2.0界面快速入门

NSIS 2.0 版本支持定制的用户界面.所谓的 Modern UI(下称 MUI) 就是一种模仿最新的 Windows 界面风格的界面系统.MUI 改变了 NSIS 脚本的编写习惯,它使用 NSIS 的宏来表达,指定 MUI 的属性需要使用宏.所以,诸如 LicenseText, Icon, CheckBitmap, InstallColors 在 MUI 中失去意义. MUI 的内置向导页面 和安装程序有关的向导页面 MUI_PAGE_WELCOME 该向导页面显示欢迎信息 MUI_PAGE

seajs 2.3.0 傻瓜式入门

[seajs] 很久之前就想使用seajs,尝试了多次,可能是自己比较笨或者seaj的文档和例子太简单,总是无从下手, 这次自己搞了一个网站,引用多个js的时候需要减少http请求, 找了半天又找到seajs+spm,好吧,再来一次. [博文] 这个博文讲的很透彻,好像是seajs开发者写的,供参考,如果这个博文能看懂那就不必再往下看了, 地址:https://speakerdeck.com/lifesinger/seajs [傻瓜式入门] 如果还是看不懂,那就一步一步往下看吧. 说明: 我的例

Asp.Net MVC4.0 官方教程 入门指南之五--控制器访问模型数据

Asp.Net MVC4.0 官方教程 入门指南之五--控制器访问模型数据 在这一节中,你将新创建一个新的 MoviesController类,并编写代码,实现获取影片数据和使用视图模板在浏览器中展现影片数据的功能.在进行下步之前,点击“生成应用程序“对应用程序进行编译.右键单击Controllers文件夹,新建一个名为“MoviesController ”的控制器.在创建窗口各选项如下图所示 点击添加,将创建以下文件和文件夹: 项目的 Controllers 文件夹下新增MoviesContr

Asp.Net MVC4.0 官方教程 入门指南之四--添加一个模型

Asp.Net MVC4.0 官方教程 入门指南之四--添加一个模型 在这一节中,你将添加用于管理数据库中电影的类.这些类是ASP.NET MVC应用程序的模型部分. 你将使用.NET Framework框架下的实体框架(Entity Framework)数据访问技术,与模型类协同工作.实体框架(常简称为EF)支持一种称之为编码先行(Code First)的开发模式.编码先行使你通过编写简单的类(简称为POCO类,全称为"plain-old CLR objects."),来创建模型对象

Asp.Net MVC4.0 官方教程 入门指南之三--添加一个视图

Asp.Net MVC4.0 官方教程 入门指南之三--添加一个视图 在本节中,您需要修改HelloWorldController类,从而使用视图模板文件,干净优雅的封装生成返回到客户端浏览器HTML的过程. 您将创建一个视图模板文件,其中使用了ASP.NET MVC 3所引入的Razor视图引擎.Razor视图模板文件使用.cshtml文件扩展名,并提供了一个优雅的方式来使用C#语言创建所要输出的HTML.用Razor编写一个视图模板文件时,将所需的字符和键盘敲击数量降到了最低,并实现了快速,

Asp.Net MVC2.0 Url 路由入门---实例篇

本篇主要讲述Routing组件的作用,以及举几个实例来学习Asp.Net MVC2.0 Url路由技术. 接着上一篇开始讲,我们在Global.asax中注册一条路由后,我们的请求是怎么转到相应的View的呢?Controller和Action是怎么解析的?这就是Routing组件干的事情了. Routing的作用:它首先是获取到View传过来的请求,并解析Url请求中Controller和Action以及数据,其次他将识别出来的数据传递给Controller的Action(Controller

Asp.Net MVC4.0 官方教程 入门指南之二--添加一个控制器

Asp.Net MVC4.0 官方教程 入门指南之二--添加一个控制器 MVC概念 MVC的含义是 “模型-视图-控制器”.MVC是一个架构良好并且易于测试和易于维护的开发模式.基于MVC模式的应用程序包含: · Models: 表示该应用程序的数据并使用验证逻辑来强制实施业务规则的数据类. · Views: 应用程序动态生成 HTML所使用的模板文件. · Controllers: 处理浏览器的请求,取得数据模型,然后指定要响应浏览器请求的视图模板. 本系列教程,我们将覆盖所有这些概念,并告诉

.Net学习 第0季 学前入门

.Net 视频学习第0季 学前入门 .Net Framework 是 .Net平台中不可缺少的一部分,它提供了一个稳定的运行环境来保证我们基于.Net平台开发的各种应用能够正常地运行. C#是一种编程语言,可以开发基于.Net平台的应用.(.Net平台支持多种编程语言,但C#是主流) 桌面应用程序(winform),internet应用程序(ASP.NET),手机开发,Unity3D游戏开发或虚拟现实. .Net两种交互模式:C/S(客户端/服务器)和B/S(浏览器/服务器).桌面应用程序一般是

.Net Core 3.0 IdentityServer4 快速入门

原文:.Net Core 3.0 IdentityServer4 快速入门 .Net Core 3.0 IdentityServer4 快速入门 一.简介 IdentityServer4是用于ASP.NET Core的OpenID Connect和OAuth 2.0框架. 将IdentityServer4部署到您的应用中具备如下特点: 1).认证服务 2).单点登陆 3).API访问控制 4).联合网关 5).专注于定制 6).成熟的开源系统 7).免费和商业支持 二.整体部署 目前大多数的应用