webpack编译过程中的重要“桥梁”-tapable

发布于:2023-01-16 ⋅ 阅读:(213) ⋅ 点赞:(0)

1.webpack中的Compiler

Compiler依赖的npm包,如下:
在这里插入图片描述

const isSorted = array => {}
const sortObject = (obj, keys) => {}
class Compiler {}
module.exports = Compiler;

这三个部分就是Complier的主干。Compiler的机构总体如下:

在这里插入图片描述
svg矢量图下载

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中的实现,关键是两个地方,如上述代码中的tapcall

单步调试进入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编译过程的原理说明。

附:源码附件

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到