webpack 底层核心是一个独立的工具库tapable
webpack编译过程,可以看作 事件驱动型事件工作机制
核心的两个: 负责编译的complier
,负责创建bundles的compilation
都是tapable的实例对象
- 配置初始化
- 内容编译
- 输出编译后内容
tapable工作流程
- 实例化hook注册事件监听
- 通过hook触发事件监听
- 执行懒编译生成的可执行代码
hook本身是实例对象,在tapable库中存在几种不同类,每个类实例都是一种hook实例(钩子),不同钩子拥有不同执行特点,
从执行机制将hook分为同步和异步,异步分为并行串行两种模式
Hook不同机制执行特点
- Hook:普通钩子,监听器之间互相独立不干扰
- BailHook:熔断钩子,某个监听返回非undefined时后续不执行
- WaterfallHook:瀑布钩子,上一个监听的返回值可传递至下一个
- LoopHook:循环钩子,如果当前未返回false则一直执行
总结:
- tapable是个库,内部提供不同类,可以实例化出不同hook
- hook分为同步异步两类,无论哪类都包含上面四个执行特点
tapable库同步钩子
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
tapable库异步串行钩子
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
tapable库异步并行钩子
- AsyncParallelHook
- AsyncParallelBailHook
同步钩子使用
安装:yarn add tapable --dev
使用:导入->实例化->添加监听->触发监听
测试SyncHook
// 导入
const { SyncHook } = require('tapable')
// 实例化
let hook = new SyncHook(['name','age'])
// 添加事件监听
hook.tap('fn1',function(name,age){
// 钩子监听触发时会执行此函数体
console.log("SyncHook:fn1-->",name,age);
})
// 触发监听函数
hook.call('mcgee',18)
流式监听,可以定义多个监听事件依次处理内容
const { SyncHook } = require('tapable')
let hook = new SyncHook(['name','age'])
// 添加事件监听
hook.tap('fn1',function(name,age){
console.log("SyncHook:fn1-->",name,age); // SyncHook:fn1--> mcgee 18
})
hook.tap('fn2',function(name,age){
console.log("SyncHook:fn2-->",name+"爸爸",age+30); // SyncHook:fn2--> mcgee爸爸 48
})
hook.call('mcgee',18)
测试SyncBailHook
注意fn1
return了非undefined
的值,因此fn2
不会执行,熔断的含义
const { SyncBailHook } = require('tapable')
let hook = new SyncBailHook(['name','age'])
hook.tap('fn1',function(name,age){
console.log("SyncBailHook:fn1-->",name,age);
return 1;
})
hook.tap('fn2',function(name,age){
console.log("SyncBailHook:fn2-->",name+"爸爸",age+30);
})
hook.call('mcgee',18)
测试SyncWaterfallHook
返回值会被下一个监听获取
const { SyncWaterfallHook } = require('tapable')
let hook = new SyncWaterfallHook(['name','age'])
hook.tap('fn1',function(name,age){
console.log("SyncWaterfallHook:fn1-->",name,age); // SyncWaterfallHook:fn1--> mcgee 18
return "爸爸"
})
hook.tap('fn2',function(name,age){
console.log("SyncWaterfallHook:fn2-->",name,age+30); // SyncWaterfallHook:fn2--> 爸爸 48
})
hook.call('mcgee',18)
测试SyncLoopHook
如果监听未返回undefined,则一直执行,重新从taps数组里的第一个tap开始执行可以断点看一下
const { SyncLoopHook } = require('tapable')
let hook = new SyncLoopHook(['name','age'])
hook.tap('fn1',function(name,age){
console.log("SyncLoopHook:fn1-->",name,age);
if(++count1 === 1)
{
count1 = 0
return undefined
}
return true
})
hook.tap('fn2',function(name,age){
console.log("SyncLoopHook:fn2-->",name,age+30);
})
hook.call('mcgee',18)
测试AsyncParallelHook
对于异步钩子并行的使用,在添加事件监听时会存在三种方式
- tap
- tapAsync
- tapPromise
const { AsyncParallelHook } = require('tapable')
let hook = new AsyncParallelHook(['name'])
添加事件监听 tap
hook.tap('fn1',function(name){
// 钩子监听触发时会执行此函数体
console.log("AsyncParallelHook:fn1-->",name);
})
hook.tap('fn2',function(name){
console.log("AsyncParallelHook:fn2-->",name);
})
触发监听函数
hook.callAsync('mcgee',function(){
console.log("run~~"); // 执行的回调,依次输出fn1,fn2,run~~~
console.timeEnd('time')
})
添加事件监听 tapAsync
console.time('time')
hook.tapAsync('fn1',function(name,callback){
setTimeout(()=>{
console.log("AsyncParallelHook:fn1-->",name);
callback()
},1000)
})
hook.tapAsync('fn2',function(name,callback){
setTimeout(()=>{
console.log("AsyncParallelHook:fn2-->",name);
callback()
},2000)
})
触发监听函数
hook.callAsync('mcgee',function(){
console.log("run~~");
console.timeEnd('time')
})
添加事件监听 tapPromise
console.time('time')
hook.tapPromise('fn1',function(name){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log("AsyncParallelHook:fn1-->",name);
resolve()
},1000)
})
})
hook.tapPromise('fn2',function(name){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log("AsyncParallelHook:fn2-->",name);
resolve()
},2000)
})
})
触发监听函数
hook.promise('foo').then(()=>{
console.log("run~~");
console.timeEnd('time')
})
测试AsyncParallelBailHook
熔断操作,通过callback函数错误优先,fn2不会执行
const { AsyncParallelBailHook } = require('tapable')
let hook = new AsyncParallelBailHook(['name'])
console.time('time')
hook.tapAsync('fn1',function(name,callback){
setTimeout(()=>{
console.log("AsyncParallelBailHook:fn1-->",name);
callback('err') // callback回调中错误优先
},1000)
})
hook.tapAsync('fn2',function(name,callback){
setTimeout(()=>{
console.log("AsyncParallelBailHook:fn2-->",name);
callback()
},2000)
})
触发监听函数
hook.callAsync('mcgee',function(){
console.log("run~~");
console.timeEnd('time')
})
测试AsyncSeriesHook
串行,下面代码会执行3s
const { AsyncSeriesHook } = require('tapable')
let hook = new AsyncSeriesHook(['name'])
console.time('time')
hook.tapPromise('fn1',function(name){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log("AsyncSeriesHook:fn1-->",name);
resolve()
},1000)
})
})
hook.tapPromise('fn2',function(name){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log("AsyncSeriesHook:fn2-->",name);
resolve()
},2000)
})
})
执行监听
hook.promise('foo').then(()=>{
console.log("run~~");
console.timeEnd('time')
})
基于SyncHook的源码分析
从源码可以看出,node_modules/tapable/lib/Hook.js
文件为所有钩子的基类文件,
其他钩子都是继承自Hook.js
并对Hook.js
内的方法进行改写的操作,
tap会将注册的内容存成对象,{type:'async',fn:f,name:'fn1'}
再塞入Hook类的属性taps数组中
hook.tap('fn1',function(name){
console.log("AsyncParallelHook:fn1-->",name);
})
基于SyncHook的源码分析2
- 导入
- 实例化 SyncHook 的基类hook,两个重要属性 _x 和 taps
- _x = [ f1, f2... ] tap注册的回调内容
- taps = [ {}, {} ] tap数组
- 调用 call 方法,使用了 HookCodeFactory 类 setup create
Hook
SyncHook
HookCodeFactory
// # hook.js 基类 class Hook { constructor(args = []) { this.args = args this.taps = [] // 将来用于存放组装好的 {} this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3....] } tap(options, fn) { if (typeof options === 'string') { options = { name: options } } options = Object.assign({ fn }, options) // { fn:... name:fn1 } // 调用以下方法将组装好的 options 添加至 [] this._insert(options) } _insert(options) { this.taps[this.taps.length] = options } call(...args) { // 01 创建将来要具体执行的函数代码结构 let callFn = this._createCall() // 02 调用上述的函数(args传入进去) return callFn.apply(this, args) } _createCall() { return this.compile({ taps: this.taps, args: this.args }) } } module.exports = Hook
// # SyncHook.js let Hook = require('./Hook.js') //-------------------------------------------------------- // 代码组装工厂 HookCodeFactory 基于不同hook子类(SyncHook,SyncBailHook),设置不同函数体的类 class HookCodeFactory { args() { return this.options.args.join(',') // ["name", "age"]===> name, age } head() { return `var _x = this._x;` } content() { let code = `` for (var i = 0; i < this.options.taps.length; i++) { code += `var _fn${i} = _x[${i}];_fn${i}(${this.args()});` } return code } setup(instance, options) { // 先准备后续需要使用到的数据 this.options = options // 这里的操作在源码中是通过 init 方法实现,而我们当前是直接挂在了 this 身上 instance._x = options.taps.map(o => o.fn) // this._x = [f1, f2, ....] } create() { // 核心就是创建一段可执行的代码体然后返回 let fn // fn = new Function("name, age", "var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);") fn = new Function( this.args(), this.head() + this.content() ) return fn } } //-------------------------------------------------------- let factory = new HookCodeFactory() // 子类 SyncHook class SyncHook extends Hook { constructor(args) { super(args) } compile(options) { // {taps: [{}, {}], args: [name, age]} factory.setup(this, options) return factory.create(options) } } module.exports = SyncHook
使用上面的hook类
// # useHook.js const SyncHook = require('./SyncHook.js') let hook = new SyncHook(['name', 'age']) hook.tap('fn1', function (name, age) { console.log('fn1-->', name, age) }) hook.tap('fn2', function (name, age) { console.log('fn2-->', name, age) }) hook.call('zoe66', 18) /** * 01 实例化 hook , 定义 _x = [f1, f2, ...] taps = [{}, {}] * 02 实例调用 tap taps = [{}, {}] * 03 调用 call 方法, HookCodeFactory setup create * 04 Hook SyncHook HookCodeFactory */
#### 基于AsyncParallelHook的源码分析
// # useHook.js const AsyncParallelHook = require('./AsyncParallelHook.js') let hook = new AsyncParallelHook(['name', 'age']) hook.tapAsync('fn1', function (name, age, callback) { console.log('fn1-->', name, age) callback() }) hook.tapAsync('fn2', function (name, age, callback) { console.log('fn2-->', name, age) callback() }) hook.callAsync('zoe66', 18)
代码工程要修改的内容
class HookCodeFactory { head() { return `"use strict";var _context;var _x = this._x;` } content() { let code = `var _counter = ${this.options.taps.length};var _done = (function () { _callback(); });` for (var i = 0; i < this.options.taps.length; i++) { code += `var _fn${i} = _x[${i}];_fn${i}(name, age, (function () { if (--_counter === 0) _done(); }));` } return code } args({ after, before } = {}) { let allArgs = this.options.args if (before) allArgs = [before].concat(allArgs) if (after) allArgs = allArgs.concat(after) return allArgs.join(',') // ["name", "age"]===> name, age } }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。