Webpack
Его успех заключается не только в мощных возможностях упаковки и построения, но и в гибком механизме подключаемых модулей.
Webpack — это, по сути, механизм потока событий, его рабочий процесс заключается в последовательном подключении различных плагинов, а ядром всего этого является Tapable.
изучениеWebpack
Вы часто можете увидеть приведенное выше введение. То есть учитьсяWebpack
Суть в том, чтобы учитьсяTapable
. учиться лучшеWebpack
принцип.
1. Нажимаемый
фактическиtapable
Основная идея чем-то похожа наnode.js
серединаevents
,базовыйОпубликовать/подписатьсямодель.
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// 注册事件对应的监听函数
myEmitter.on('start', (params) => {
console.log("输出", params)
});
// 触发事件 并传入参数
myEmitter.emit('start', '学习webpack工作流'); // 输出 学习webpack工作流
2. Знакомство с цепляющими крючками
первый,tapable
Предусмотрено 10 крючков следующим образом.
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesLoopHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Во-вторых, краткое введение в использование всех хуков выглядит следующим образом: (вы можете бросить беглый взгляд на него, просто посмотрите вниз)
серийный номер | имя хука | способ исполнения | Использовать баллы |
---|---|---|---|
1 | SyncHook | Синхронный последовательный | Не заботьтесь о возвращаемом значении функции слушателя |
2 | SyncBailHook | Синхронный последовательный | Пока возвращаемое значение одной из функций прослушивателя не является неопределенным, пропустите всю остальную логику. |
3 | SyncWaterfallHook | Синхронный последовательный | Возвращаемое значение предыдущей функции прослушивателя может быть передано следующей функции прослушивателя. |
4 | SyncLoopHook | Синхронизированный цикл | Когда функция прослушивателя срабатывает, если функция прослушивателя возвращает true, функция прослушивателя будет выполняться повторно, а если она возвращает значение undefined, это означает выход из цикла. |
5 | AsyncParallelHook | Асинхронный параллелизм | Не заботьтесь о возвращаемом значении функции слушателя |
6 | AsyncParallelBailHook | Асинхронный параллелизм | Пока возвращаемое значение функции прослушивателя не равно нулю, выполнение следующей функции прослушивателя будет игнорироваться, и она перейдет непосредственно к функции обратного вызова, которая запускает привязку функции, например callAsync, а затем выполнит связанную функцию обратного вызова. . |
7 | AsyncSeriesHook | Асинхронный последовательный | Не заботьтесь о параметрах callback() |
8 | AsyncSeriesBailHook | Асинхронный последовательный | Если параметр callback() не равен нулю, функция обратного вызова, которая запускает привязку функции, например callAsync, будет выполняться напрямую. |
9 | AsyncSeriesWaterfallHook | Асинхронный последовательный | Второй параметр обратного вызова (ошибка, данные) в предыдущей функции мониторинга может использоваться как параметр следующей функции мониторинга. |
10 | AsyncSeriesLoopHook | Асинхронный последовательный | Может инициировать вызовы цикла обработчика. |
3. Введение в использование вышеупомянутого хука
(1.1) Синхрук
Синхронный последовательный, не заботьтесь о возвращаемом значении функции слушателя.
Начнем с самого простогоSyncHook
, на самом деле каждыйHook
Они все одинаковые, и легко понять одно из других.
const {SyncHook} = require("tapable");
//所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。
let queue = new SyncHook(['param1']);
// 订阅tap 的第一个参数是用来标识订阅的函数的
queue.tap('event 1', function (param1) {
console.log(param1, 1);
});
queue.tap('event 2', function (param1) {
console.log(param1, 2);
});
queue.tap('event 3', function () {
console.log(3);
});
// 发布的时候触发订阅的函数 同时传入参数
queue.call('hello');
// 控制台输出
/* hello 1
hello 2
3
*/
Как видите, события, на которые подписан этот хук, выполняются синхронно по порядку.
(1.2) Принцип SyncHook
Простое моделирование принципа.
class SyncHook{
constructor(){
this.taps = [];
}
// 订阅
tap(name, fn){
this.taps.push(fn);
}
// 发布
call(){
this.taps.forEach(tap => tap(...arguments));
}
}
(2.1) SyncBailHook
посмотри сноваSyncBailHook
использование.
Пока возвращаемое значение одной из функций прослушивателя не является неопределенным, вся остальная логика пропускается.
let queue = new SyncBailHook(['param1']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。
// 订阅
queue.tap('event 1', function (param1) {// tap 的第一个参数是用来标识订阅的函数的
console.log(param1, 1);
return 1;
});
queue.tap('event 2', function (param1) {
console.log(param1, 2);
});
queue.tap('event 3', function () {
console.log(3);
});
// 发布
queue.call('hello', 'world');// 发布的时候触发订阅的函数 同时传入参数
// 控制台输出
/* hello 1 */
Как видите, до тех пор, пока возвращаемое значение одной из функций слушателя неundefined
, то вся остальная логика пропускается.
(2.2) Принцип SyncBailHook
Простое моделирование принципа.
class SyncBailHook {
constructor() {
this.taps = [];
}
// 订阅
tap(name, fn) {
this.taps.push(fn);
}
// 发布
call() {
for (let i = 0, l = this.taps.length; i < l; i++) {
let tap = this.taps[i];
let result = tap(...arguments);
if (result) {
break;
}
}
}
}
(3) Обзор SyncHook и SyncBailHook
вышесказанное2
Поток выполнения хука показан на следующем рисунке:
2
Введение в хуки можно найтиtapable
предоставляет разнообразныеhook
Приходите и помогите нам управлять выполнением событий.
tapable
Основная функция заключается в управлении потоком выполнения между серией зарегистрированных событий.Например, если я регистрирую три события, я могу надеяться, что они параллельны или выполняются синхронно и последовательно, или если одно из них терпит неудачу, последующие события не будет выполняться, эти функции могут бытьtapable
изhook
выполнить.
Как и взаимосвязь между пробуждением, походом на работу и завтраком, пробуждение должно быть приоритетом. Но еда и поход на работу — не обязательно одно и то же. Что, если ты опоздаешь? Может быть, отказаться от завтрака!
4. Интерпретация исходного кода Tapable
Помните суть, ядроcall
а такжеtap
два метода.
Помните суть, ядроcall
а такжеtap
два метода.
Помните суть, ядроcall
а такжеtap
два метода.
Тогда давайте посмотримtapable
исходный кодSyncHook
Как это достигается следующим образом. Опять же, после прочтения одного, другие, естественно, будут поняты. Для понимания исходный код минимизирован, часть неосновного кода удалена.
// node_modules/tapable/lib/SyncHook.js
const factory = new SyncHookCodeFactory();
// 继承基础Hook类
class SyncHook extends Hook {
// 重写Hook的compile方法
compile(options) {
// 开发者订阅的事件传
factory.setup(this, options);
// 动态生成call方法
return factory.create(options);
}
}
module.exports = SyncHook;
Основной код очень прост, вы можете видетьSyncHook
только что унаследовалHook
базовый класс. и переписанcompile
метод.
Первый взгляд наHook
базовыйtap
метод. Вы можете видеть каждый звонокtap
, состоит в том, чтобы собрать текущийhook
Экземпляр всех подписанных событий вtaps
множество.
// node_modules/tapable/lib/Hook.js
// 订阅
tap(options, fn) {
// 同步 整理配置项
options = Object.assign({ type: "sync", fn: fn }, options);
// 将订阅的事件存储在taps里面
this._insert(options);
}
_insert(item) {
// 将item 推进 this.taps
this.taps[i] = item;
}
Тогда взглянитеHook
базовыйcall
Как реализован метод.
// node_modules/tapable/lib/Hook.js
class Hook {
constructor(args) {
this.taps = [];
this.call = this._call;
}
compile(options) {
// 继承类必须重写compile
throw new Error("Abstract: should be overriden");
}
// 执行compile生成call方法
_createCall(type) {
return this.compile({
taps: this.taps,
// ...等参数
});
}
}
// 动态生成call方法
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
// 创造call等函数
this[name] = this._createCall(type);
// 执行触发call等函数
return this[name](...args);
};
}
// 定义_call方法
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
});
С помощью приведенного выше кода мы можем найти, чтоcall
Что такое метод, переопределяяcompile
сгенерированный метод. Тогда давайте посмотримcompile
Что именно делает метод.
Давайте взглянемSyncHook
весь код.
// node_modules/tapable/lib/SyncHook.js
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
// 继承工厂类
class SyncHookCodeFactory extends HookCodeFactory {
// call方法个性化定制
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
// 继承基础Hook类
class SyncHook extends Hook {
// 重写Hook的compile方法
compile(options) {
// 开发者订阅的事件传
factory.setup(this, options);
// 动态生成call方法
return factory.create(options);
}
}
module.exports = SyncHook;
можно увидетьcompile
в основном выполнятьfactory
метод, в то время какfactory
даSyncHookCodeFactory
, который наследуетHookCodeFactory
класс, тогдаfactory
экземпляр называетсяsetup
метод.
setup
что будетtaps
Методы событий, на которые подписаны, унифицированы дляthis._x
;
// node_modules/tapable/lib/HookCodeFactory.js
setup(instance, options) {
// 将taps里的所有fn 赋值给 _x
instance._x = options.taps.map(t => t.fn);
}
тогда посмотри еще разfactory
экземпляр называетсяcreate
метод.
// node_modules/tapable/lib/HookCodeFactory.js
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
// 参数
this.args(),
// 函数体
'"use strict";\n' +
// 获取一些需要的变量
this.header() +
// 事件运行逻辑
this.content({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
}
}
create
Все входящие события будут собраны. последнее поколениеcall
метод.
Ниже приведен окончательный результат нашего случая.call
метод.
function anonymous(param1) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(param1);
var _fn1 = _x[1];
_fn1(param1);
var _fn2 = _x[2];
_fn2(param1);
}
если вы подпишитесь5
событие, приведенный выше код становится5
последовательное выполнение функций. и параметр должен быть созданhook
Экземпляр объявлен. иначеtap
Параметры, переданные в событие, бесполезны~
Приведенный выше код по-прежнему сильно сокращен, поэтому вы можете сразу перейти к исходному коду, который очень лаконичен и прост для понимания. Автору респект. 👍
Подводя итог, ядроcall
а такжеtap
два метода.其实还有tapAsync
Подождите... но принцип тот же.tap
Собирайте подписанные события и запускайтеcall
метод согласноhook
Тип динамически генерирует соответствующее тело выполнения. Как показано ниже, другиеhook
Реализация такая же.
5. Применение Tapable в Webpack
Webpack
Процесс можно разделить на следующие три этапа:
воплощать в жизньwebpack
, создастcompiler
пример.
// node_modules/webpack/lib/webpack.js
const Compiler = require("./Compiler");
const MultiCompiler = require("./MultiCompiler");
const webpack = (options, callback) => {
// ...省略了多余代码...
let compiler;
if (typeof options === "object") {
compiler = new Compiler(options.context);
} else {
throw new Error("Invalid argument: options");
}
})
мы обнаруживаемCompiler
наследуетсяTapable
из. нашли в то же времяwebpack
жизненный циклhooks
Все виды крючков.
// node_modules/webpack/lib/Compiler.js
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
// ....等等等很多 大家看下源码吧.... 不看也没有关系
}
}
}
а затем инициализироватьwebpack
В процессе настройки он будет циклически проходить через наши сконфигурированные иwebpack
Все плагины по умолчаниюplugin
.
// 订阅在options中的所有插件
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
Этот процесс будетplugin
все вtap
События собираются в каждый жизненный циклhook
середина.
Наконец, согласно каждомуhook
воплощать в жизньcall
Порядок методов (он же жизненный цикл). ты можешь положить всеplugin
реализовано.
В качестве примера ниже приведен код часто используемого нами плагина горячего обновления, который подписывается наadditionalPass
Ждатьhook
.
webpack
Его рабочий процесс может сочетать различные плагиныplugin
Причина тандема, и в основе всего этого то, чтоTapable
.
Шесть, плюнь слот
Хотя дизайн плагина очень гибкий, мы можем писать операции плагинаwebpack
весь жизненный цикл. Однако я также обнаружил некоторые проблемы, связанные с дизайном плагина, то есть опыт чтения исходного кода очень плохой:
(1) Соединение ослаблено. использоватьtapable
Крючки аналогичны режиму мониторинга событий. Хотя они могут быть эффективно развязаны, регистрация крючков практически не связана с вызовом.
(2) Я вижу, что модуль в исходном коде предоставляет несколько хуков, но я не знаю, когда и где будет вызываться хук, и какие методы прописаны на хуке, когда и где. Раньше их решали путем поиска в кодовой базе ключевых слов.
(3) Есть много крючков.webpack
Крючки внутри очень большие, и их количество достигает180+
,
Ссылка на ссылку
В этой статье в основном говорится о принципе, пониманииtapable
. Чтобы узнать об использовании других хуков, смотрите эту статью.