1.webpack中的Compiler
Compiler依赖的npm包,如下:
const isSorted = array => {}
const sortObject = (obj, keys) => {}
class Compiler {}
module.exports = Compiler;
这三个部分就是Complier的主干。Compiler的机构总体如下:
2.Compiler构造函数中用到的tapable的不同类型的钩子
从constructor讲起,在Complier的构造函数中首先看到的是Object.freeze,freeze直译过来就是“冻结、冻僵”的意思。这里的Object.freeze方法顾名思义是冻结一个对象。冻结后不能被修改。如下图:
在构造函数中初始化对象的属性对应如下几种类型:
- SyncHook
- SyncBailHook
- AsyncSeriesHook
- SyncHook
- AsyncParallelHook
这几个方法都是tapable的内容,所以如果深入webpack的Compiler过程就需要跨过tapable的门槛。
3.tapable
在npm中tapable如下:
(图片来自:https://www.npmjs.com/package/tapable)
4.tapable实例及SyncHook的实现过程
结合官方给出的示例,改造部分内容如下:
const path=require('path');
console.log('-----learn tapable.start--------')
const {
SyncHook
} = require("tapable");
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"],'askcomputer'),
brake: new SyncHook()
};
}
setSpeed(newSpeed) {
this.hooks.accelerate.call(newSpeed);
}
setWarningLampOn(){
this.hooks.brake.call();
}
}
const myCar = new Car();
const warningLamp={
on:function(){
console.log('------警示灯开启--------');
}
}
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
myCar.setSpeed(20);
myCar.setWarningLampOn();
console.log('-----learn tapable.end--------')
module.exports={
entry:'./main.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'../dist')
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
}
}
(说明:因为主线是webpack,这里只是对tapable的内容介绍,用于辅助webpack的学习,所以这里把示例代码融合进了 webpack.config.js
使得可以在vscode下基于node环境调试。)
上述代码运行调试环境后输出如下:
在官方的说明把它解释为钩子,实际上在笔者看来,它就是我们常见的基于观察者模式实现的事件监听机制。这么解释的话可能会更容易让人理解。
对于tapable中的实现,关键是两个地方,如上述代码中的tap
和 call
。
单步调试进入tapable
下的 Hook.js
中代码如下:
_tap(type, options, fn) {
...
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tap(options, fn) {
this._tap("sync", options, fn);
}
通过insert方法,返回得到this.taps的数组,得到的结果如下:
紧接着查看,通过call
调用的过程,代码中的方式是通过 myCar.setWarningLampOn
调用 CALL_DELEGATE
并且传递给 _createCall
。
//Hook.js
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
在_createCall
中就再次用到了构造对象的过程中,初始化时生成的taps。
关键是要看这个过程中的 taps 是如何调用的, 这个比较关键。
//SyncHook.js
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
//HookCodeFactory.js
setup(instance,options)
{
//options.taps是在初始化构造过程中添加的tap方法数组,下面这一行通过数组的map方法过滤返回对应的fn
instance._x=options.taps.map(t=>t.fn)
}
所以这里得到的instance._x
就是taps
中对应的fn
。
setup
安装完成后,下一步:factory.create(options)
:
//HookCodeFactory.js
class HookCodeFactory {
...
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
...
}
通过使用Function构造一个新的函数。第一个参数是函数的参数,第二个参数是函数的方法体。其中header()
方法返回的内容如下:
var _context;var _x = this._x;
然后通过 contentWithInterceptors
->content
->callTapSeries
->callTap
,得到如下内容:
对应的匿名函数格式化后,如下:
(function anonymous() {
"use strict";
var _context;
var _x = this._x; //这里的this._x就与上文中的 instance._x=options.taps.map(t=>t.fn) 相对应
var _fn0 = _x[0];
_fn0();
})
到这就完成了一次this.hooks.brake.call();
对应的函数调用。tapable中的 synchook 的整个环节就是这样一个流程。它相当于实现了一次事件监听机制。中间使用 Hook 这个钩子做为观察者进行观察。
本篇通过对tapable通过SyncHook的应用,说明了它从 tap 到 call 之间发生的过程。以此为基础,下一篇继续回归webpack的主线,继续webpack编译过程的原理说明。