webpack 4.0 Tapable 类中的常用钩子函数源码分析

栏目: 编程工具 · 发布时间: 7年前

内容简介:Tapable 是webpack中的基础类,类似于node中的EventEmitter,都是注册监听,然后收发事件,监听函数执行的过程,自身可以被继承或混入到其它模块中。webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。所以如果你想了解webpack的源码,那么先来了解一下Tapable这个基础类显得尤为必要,那接下来让

Tapable 是webpack中的基础类,类似于node中的EventEmitter,都是注册监听,然后收发事件,监听函数执行的过程,自身可以被继承或混入到其它模块中。

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。

所以如果你想了解webpack的源码,那么先来了解一下Tapable这个基础类显得尤为必要,那接下来让我带你先来了解一下这个类上的常用的 9个钩子函数吧!

安装

npm i Tapable -d
复制代码

hooks分类

常用的钩子主要包含以下几种,分为同步和异步,异步又分为并发执行和串行执行,如下图:

webpack 4.0 Tapable 类中的常用钩子函数源码分析

hooks详解

每一个钩子都是一个构造函数,所有的构造函数都接收一个可选的参数 这个参数是一个数组,数组里面可以放一些参数 例如:‘name’,当触发hook事件时 需要传入name 参数,然后监听函数中可以获取name参数。

const hook = new Hook([‘name’])
复制代码

那怎么注册事件呢?

根据不同的钩子函数使用不同的方法注册事件,常用的注册事件的方法有:tap, tapPromise, tapAsync。

那怎么触发事件呢?

同样的,也是根据不同的钩子函数 使用的触发事件的方法也不相同,常用的触发事件的方法有:call,promise, callAsync。

Sync*类型的hooks

注册在该钩子下面的插件的执行顺序都是顺序执行。 只能使用tap注册,不能使用tapPromise和tapAsync注册

1、SyncHook

串行同步执行 不关心返回值
复制代码

用法

const { SyncHook } = require('tapable');
const mySyncHook = new SyncHook(['name', 'age']);
// 为什么叫tap水龙头 接收两个参数,第一个参数是名称(备注:没有任何意义)  第二个参数是一个函数 接收一个参数  name这个name和上面的name对应 age和上面的age对应
mySyncHook.tap('1', function (name, age) {
    console.log(name, age, 1)
    return 'wrong' // 不关心返回值 这里写返回值对结果没有任何影响
});

mySyncHook.tap('2', function (name, age) {
    console.log(name, age, 2)
});

mySyncHook.tap('3', function (name, age) {
    console.log(name, age, 3)
});

mySyncHook.call('liushiyu', '18');
// 执行的结果
// liushiyu 18 1
// liushiyu 18 2
// liushiyu 18 3
复制代码

SyncHook源码大致实现

class SyncHook {
    constructor () {
        this.hooks = [];
    }
    tap (name, fn) {
        this.hooks.push(fn)
    }
    call () {
        this.hooks.forEach(hook => hook(...arguments));
    }
}
复制代码

2、SyncBailHook

串行同步执行 有一个返回值不为null就跳过剩下的逻辑
Bail 是保险的意思 有一个出错就不往下执行了
复制代码

用法 同SyncHook 的用法

const { SyncBailHook } = require('tapable');
const mySyncBailHook = new SyncBailHook(['name', 'age']);
// 输出结果
// liushiyu 18 1
// return 的值不是null 所以剩下的逻辑就不执行了
复制代码

SyncBailHook源码大致实现

class SyncBailHook {
    constructor () {
        this.hooks = [];
    }
    tap (name, fn) {
        this.hooks.push(fn)
    }
    call () {
        for(let i=0; i<this.hooks.length; i++) {
            let hook = this.hooks[i];
            let result = hook(...arguments);
            if (result) {
                break;
            }
        }
    }
}
复制代码

3、SyncWaterfallHook

下一个任务要拿到上一个任务的返回值
复制代码

用法

