.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个比较复杂,干讲也不知道怎么解释,等到后面的代码有用到的时候再组具体分析。