.8-浅析webpack源码之Tapable介绍
Tapable工具
完成webpack默认参数注入后,下一步虽然是 new Compiler() ,但是这东西不是一下可以讲完的,复杂的一批。
不如先从工具入手,分块讲解compiler,首先来看看事件流执行器Tapable工具。
tips:这里的Tapable源码来自于webpack内部自带的tapable,如果通过npm i tapable查看会发现完全不一样。
出现地点如下:
class Compiler extends Tapable {
// ...
}
class Compilation extends Tapable {
// ...
}
可以看到核心对象基本上都继承于该工具,用于处理事件流,Tapable源码整理如下(用ES6的class复写了一遍,看起来比较清晰):
// 原型方法混入
Tapable.mixin = function mixinTapable(pt) { /**/ }; function copyProperties(from, to) { /**/ } // 服务于某些apply
function fastFilter(fun /*, thisArg*/ ) { /*...*/ } class Tapable {
constructor() {
this._plugins = {};
}
plugin(name, fn) { /*...*/ }
hasPlugins(name) { /*...*/ }
apply() { /*...*/ }
applyPlugins(name) { /*...*/ }
applyPlugins0(name) { /*...*/ }
applyPlugins1(name, param) { /*...*/ }
applyPlugins2(name, param1, param2) { /*...*/ }
applyPluginsWaterfall(name, init) { /*...*/ }
applyPluginsWaterfall0(name, init) { /*...*/ }
applyPluginsWaterfall1(name, init, param) { /*...*/ }
applyPluginsWaterfall2(name, init, param1, param2) { /*...*/ }
applyPluginsBailResult(name) { /*...*/ }
applyPluginsBailResult1(name, param) { /*...*/ }
applyPluginsBailResult2(name, param1, param2) { /*...*/ }
applyPluginsBailResult3(name, param1, param2, param3) { /*...*/ }
applyPluginsBailResult4(name, param1, param2, param3, param4) { /*...*/ }
applyPluginsBailResult5(name, param1, param2, param3, param4, param5) { /*...*/ }
applyPluginsAsyncSeries(name) { /*...*/ }
applyPluginsAsyncSeries1(name, param, callback) { /*...*/ }
applyPluginsAsyncSeriesBailResult(name) { /*...*/ }
applyPluginsAsyncSeriesBailResult1(name, param, callback) { /*...*/ }
applyPluginsAsyncWaterfall(name, init, callback) { /*...*/ }
applyPluginsParallel(name) { /*...*/ }
applyPluginsParallelBailResult(name) { /*...*/ }
applyPluginsParallelBailResult1(name, param, callback) { /*...*/ }
} module.exports = Tapable;
构造函数只是简单的声明了一个_plugins对象,外部函数包括有一个混入函数、一个工具函数,原型上则是大量apply...
先从简单的入手,看看混入函数:
// 将Tapable原型方法复制到指定对象中
function copyProperties(from, to) {
for (var key in from)
to[key] = from[key];
return to;
}
// 传入对象
Tapable.mixin = function mixinTapable(pt) {
copyProperties(Tapable.prototype, pt);
};
非常简单,用一个小案例说明:
const Tapable = require('./Tapable');
var sourObj = { ownKey: null };
Tapable.mixin(sourObj);
通过mixin方法的调用,sourObj会变成:

至于另外一个工具函数,单独讲没有任何意义,所以在用到的时候再做分析。
接下来分析原型函数,其中有两个函数是基本操作函数,其余的都是用不同方式执行指定名字的事件流。
先看基本的。
基本操作函数
plugin
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);
};
这是Tapable最基本的操作,给指定的事件流注入新函数。
hasPlugins
Tapable.prototype.hasPlugins = function hasPlugins(name) {
// 尝试获取对应事件流
var plugins = this._plugins[name];
// 存在事件流且有可执行函数
return plugins && plugins.length > 0;
};
has判断,没啥好讲的。
事件流执行
接下来看看所有的事件流执行方式。(源码中尽量使用ES6进行改写以增强可读性,留个注释在那)
首先是一个比较特殊的原型函数:
apply
Tapable.prototype.apply = function apply(...fns) {
// 遍历所有参数并执行
for (var i = 0; i < fns.length; i++) {
fns[i].apply(this);
}
};
该函数并不直接关联于_plugins对象,而是按照参数传入顺序依次执行。
applyPlugins
这个方式非常简单暴力,依次遍历指定name的事件流,不同名字的函数可接受参数数量不一样。
// 不接受传参
Tapable.prototype.applyPlugins0 = function applyPlugins0(name) {
var plugins = this._plugins[name];
if (!plugins) return;
for (var i = 0; i < plugins.length; i++)
plugins[i].call(this);
};
// 接受一个参数
Tapable.prototype.applyPlugins1 = function applyPlugins1(name, param) {
var plugins = this._plugins[name];
if (!plugins) return;
for (var i = 0; i < plugins.length; i++)
plugins[i].call(this, param);
};
// 接受两个参数
Tapable.prototype.applyPlugins2 = function applyPlugins2(name, param1, param2) {
var plugins = this._plugins[name];
if (!plugins) return;
for (var i = 0; i < plugins.length; i++)
plugins[i].call(this, param1, param2);
};
// 接受任意数量参数
Tapable.prototype.applyPlugins = function applyPlugins(name, ...args) {
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);
};
语义化满分,0代表不接受参数,1代表1个...而s代表任意数量的参数。
applyPluginsWaterfall
这种方式的特点是:事件流执行过程中,每一次执行的返回值会作为下一次的参数(仅限于第一个参数)。
Tapable.prototype.applyPluginsWaterfall0 = function applyPluginsWaterfall0(name, init) {
var plugins = this._plugins[name];
if (!plugins) return init;
var current = init;
for (var i = 0; i < plugins.length; i++)
current = plugins[i].call(this, current);
return current;
};
// ...1
// ...2
Tapable.prototype.applyPluginsWaterfall = function applyPluginsWaterfall(name, init, ...args) {
if (!this._plugins[name]) return init;
// var args = Array.prototype.slice.call(arguments, 1);
var plugins = this._plugins[name];
var current = init;
for (var i = 0; i < plugins.length; i++) {
current = plugins[i].call(this, current, ...args);
}
return current;
};
applyPluginsBailResult
这种方式的特点是:事件流执行过程中,返回第一个不是undefined的值,后续函数不执行。
Tapable.prototype.applyPluginsBailResult = function applyPluginsBailResult(name, ...args) {
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);
if (typeof result !== "undefined") {
return result;
}
}
};
// 1,2,3,4,5
applyPluginsAsync...
带有Async的均为异步调用方式,特点是事件流会在回调中依次进行,区别主要在于回调函数的参数处理,具体的使用方式还需要在实际应用中来看。
Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsyncSeries(name, ...args) {
// var args = Array.prototype.slice.call(arguments, 1);
// 最后一个参数为回调函数 其余为普通参数
var callback = args.pop();
var plugins = this._plugins[name];
if (!plugins || plugins.length === 0) return callback();
var i = 0;
// var _this = this;
// 包装
args.push(copyProperties(callback, (err) => {
if (err) return callback(err);
i++;
if (i >= plugins.length) {
return callback();
}
plugins[i].apply(this, args);
}));
// 内部继续使用此方式可依次执行事件流
plugins[0].apply(this, args);
};
// ..1
// applyPluginsAsyncSeriesBailResult => 回调函数传了参数就直接执行回调并返回终止事件流
// ..1
// applyPluginsAsyncWaterfall => 回调函数每次取给定的参数
剩下的3个比较复杂,干讲也不知道怎么解释,等到后面的代码有用到的时候再组具体分析。
.8-浅析webpack源码之Tapable介绍的更多相关文章
- .17-浅析webpack源码之compile流程-入口函数run
本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now( ...
- .3-浅析webpack源码之预编译总览
写在前面: 本来一开始想沿用之前vue源码的标题:webpack源码之***,但是这个工具比较巨大,所以为防止有人觉得我装逼跑来喷我(或者随时鸽),加上浅析二字,以示怂. 既然是浅析,那么案例就不必太 ...
- .34-浅析webpack源码之事件流make(3)
新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...
- 浅析libuv源码-node事件轮询解析(3)
好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...
- 从Webpack源码探究打包流程,萌新也能看懂~
简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...
- .30-浅析webpack源码之doResolve事件流(1)
这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...
- .30-浅析webpack源码之doResolve事件流(2)
这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...
- (3.1)mysql基础深入——mysql二进制与源码目录结构介绍
(3.1)mysql基础深入——mysql二进制与源码目录结构介绍 关键字:二进制目录结构,源码目录结构(编译安装目录结构) 1.二进制安装程序目录结构 [1] BIN -- mysql的可执行文件( ...
- webpack源码-依赖收集
webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compi ...
随机推荐
- Mysql知识点个人整理
1.概念 数据库:保存有组织的数据的容器. 表: 某种特定类型数据的结构化清单 模式:关于数据库和表的布局和特性的信息?(有时指数据库) 主键: primary key 一个列或一组列,其值能唯一区分 ...
- Day03(黑客成长日记)
#猜数游戏 != 是不等于 # import random # secret = random.randint(,) # gwea = # tries = # : # guess = int(inpu ...
- [f]计时器
// 计时器 function Timer(ele) { this._mStr = ''; this._sStr = ''; this._m = 0; this._s = 0; this._setTi ...
- html-minifier中文文档
HTMLMinifier是一个高度可配置的.经过良好测试的.基于javascript的HTML缩小器.参见相应的博客文章,了解它的工作原理.每个选项的描述.测试结果和结论.在线测试套件.还可以看到相应 ...
- MySQL数据库插入中文乱码解决方法
在mysql数据库中,插入中文数据时,会出现乱码的现象. 我的测试方法: 首先用Navicat for MySql 插入一行数据,带有中文的. 再用mysql命令行来查看插入的数据,看是否出现乱码. ...
- bzoj3929(sam)
因为题目中树的特殊性暴力dfs建sam就好了.然后sam有一个有意思的性质是一个点代表的子串个数等于mx[i]-mx[fail[i]],至于为什么,我不会严谨的证明,但想想还是可以的,就是当前串的所有 ...
- 卷积在深度学习中的作用(转自http://timdettmers.com/2015/03/26/convolution-deep-learning/)
卷积可能是现在深入学习中最重要的概念.卷积网络和卷积网络将深度学习推向了几乎所有机器学习任务的最前沿.但是,卷积如此强大呢?它是如何工作的?在这篇博客文章中,我将解释卷积并将其与其他概念联系起来,以帮 ...
- 基于ESP32的uart通讯
本文源码地址为:http://download.csdn.net/download/noticeable/9961054 ESP32上有三个UART通讯接口,设备号,从0~2,即UART0,UART1 ...
- 文件描述符fd、文件指针fp和vfork()
1. fd:在形式上是一个非负整数.实际上他是一个索引值.指向kernal为每一个进程所维护的该进程打开文件的记录表. 当程序打开一个文件或者创建一个新文件的时候kernal向进程返回一个文件描述符. ...
- Golang 调用 Python 代码
go 中的 cgo 模块可以让 go 无缝调用 c 或者 c++ 的代码,而 python 本身就是个 c 库,自然也可以由 cgo 直接调用,前提是指定正确的编译条件,如 Python.h 头文件( ...