const { SyncWaterfallHook } = require('tapable');
const mySyncWaterfallHook = new SyncWaterfallHook(['name']);

mySyncWaterfallHook.tap('1', function (name) {
    console.log(name, '1')
    return '1'
})
mySyncWaterfallHook.tap('2', function (name) {
    console.log(name, '2')
    return '2'
})
mySyncWaterfallHook.tap('3', function (name) {
    console.log(name, '3')
})

mySyncWaterfallHook.call('liu')
// 输出结果
// liu 1
// 1 2
// 2 3
复制代码

SyncWaterfallHook源码大致实现

class SyncWaterfallHook {
    constructor () {
        this.hooks = [];
    }
    tap (name, fn) {
        this.hooks.push(fn)
    }
    call () {
        let result = null
        for(let i=0; i< this.hooks.length; i++) {
            let hook = this.hooks[i];
            if (!i) {
                result = hook(...arguments)
            } else {
                result = hook(result)
            }
        }
    }
}
复制代码

4、SyncLoopHook

监听函数返回true表示继续循环,返回undefine表示结束循环
复制代码

用法

const { SyncLoopHook } = require('tapable');
const mySyncLoopHook = new SyncLoopHook(['name']);

let count = 0;
mySyncLoopHook.tap('1', function (name) {
    console.log(count++);
    if (count < name) {
        return true
    } else {
        return
    }
});

mySyncLoopHook.call('4');
// 输出结果
//0
//1
//2
//3
复制代码

SyncLoopHook源码大致实现

class SyncLoopHook {
    constructor () {
        this.hook;
    }
    tap (name, fn) {
        this.hook = fn
    }
    call () {
        let result = this.hook(...arguments);
        // do{
        //     result = this.hook(...arguments)
        // } while(result)
        while(result) {
            result = this.hook(...arguments)
        }
    }
}
复制代码

Async*类型的hooks

支持tap、tapPromise、tapAsync注册 每次都是调用tap、tapSync、tapPromise注册不同类型的插件钩子,通过调用call、callAsync 、promise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。

异步并发执行

1、AsyncParallelHook

异步并发执行  
复制代码

用法

有三种注册方式

// 第一种注册方式 tap
myAsyncParallelHook.tap('1', function (name) {
    console.log(1, name)
})

myAsyncParallelHook.tap('2', function (name) {
    console.log(2, name)
})

myAsyncParallelHook.tap('3', function (name) {
    console.log(3, name)
})

myAsyncParallelHook.callAsync('liu', function () {
    console.log('over')
});
// 1 'liu'
// 2 'liu'
// 3 'liu'
// over

// 第二种注册方式 tapAsync  凡事有异步 必有回调
console.time('cost')
myAsyncParallelHook.tapAsync('1', function (name, callback) {
    setTimeout(function () {
        console.log(1, name)
        callback()
    }, 1000)
    // callback()
})

myAsyncParallelHook.tapAsync('2', function (name, callback) {
    setTimeout(function () {
        console.log(2, name)
        callback()
    }, 2000)
    // callback()
})

myAsyncParallelHook.tapAsync('3', function (name, callback) {
    setTimeout(function () {
        console.log(3, name)
        callback()
    }, 3000)
})

myAsyncParallelHook.callAsync('liu', () => {
    console.log('over')
    console.timeEnd('cost')
});
并行执行 花费的总时间是时间最长的那个
//1 'liu'
//2 'liu'
//3 'liu'
//over
//cost: 3005.083ms

// 第三种注册方式 tapPromise
console.time('cost')
myAsyncParallelHook.tapPromise('1', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(1, name)
            resolve()
        }, 1000)
    })
})

myAsyncParallelHook.tapPromise('2', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(2, name)
            resolve()
        }, 2000)
    })
})

myAsyncParallelHook.tapPromise('3', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(3, name)
            resolve()
        }, 3000)
    })
})

