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介绍的更多相关文章

  1. .17-浅析webpack源码之compile流程-入口函数run

    本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now( ...

  2. .3-浅析webpack源码之预编译总览

    写在前面: 本来一开始想沿用之前vue源码的标题:webpack源码之***,但是这个工具比较巨大,所以为防止有人觉得我装逼跑来喷我(或者随时鸽),加上浅析二字,以示怂. 既然是浅析,那么案例就不必太 ...

  3. .34-浅析webpack源码之事件流make(3)

    新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...

  4. 浅析libuv源码-node事件轮询解析(3)

    好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...

  5. 从Webpack源码探究打包流程,萌新也能看懂~

    简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...

  6. .30-浅析webpack源码之doResolve事件流(1)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

  7. .30-浅析webpack源码之doResolve事件流(2)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

  8. (3.1)mysql基础深入——mysql二进制与源码目录结构介绍

    (3.1)mysql基础深入——mysql二进制与源码目录结构介绍 关键字:二进制目录结构,源码目录结构(编译安装目录结构) 1.二进制安装程序目录结构 [1] BIN -- mysql的可执行文件( ...

  9. webpack源码-依赖收集

    webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compi ...

随机推荐

  1. SpringBoot2.x配置JsonRedisSerializer

    @Configurationpublic class SpringCacheRedisConfig { @Bean public RedisCacheManager cacheManager(Redi ...

  2. 14. The Realities of Telecommuting 远程办公的现状

    14. The Realities of Telecommuting 远程办公的现状 (1) Telecommuting——substituting the computer for the trip ...

  3. 服务器安装ubuntu 14.04 server,开机启动屏幕不停滚动错误WRITE SAME failed. Manually zeroing

    昨天给服务器上安装了一个Ubuntu-14.04-server系统,安装完成后系统可以正常启动,但屏幕上一直滚动着一个错误,sda1:WRITE SAME failed. Manually zeroi ...

  4. ACM_贪心法_queue_Fence Repair

    题目如下,来源POJ Farmer John wants to repair a small length of the fence around the pasture. He measures t ...

  5. python bittorrent 使用与学习

    更新于20171128 关于这个p2p的技术,阿里开源了蜻蜓,大家可以看看,感觉应该比这个要好用,而且文档也全. 前言 最近在学习python的p2p协议,发现网上找不到好的文章,又仔细找了找终于找到 ...

  6. 基于esp32的IIC通讯

    本文源码地址在:http://download.csdn.net/download/noticeable/9962029 IIC 通讯应该是当代比较常用的几种通讯方式之一,其无需特殊的IO接口,连线方 ...

  7. JMeter 教程汇总链接

    http://www.360doc.com/content/14/0318/23/16361380_361732630.shtml 可以作为入门系列教程. 尽管网页也给出了视频链接,但是我不建议看视频 ...

  8. CentOS 解决vim乱码问题

    今天在服务器安装了任务调度工具(TaskCTL) 发现是乱码的,看了官方文档说的办法也没有处理成功,可能由于他们已经有一段时间没有维护这个版本了.(以前提供的免费版本) 后来发现CentOS的Vim的 ...

  9. Android数据存储之SharedPreferences使用

    SharedPreferences是Android中一种轻型的数据存储类.本质上是基于XML文件进行存储Key-Value键值对的数据,生成的XML文件的目录在/data/data/包名/Shared ...

  10. sublime text 3中安装ctags支持函数跳转,安装convertToUtf8支持中文步骤[工具篇]

    sublime text x是个很不错的编辑器,但是各种插件都需要自己安装,有时也有点不方便,尤其是自己还不不知道怎么安装的时候.开发中经常用到的,函数跳转,就是一个比较难安装的东西,记录如下(系统为 ...