1 Tapable
Введение
webpack
По сути, это механизм потока событий, и его рабочий процесс заключается в последовательном подключении различных подключаемых модулей, а ядром всего этого являетсяTapable
,webpack
Основной отвечает за компиляциюCompiler
и отвечает за создание пакетовCompilation
обеTapable
пример. В этой статье в основном представлена функция ловушки в Tapable.
Пакет tapable предоставляет множество классов ловушек, которые можно использовать для создания функций ловушек для плагинов, в основном включая следующие:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Конструктор всех классов ловушек принимает необязательный параметр, представляющий собой массив строковых параметров, как показано ниже:
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
Далее мы подробно расскажем об использовании хуков, а также о принципах некоторых классов хуков.
Обзор 2 крючков
Обычно используемые хуки в основном включают следующие, которые делятся на синхронные и асинхронные, а асинхронные делятся на параллельное выполнение и последовательное выполнение, как показано на следующем рисунке:
Во-первых, общий опыт использования под крюком, следующим образомсерийный номер | имя хука | способ исполнения | Использовать баллы |
---|---|---|---|
1 | SyncHook | Синхронный последовательный | Не заботьтесь о возвращаемом значении функции слушателя |
2 | SyncBailHook | Синхронный последовательный | Пока есть функция монитора, возвращаемое значение функции не равноnull , затем пропустите всю оставшуюся логику |
3 | SyncWaterfallHook | Синхронный последовательный | Возвращаемое значение предыдущей функции прослушивателя может быть передано следующей функции прослушивателя. |
4 | SyncLoopHook | Синхронизированный цикл | Когда функция прослушивателя запускается, если функция прослушивателя возвращаетtrue Когда функция слушателя выполняется повторно, если она возвращаетundefined значит выйти из цикла |
5 | AsyncParallelHook | Асинхронный параллелизм | Не заботьтесь о возвращаемом значении функции слушателя |
6 | AsyncParallelBailHook | Асинхронный параллелизм | До тех пор, пока возвращаемое значение функции слушателя неnull , он проигнорирует выполнение следующей функции слушателя и перейдет непосредственно кcallAsync Подождите, пока функция обратного вызова будет привязана к триггерной функции, а затем выполните связанную функцию обратного вызова. |
7 | AsyncSeriesHook | Асинхронный последовательный | это не имеет значенияcallback() параметры |
8 | AsyncSeriesBailHook | Асинхронный последовательный |
callback() Параметр неnull , он будет выполняться напрямуюcallAsync Функция обратного вызова, такая как привязка триггерной функции |
9 | AsyncSeriesWaterfallHook | Асинхронный последовательный | в предыдущей функции слушателяcallback(err, data) Второй параметр можно использовать как параметр следующей функции слушателя. |
3 крючка
синхронизирующие* хуки
Синхронный последовательный
(1) SyncHook
Не заботьтесь о возвращаемом значении функции слушателя
- usage
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。
// 订阅
queue.tap('1', function (name, name2) {// tap 的第一个参数是用来标识订阅的函数的
console.log(name, name2, 1);
return '1'
});
queue.tap('2', function (name) {
console.log(name, 2);
});
queue.tap('3', function (name) {
console.log(name, 3);
});
// 发布
queue.call('webpack', 'webpack-cli');// 发布的时候触发订阅的函数 同时传入参数
// 执行结果:
/*
webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数
webpack 2
webpack 3
*/
- принцип
class SyncHook_MY{
constructor(){
this.hooks = [];
}
// 订阅
tap(name, fn){
this.hooks.push(fn);
}
// 发布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
(2) SyncBailHook
Пока есть функция монитора, возвращаемое значение функции не равноnull
, затем пропустите всю оставшуюся логику
- usage
const {
SyncBailHook
} = require("tapable");
let queue = new SyncBailHook(['name']);
queue.tap('1', function (name) {
console.log(name, 1);
});
queue.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue.tap('3', function (name) {
console.log(name, 3);
});
queue.call('webpack');
// 执行结果:
/*
webpack 1
webpack 2
*/
- принцип
class SyncBailHook_MY {
constructor() {
this.hooks = [];
}
// 订阅
tap(name, fn) {
this.hooks.push(fn);
}
// 发布
call() {
for (let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
(3) SyncWaterfallHook
Возвращаемое значение предыдущей функции прослушивателя может быть передано следующей функции прослушивателя.
- usage
const {
SyncWaterfallHook
} = require("tapable");
let queue = new SyncWaterfallHook(['name']);
// 上一个函数的返回值可以传给下一个函数
queue.tap('1', function (name) {
console.log(name, 1);
return 1;
});
queue.tap('2', function (data) {
console.log(data, 2);
return 2;
});
queue.tap('3', function (data) {
console.log(data, 3);
});
queue.call('webpack');
// 执行结果:
/*
webpack 1
1 2
2 3
*/
- принцип
class SyncWaterfallHook_MY{
constructor(){
this.hooks = [];
}
// 订阅
tap(name, fn){
this.hooks.push(fn);
}
// 发布
call(){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...arguments): hook(result);
}
}
}
(4) SyncLoopHook
Когда функция прослушивателя запускается, если функция прослушивателя возвращаетtrue
Когда функция слушателя выполняется повторно, если она возвращаетundefined
значит выйти из цикла
- usage
const {
SyncLoopHook
} = require("tapable");
let queue = new SyncLoopHook(['name']);
let count = 3;
queue.tap('1', function (name) {
console.log('count: ', count--);
if (count > 0) {
return true;
}
return;
});
queue.call('webpack');
// 执行结果:
/*
count: 3
count: 2
count: 1
*/
- принцип
class SyncLoopHook_MY {
constructor() {
this.hook = null;
}
// 订阅
tap(name, fn) {
this.hook = fn;
}
// 发布
call() {
let result;
do {
result = this.hook(...arguments);
} while (result)
}
}
асинхронные* хуки
асинхронный параллелизм
(1) AsyncParallelHook
Не заботьтесь о возвращаемом значении функции слушателя.
Существует три режима регистрации/освобождения, а именно:
Асинхронная подписка | метод вызова |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
- usage - tap
const {
AsyncParallelHook
} = require("tapable");
let queue1 = new AsyncParallelHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// 执行结果
/*
webpack 1
webpack 2
webpack 3
cost: 4.520ms
*/
- usage - tapAsync
let queue2 = new AsyncParallelHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// 执行结果
/*
webpack 1
webpack 2
webpack 3
over
time: 3004.411ms
*/
- usage - promise
let queue3 = new AsyncParallelHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
resolve();
}, 2000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
/*
webpack 1
webpack 2
webpack 3
over
cost3: 3007.925ms
*/
(2) AsyncParallelBailHook
До тех пор, пока возвращаемое значение функции слушателя неnull
, он проигнорирует выполнение следующей функции слушателя и перейдет непосредственно кcallAsync
Подождите, пока функция обратного вызова будет связана функцией триггера, а затем выполните связанную функцию обратного вызова.
- usage - tap
let queue1 = new AsyncParallelBailHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// 执行结果:
/*
webpack 1
webpack 2
cost: 4.975ms
*/
- usage - tapAsync
let queue2 = new AsyncParallelBailHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
return 'wrong';// 最后的回调就不会调用了
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// 执行结果:
/*
webpack 1
webpack 2
webpack 3
*/
- usage - promise
let queue3 = new AsyncParallelBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('2', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
reject('wrong');// reject()的参数是一个不为null的参数时,最后的回调就不会再调用了
}, 2000);
});
});
queue3.tapPromise('3', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
// 执行结果:
/*
webpack 1
webpack 2
error
cost3: 2009.970ms
webpack 3
*/
Асинхронный последовательный
(1) AsyncSeriesHook
это не имеет значенияcallback()
параметры
- usage - tap
const {
AsyncSeriesHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('zfpx', err => {
console.log(err);
console.timeEnd('cost1');
});
// 执行结果
/*
1
2
3
undefined
cost1: 3.933ms
*/
- usage - tapAsync
let queue2 = new AsyncSeriesHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', (err) => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果
/*
webpack 1
webpack 2
webpack 3
undefined
over
cost2: 6019.621ms
*/
- usage - promise
let queue3 = new AsyncSeriesHook(['name']);
console.time('cost3');
queue3.tapPromise('1',function(name){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 1);
resolve();
},1000)
});
});
queue3.tapPromise('2',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 2);
resolve();
},2000)
});
});
queue3.tapPromise('3',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 3);
resolve();
},3000)
});
});
queue3.promise('webapck').then(err=>{
console.log(err);
console.timeEnd('cost3');
});
// 执行结果
/*
webapck 1
webapck 2
webapck 3
undefined
cost3: 6021.817ms
*/
- принцип
class AsyncSeriesHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
var slef = this;
var args = Array.from(arguments);
let done = args.pop();
let idx = 0;
function next(err) {
// 如果next的参数有值,就直接跳跃到 执行callAsync的回调函数
if (err) return done(err);
let fn = slef.hooks[idx++];
fn ? fn(...args, next) : done();
}
next();
}
}
(2) AsyncSeriesBailHook
callback()
Параметр неnull
, он будет выполняться напрямуюcallAsync
Функция обратного вызова, такая как привязка триггерной функции
- usage - tap
const {
AsyncSeriesBailHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesBailHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.timeEnd('cost1');
});
// 执行结果:
/*
1
null
cost1: 3.979ms
*/
- usage - tapAsync
let queue2 = new AsyncSeriesBailHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log(name, 1);
callback();
}, 1000)
});
queue2.tapAsync('2', function (name, callback) {
setTimeout(function () {
console.log(name, 2);
callback('wrong');
}, 2000)
});
queue2.tapAsync('3', function (name, callback) {
setTimeout(function () {
console.log(name, 3);
callback();
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果
/*
webpack 1
webpack 2
wrong
over
cost2: 3014.616ms
*/
- usage - promise
let queue3 = new AsyncSeriesBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 1);
resolve();
}, 1000)
});
});
queue3.tapPromise('2', function (name, callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 2);
reject();
}, 2000)
});
});
queue3.tapPromise('3', function (name, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(name, 3);
resolve();
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.log('over');
console.timeEnd('cost3');
}, err => {
console.log(err);
console.log('error');
console.timeEnd('cost3');
});
// 执行结果:
/*
webpack 1
webpack 2
undefined
error
cost3: 3017.608ms
*/
(3) AsyncSeriesWaterfallHook
в предыдущей функции слушателяcallback(err, data)
Второй параметр можно использовать как параметр следующей функции слушателя.
- usage - tap
const {
AsyncSeriesWaterfallHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(name, 1);
return 'lily'
});
queue1.tap('2', function (data) {
console.log(2, data);
return 'Tom';
});
queue1.tap('3', function (data) {
console.log(3, data);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost1');
});
// 执行结果:
/*
webpack 1
2 'lily'
3 'Tom'
null
over
cost1: 5.525ms
*/
- usage - tapAsync
let queue2 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log('1: ', name);
callback(null, 2);
}, 1000)
});
queue2.tapAsync('2', function (data, callback) {
setTimeout(function () {
console.log('2: ', data);
callback(null, 3);
}, 2000)
});
queue2.tapAsync('3', function (data, callback) {
setTimeout(function () {
console.log('3: ', data);
callback(null, 3);
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// 执行结果:
/*
1: webpack
2: 2
3: 3
null
over
cost2: 6016.889ms
*/
- usage - promise
let queue3 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1:', name);
resolve('1');
}, 1000)
});
});
queue3.tapPromise('2', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('2:', data);
resolve('2');
}, 2000)
});
});
queue3.tapPromise('3', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('3:', data);
resolve('over');
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.timeEnd('cost3');
}, err => {
console.log(err);
console.timeEnd('cost3');
});
// 执行结果:
/*
1: webpack
2: 1
3: 2
over
cost3: 6016.703ms
*/
- принцип
class AsyncSeriesWaterfallHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
let self = this;
var args = Array.from(arguments);
let done = args.pop();
console.log(args);
let idx = 0;
let result = null;
function next(err, data) {
if (idx >= self.hooks.length) return done();
if (err) {
return done(err);
}
let fn = self.hooks[idx++];
if (idx == 1) {
fn(...args, next);
} else {
fn(data, next);
}
}
next();
}
}