myAsyncParallelHook.promise('liu').then(function () {
    console.log('ok')
    console.timeEnd('cost')
}, function () {
    console.log('error')
    console.timeEnd('cost')
});

// 1 'liu'
// 2 'liu'
// 3 'liu'
// ok
// cost: 3001.903ms
复制代码

...

2、AsyncParallelBailHook

有一个失败了 其他的都不用走了
复制代码

用法

const { AsyncParallelBailHook } = require('tapable');
const myAsyncParallelBailHook = new AsyncParallelBailHook(['name']);

// 第一种注册方式 tap
myAsyncParallelBailHook.tap('1', function (name) {
    console.log(1, name)
    return 'wrong'
})

myAsyncParallelBailHook.tap('2', function (name) {
    console.log(2, name)
})

myAsyncParallelBailHook.tap('3', function (name) {
    console.log(3, name)
})

myAsyncParallelBailHook.callAsync('liu', function () {
    console.log('over')
});
// 1 'liu'
// over

// 第二种注册方式 tapAsync  凡事有异步 必有回调
console.time('cost')
myAsyncParallelBailHook.tapAsync('1', function (name, callback) {
    setTimeout(function () {
        console.log(1, name)
        return 'wrong';// 最后的回调就不会调用了
        callback()
    }, 1000)
    // callback()
})

myAsyncParallelBailHook.tapAsync('2', function (name, callback) {
    setTimeout(function () {
        console.log(2, name)
        callback()
    }, 2000)
    // callback()
})

myAsyncParallelBailHook.tapAsync('3', function (name, callback) {
    setTimeout(function () {
        console.log(3, name)
        callback()
    }, 3000)
})

myAsyncParallelBailHook.callAsync('liu', () => {
    console.log('over')
console.timeEnd('cost')
});

// 1 'liu'
// 2 'liu'
// 3 'liu'

// 第三种注册方式 tapPromise
console.time('cost')
myAsyncParallelBailHook.tapPromise('1', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(1, name)
            reject('wrong');// reject()的参数是一个不为null的参数时,最后的回调就不会再调用了
        }, 1000)
    })
})

myAsyncParallelBailHook.tapPromise('2', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(2, name)
            resolve()
        }, 2000)
    })
})

myAsyncParallelBailHook.tapPromise('3', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(3, name)
            resolve()
        }, 3000)
    })
})

myAsyncParallelBailHook.promise('liu').then(function () {
    console.log('ok')
    console.timeEnd('cost')
}, function () {
    console.log('error')
    console.timeEnd('cost')
});


// 1 'liu'
// error
// cost: 1006.030ms
// 2 'liu'
// 3 'liu'
复制代码

异步串行执行

1、AsyncSeriesHook

用法

let { AsyncSeriesHook } = require('tapable');
let myAsyncSeriesHook = new AsyncSeriesHook(['name']);
console.time('coast')
myAsyncSeriesHook.tapAsync('1', function (name, cb) {
    setTimeout(function () {
        console.log('1', name)
        cb()
    }, 1000)
});

myAsyncSeriesHook.tapAsync('2', function (name, cb) {
    setTimeout(function () {
        console.log('2', name)
        cb()
    }, 2000)
});

myAsyncSeriesHook.tapAsync('3', function (name, cb) {
    setTimeout(function () {
        console.log('3', name)
        cb()
    }, 3000)
});

myAsyncSeriesHook.callAsync('liu', function () {
    console.log('over')
    console.timeEnd('coast')
})

// 1 liu
// 2 liu
// 3 liu
// over
// coast: 6010.515ms 异步串行执行消耗的时间是所有的总和
复制代码

AsyncSeriesHook源码大致实现

class AsyncSeriesHook{
    constructor() {
        this.hooks = [];
    }
    tapAsync () {
        this.hooks.push(arguments[arguments.length-1]);
    }
    callAsync () {
        let args = Array.from(arguments); //将传进来的参数转化为数组
        let done = args.pop(); // 取出数组的最后一项 即成功后的回调函数
        let index = 0;
        let that = this;
        function next(err) {
            if (err) return done();
            let fn = that.hooks[index++];
            fn ? fn(...args, next) : done();
        }
        next()
    }
}
复制代码

