Tapable механизма плагина webpack

внешний интерфейс Webpack

Статья впервые опубликована на:GitHub.com/US TB-Вуд, умри, о ты…

Tapable

Несколько слов о вебпакеEverything is a plugin, Webpack — это, по сути, механизм потока событий, его рабочий процесс заключается в последовательном соединении различных плагинов, а ядром всего этого является Tapable. Tapable чем-то похож на библиотеку событий nodejs, и основным принципом также является модель зависимости и публикации-подписки. Компилятор, ответственный за компиляцию ядра веб-пакета, и компилятор, отвечающий за создание пакетов, являются экземплярами Tapable. Ниже представлено использование и принцип Tapable. Исходный адрес кода для следующего примера:GitHub.com/US TB-Вуд, умри, о ты…

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

Обзор Tapable Hook

Tapable предоставляет множество типов хуков, которые делятся на две категории: синхронные и асинхронные (в асинхронных различают асинхронные параллельные и асинхронные последовательные), а по условиям завершения выполнения события выводятся типы Bail/Waterfall/Loop.

На диаграмме ниже показано, что делает каждый тип:

  • BasicHook:Выполняйте каждый, не заботясь о возвращаемом значении функции, есть SyncHook, AsyncParallelHook, AsyncSeriesHook.
  • BailHook:Хуки выполняются последовательно, и возвращается первый результат !== undefined, и выполнение не будет продолжено. Есть: SyncBailHook, AsyncSeriseBailHook, AsyncParallelBailHook.
  • WaterfallHook:Как и в случае с уменьшением, если результат предыдущей функции-ловушки result !== undefined, результат будет использоваться в качестве первого параметра последней функции-ловушки. Поскольку он выполняется последовательно, этот хук предоставляется только в классах Sync и AsyncSeries: SyncWaterfallHook, AsyncSeriesWaterfallHook.
  • LoopHook:Хук выполняется в цикле до тех пор, пока все результаты функции не приведут к результату === undefined. Точно так же из-за зависимости от серийности есть только SyncLoopHook и AsyncSeriseLoopHook (PS: я пока не видел конкретного варианта использования)

Использование и реализация SyncHook

Синхронизация - это отношение выполнения синхронного последовательного канала, использование выглядит следующим образом:

let { SyncHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
    });
    this.hooks.arch.tap("react", function(name) {
      console.log("react", name);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();

/**
 * 打印出来的值为:
 * node musion
 * react musion
 */

Типичная реализация SyncHook через публикацию и подписку выглядит следующим образом:

// 钩子是同步的
class SyncHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach(task => task(...args));
  }
}

let hook = new SyncHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
});
hook.tap("node", function(name) {
  console.log("node", name);
});
hook.call("musion");

/**
 * 打印出来的值为:
 * node musion
 * react musion
 */

Использование и реализация SyncBailHook

SyncBailHook — это отношение синхронного последовательного выполнения. Пока возвращаемое значение одной функции в функции прослушивания не равно нулю, вся оставшаяся логика будет пропущена. Использование выглядит следующим образом:

let { SyncBailHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncBailHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
      //return "stop";
      return undefined;
    });
    this.hooks.arch.tap("react", function(name) {
      console.log("react", name);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();


/**
 * 打印出来的值为:
 * node musion
 * react musion
 */

Реализация SyncBailHook:

// 钩子是同步的,bail -> 保险
class SyncBailHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    // 当前函数的返回值
    let ret;
    // 当前要先执行第一个
    let index = 0;
    do {
      ret = this.tasks[index++](...args);
    } while (ret === undefined && index < this.tasks.length);
  }
}

let hook = new SyncBailHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
  return "stop";
});
hook.tap("node", function(name) {
  console.log("node", name);
});
hook.call("musion");


/**
 * 打印出来的值为:
 * node musion
 * react musion
 */

Использование и реализация SyncWaterfallHook

