完成webpack默认参数注入后,下一步虽然是 new Compiler() ,可是这东西不是一下能够讲完的,复杂的一批。webpack
不如先从工具入手,分块讲解compiler,首先来看看事件流执行器Tapable工具。web
tips:这里的Tapable源码来自于webpack内部自带的tapable,若是经过npm i tapable查看会发现彻底不同。npm
出现地点以下:app
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); };
很是简单,用一个小案例说明:ui
const Tapable = require('./Tapable'); var sourObj = { ownKey: null }; Tapable.mixin(sourObj);
经过mixin方法的调用,sourObj会变成:this
至于另一个工具函数,单独讲没有任何意义,因此在用到的时候再作分析。spa
接下来分析原型函数,其中有两个函数是基本操做函数,其他的都是用不一样方式执行指定名字的事件流。
先看基本的。
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个比较复杂,干讲也不知道怎么解释,等到后面的代码有用到的时候再组具体分析。