2、AsyncSeriesBailHook

用法

// 异步串行执行
let { AsyncSeriesBailHook } = require('tapable');
let myAsyncSeriesBailHook = new AsyncSeriesBailHook(['name']);
console.time('coast')
myAsyncSeriesBailHook.tapAsync('1', function (name, cb) {
    setTimeout(function () {
        console.log('1', name)
        cb('wrong')
    }, 1000)
});

myAsyncSeriesBailHook.tapAsync('2', function (name, cb) {
    setTimeout(function () {
        console.log('2', name)
        cb()
    }, 2000)
});

myAsyncSeriesBailHook.tapAsync('3', function (name, cb) {
    setTimeout(function () {
        console.log('3', name)
        cb()
    }, 3000)
});

myAsyncSeriesBailHook.callAsync('liu', function () {
    console.log('over')
    console.timeEnd('coast')
})
// 1 liu
// over
// coast: 1004.175ms
复制代码

AsyncSeriesBailHook 源码的大致实现

class AsyncSeriesBailHook{
    constructor() {
        this.hooks = [];
    }
    tapAsync () {
        this.hooks.push(arguments[arguments.length-1]);
    }
    callAsync () {
        let args = Array.from(arguments); //将传进来的参数转化为数组
        let done = args.pop(); // 取出数组的最后一项 即成功后的回调函数
        let index = 0;
        let that = this;
        function next(err) {
            if (err) return done();
            let fn = that.hooks[index++];
            fn ? fn(...args, next) : done();
        }
        next()
    }
}
复制代码

3、AsyncSeriesWaterfallHook

下一个任务要拿到上一个任务的返回值
复制代码

用法

// 异步串行执行
let { AsyncSeriesWaterfallHook } = require('tapable');
let myAsyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(['name']);
console.time('coast')
myAsyncSeriesWaterfallHook.tapAsync('1', function (name, cb) {
    setTimeout(function () {
        console.log('1', name)
        cb(null, 'aa')
    }, 1000)
});

myAsyncSeriesWaterfallHook.tapAsync('2', function (name, cb) {
    setTimeout(function () {
        console.log('2', name)
        cb(null, 'bb')
    }, 2000)
});

myAsyncSeriesWaterfallHook.tapAsync('3', function (name, cb) {
    setTimeout(function () {
        console.log('3', name)
        cb(null, 'cc')
    }, 3000)
});

myAsyncSeriesWaterfallHook.callAsync('liu', function () {
    console.log('over')
    console.timeEnd('coast')
})

// 1 liu
// 2 aa
// 3 bb
// over
// coast: 6011.774ms
复制代码

AsyncSeriesWaterfallHook 源码的大致实现

class AsyncSeriesWaterfallHook{
    constructor() {
        this.hooks = [];
    }
    tapAsync () {
        this.hooks.push(arguments[arguments.length-1]);
    }
    callAsync () {
        let args = Array.from(arguments); //将传进来的参数转化为数组
        let done = args.pop(); // 取出数组的最后一项 即成功后的回调函数
        let index = 0;
        let that = this;
        function next(err, data) {
            if(index>=that.hooks.length) return done();
            if (err) return done(err);
            let fn = that.hooks[index++];
            if (index == 1) {
                fn(...args, next)
            } else {
                fn(data, next)
            }
        }
        next()
    }
}
复制代码

以上所述就是小编给大家介绍的《webpack 4.0 Tapable 类中的常用钩子函数源码分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript

JavaScript

Douglas Crockford / Yahoo Press / 2008-5 / GBP 23.99

Most programming languages contain good and bad parts, but JavaScript has more than its share of the bad, having been developed and released in a hurry before it could be refined. This authoritative b......一起来看看 《JavaScript》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具