SyncWaterfallHook — это отношение синхронного последовательного выполнения. Возвращаемое значение предыдущей функции прослушивателя может быть передано следующей функции прослушивателя. Использование выглядит следующим образом:

let { SyncWaterfallHook } = require("tapable");

// waterfall 瀑布 上面会影响下面的

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncWaterfallHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tap("node", function(name) {
      console.log("node", name);
      return "node学得还不错";
    });
    this.hooks.arch.tap("react", function(data) {
      console.log("react", data);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();

/**
 * 打印出来的值为:
 * node musion
 * react node学得还不错
 */

Реализация SyncWaterfallHook:

// 钩子是同步的
class SyncWaterfallHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    let [first, ...others] = this.tasks;
    let ret = first(...args);
    others.reduce((a, b) => {
      return b(a);
    }, ret);
  }
}

let hook = new SyncWaterfallHook(["name"]);

hook.tap("react", function(name) {
  console.log("react", name);
  return "react ok";
});
hook.tap("node", function(data) {
  console.log("node", data);
  return "node ok";
});
hook.tap("webpack", function(data) {
  console.log("webpack", data);
});
hook.call("musion");

/**
 * 打印出来的值为:
 * react musion
 * node react ok
 * webpack node ok
 */

Использование и внедрение Syncloophook

SyncLoopHook — это отношения выполнения синхронного цикла. Когда функция прослушивателя запускается, если функция прослушивателя возвращает значение true, функция прослушивателя будет выполняться повторно. Если она возвращает неопределенное значение, это означает выход из цикла. Использование выглядит следующим образом:

let { SyncLoopHook } = require("tapable");

// 同步遇到某个不返回undefined的监听函数会多次执行

class Lesson {
  constructor() {
    this.index = 0;
    this.hooks = {
      arch: new SyncLoopHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tap("node", name => {
      console.log("node", name);
      return ++this.index === 3 ? undefined : "继续学";
    });
    this.hooks.arch.tap("react", data => {
      console.log("react", data);
    });
  }
  start() {
    this.hooks.arch.call("musion");
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();

/**
 * 打印出来的值为:
 * node musion
 * node musion
 * node musion
 * react musion
 */

Реализация SyncLoopHook:

// 钩子是同步的
class SyncLoopHook {
  // args => ["name"]
  constructor() {
    this.tasks = [];
  }
  tap(name, task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach(task => {
      let ret;
      do {
        ret = task(...args);
      } while (ret != undefined);
    });
  }
}

let hook = new SyncLoopHook(["name"]);

let total = 0;
hook.tap("react", function(name) {
  console.log("react", name);
  return ++total === 3 ? undefined : "继续学";
});
hook.tap("node", function(data) {
  console.log("node", data);
});
hook.tap("webpack", function(data) {
  console.log("webpack", data);
});
hook.call("musion");

/**
 * 打印出来的值为:
 * react musion
 * react musion
 * react musion
 * node musion
 * webpack musion
 */

Использование и реализация AsyncParallelHook

AsyncParallelHook — это отношение асинхронного одновременного выполнения, которое используется следующим образом:

let { AsyncParallelHook } = require("tapable");
// 异步的钩子分为串行和并行
// 串行:第一个异步执行完,才会执行第二个
// 并行:需要等待所有并发的异步事件执行后再执行回调方法

// 注册方法: tap注册 tapAsync注册

class Lesson {
  constructor() {
    this.hooks = {
      arch: new AsyncParallelHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tapAsync("node", (name, cb) => {
      setTimeout(() => {
        console.log("node", name);
        cb();
      }, 1000);
    });
    this.hooks.arch.tapAsync("react", (name, cb) => {
      setTimeout(() => {
        console.log("react", name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync("musion", function() {
      console.log("end");
    });
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();

/**
 * 打印出来的值为:
 * node musion
 * react musion
 * end
 */


Реализация AsyncParallelHook:

class SyncParralleHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    // 拿出最终的函数
    let finalCallBack = args.pop();
    let index = 0;
    // 类似Promise.all
    let done = () => {
      index++;
      if (index === this.tasks.length) {
        finalCallBack();
      }
    };
    this.tasks.forEach(task => {
      task(...args, done);
    });
  }
}

let hook = new SyncParralleHook(["name"]);

hook.tapAsync("react", function(name, cb) {
  setTimeout(() => {
    console.log("react", name);
    cb();
  }, 1000);
});
hook.tapAsync("node", function(name, cb) {
  setTimeout(() => {
    console.log("node", name);
    cb();
  }, 1000);
});
hook.callAsync("musion", function() {
  console.log("end");
});


/**
 * 打印出来的值为:
 * react musion
 * react musion
 * react musion
 * node musion
 * webpack musion
 */

Использование и реализация AsyncSeriesHook

AsyncSeriesHook — это отношение асинхронного последовательного выполнения, которое используется следующим образом:

// AsyncSeriesHook 异步串行
let { AsyncSeriesHook } = require("tapable");

class Lesson {
  constructor() {
    this.hooks = {
      arch: new AsyncSeriesHook(["name"])
    };
  }
  // 注册监听函数
  tap() {
    this.hooks.arch.tapAsync("node", (name, cb) => {
      setTimeout(() => {
        console.log("node", name);
        cb();
      }, 4000);
    });
    this.hooks.arch.tapAsync("react", (name, cb) => {
      setTimeout(() => {
        console.log("react", name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync("musion", function() {
      console.log("end");
    });
  }
}

let l = new Lesson();

// 注册这两个事件
l.tap();
// 启动钩子
l.start();

/**
 * 打印出来的值为:
 * node musion
 * react musion
 * end
 */


Реализация AsyncSeriesHook:

class SyncSeriesHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = () => {
      if (this.tasks.length === index) return finalCallback();
      let task = this.tasks[index++];
      task(...args, next);
    };
    next();
  }
}

Использование и реализация AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook — это отношение асинхронного последовательного выполнения. Второй параметр обратного вызова (ошибка, данные) в предыдущей функции прослушивателя может использоваться в качестве параметра следующей функции прослушивателя. Использование выглядит следующим образом:

class SyncSeriesWaterfallHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = (err, data) => {
      let task = this.tasks[index];
      if (!task) return finalCallback();
      // 执行的是第一个
      if (index === 0) {
        task(...args, next);
      } else {
        task(data, next);
      }
      index++;
    };
    next();
  }
}

let hook = new SyncSeriesWaterfallHook(["name"]);

hook.tapAsync("react", function(name, cb) {
  setTimeout(() => {
    console.log("react", name);
    cb(null, "musion");
  }, 3000);
});
hook.tapAsync("node", function(name, cb) {
  setTimeout(() => {
    console.log("node", name);
    cb(null);
  }, 1000);
});
hook.callAsync("musion", function() {
  console.log("end");
});

/**
 * 打印出来的值为:
 * node musion
 * end
 */


Реализация AsyncSeriesWaterfallHook:

class SyncSeriesWaterfallHook {
  constructor() {
    this.tasks = [];
  }
  tapAsync(name, task) {
    this.tasks.push(task);
  }
  callAsync(...args) {
    let finalCallback = args.pop();
    let index = 0;
    let next = (err, data) => {
      let task = this.tasks[index];
      if (!task) return finalCallback();
      // 执行的是第一个
      if (index === 0) {
        task(...args, next);
      } else {
        task(data, next);
      }
      index++;
    };
    next();
  }
}

Справочная статья

Вторая серия Webpack Tapable

tapable

Можете обратить внимание на мой паблик-аккаунт «Muchen Classmate», фермера на гусиной фабрике, который обычно записывает какие-то банальные мелочи, технологии, жизнь, инсайты и срастается.