[Расширенный веб-пакет] Визуально отображайте взаимосвязь между плагинами и хуками внутри веб-пакета 📈

JavaScript Webpack
[Расширенный веб-пакет] Визуально отображайте взаимосвязь между плагинами и хуками внутри веб-пакета 📈

Как друзья, добро пожаловать, чтобы следовать за мнойблогилиНовостная лента. Прошлые статьи:

введение

Успех webpack заключается не только в его мощных возможностях упаковки и построения, но и в его гибком механизме подключаемых модулей.

Возможно, вы знакомы с механизмом плагинов и хуков в webpack; но вы можете не знать, что в webpack более 180 хуков, и взаимосвязь между «созданием», «регистрацией» и «вызовом» между этими хуками и модулями (встроенными плагины) очень сложно. Таким образом, понимание взаимосвязи между внутренними плагинами и хуками webpack поможет нам лучше понять внутреннюю реализацию webpack.

«Модуль Webpack/встроенный плагин и диаграмма взаимосвязей хука 📈»: сложность также видна.


В первой части этой статьи будет представлена ​​важная концепция хуков и то, как работают плагины веб-пакетов. Однако знакомые друзья обнаружат, что этот гибкий механизм делает соединение между модулями вебпака более свободным и несвязанным, и в то же время затрудняет разбор внутренней структуры исходного кода и соединения вебпака.

Поэтому во второй части будет представлен инструмент визуального отображения отношений между плагинами и хуками в webpack, а также будет использовано изображение, чтобы прояснить эту сложную взаимосвязь внутри webpack.

Диаграмма эффекта использования инструмента визуализации:

1. Механизм плагина webpack

Прежде чем подробно представить встроенные плагины webpack и инструменты визуализации хуков, давайте взглянем на механизм плагинов в webpack.

Общий способ, которым WebPack реализует механизм плагина:

  • «создать» — webpack создает различные хуки на своих внутренних объектах;
  • «Регистрация» — плагин регистрирует собственный метод на соответствующем хуке и передает его вебпаку;
  • «Вызов» — в процессе компиляции веб-пакета будет своевременно срабатывать соответствующий хук, поэтому метод плагина также будет срабатывать.

1.1. Tapable

TapableЭто библиотека, которую webpack использует для создания хуков.

The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.

С помощью Tapable вы можете быстро создавать различные хуки. Вот функции класса для различных хуков:

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

в самом простомSyncHookНапример, это может помочь нам создать синхронный хук. Чтобы помочь понять, как Tapable используется для создания крючков, мы представляем, как Tapable используется с моделируемым сценарием «прийти домой с работы и войти в дверь».

Теперь у нас естьwelcome.jsМодуль, который устанавливает для нас ряд действий, чтобы «идти домой» (открыть дверь, снять обувь...):

// welcome.js
const {SyncHook} = require('tapable');

module.exports = class Welcome {
    constructor(words) {
        this.words = words;
        this.sayHook = new SyncHook(['words']);
    }

    // 进门回家的一系列行为
    begin() {
        console.log('开门');
        console.log('脱鞋');
        console.log('脱外套');
        // 打招呼
        this.sayHook.call(this.words);
        console.log('关门');
    }
}

Сначала мы создаем хук синхронизации в конструктореsayHook, который используется, чтобы поздороваться после этого.

Потом,begin()Метод описывает последовательность действий, которые мы совершаем, когда только приходим домой и входим в дверь: открываем дверь, разуваемся, снимаем пальто, закрываем дверь. Среди них между «Сними пальто» и «Закрой дверь» есть поведение приветствия, здесь мы запускаемsayHookПерехватите и передайте в него слова в качестве параметра.

Обратите внимание, что здесь.call()Это метод, предоставляемый Tapable для запуска хука, а не собственный метод вызова в js.

Запустить эту серию процессов также очень просто:

// run.js
const Welcome = require('./welcome');
const welcome = new Welcome('我回来啦!');
welcome.begin();

/* output:
 * 开门
 * 脱鞋
 * 脱外套
 * 关门
 * /

Далее, нам нужны разные способы поздороваться — «обычное приветствие» и «крикнуть».

Соответственно у нас будет два модуляsay.jsа такжеshout.js,пройти через.tap()метод вsayHookЗарегистрируйте соответствующий метод на хуке.

// say.js
module.exports = function (welcome) {
    welcome.sayHook.tap('say', words => {
        console.log('轻声说:', words);
    });
};

// shout.js
module.exports = function (welcome) {
    welcome.sayHook.tap('shout', words => {
        console.log('出其不意的大喊一声:', words);
    });
};

Наконец, давайте изменимrun.js,Датьwelcomeзаявлениеshout.jsэтот модуль.

// run.js
const Welcome = require('./welcome');
const applyShoutPlugin = require('./shout');
const welcome = new Welcome('我回来啦!');
applyShoutPlugin(welcome);
welcome.begin();

/* output:
 * 开门
 * 脱鞋
 * 脱外套
 * 出其不意的大喊一声: 我回来啦!
 * 关门
 * /

Таким образом, мы отделяем реализацию приветствия от приветствия. Мы также можем использоватьsay.jsмодули и дажеshout.jsИспользуйте оба одновременно. Это похоже на создание «подключаемого» системного механизма — я могу выбирать, здороваться ли и как здороваться в соответствии со своими потребностями.

Хотя приведенный выше пример очень прост, он уже может помочь нам понять использование tapable и идею плагинов.

1.2. Плагины в веб-пакете

Прежде чем представить механизм плагинов веб-пакета, давайте кратко рассмотрим приведенный выше пример «идти домой»:

  • нашWelcomeКласс — это основной функциональный класс, который содержит определенные функциональные функции.begin()с крючкомsayHook;
  • run.jsМодуль отвечает за выполнение процесса и управление потоком кода;
  • наконец,say.jsа такжеshout.jsявляются независимыми «подключаемыми» модулями. Мы можем автономно подключаться к основному процессу по мере необходимости.

Поняв приведенный выше пример, вы можете провести хорошую аналогию с веб-пакетом:

Например, есть важный класс в WebPack -Compiler, который создает множество хуков, которые будут вызываться «повсеместно». он похож на нашWelcomeДобрый.

// Compiler类中的部分钩子

this.hooks = {
    /** @type {SyncBailHook<Compilation>} */
    shouldEmit: new SyncBailHook(["compilation"]),
    /** @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"]),
    ……
}

Затем плагин в webpack передаст функцию, которую необходимо выполнить, через.tap() / .tapAsync() / .tapPromise()и другие методы регистрируются на соответствующем хуке. Таким образом, когда веб-пакет вызывает соответствующий хук, функция в плагине будет выполняться автоматически.

Тогда возникает еще один вопрос: как webpack вызывает плагин и регистрирует метод в плагине на хук на этапе компиляции?

Для этой проблемы веб-пакет предусматривает, что для каждого экземпляра плагина должен быть один.apply()Способ, WebPack позвонит всем плагинам перед упаковкой.apply()метод, плагины могут регистрировать хуки в этом методе.

в вебпакеlib/webpack.js, есть следующий код:

if (options.plugins && Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
        plugin.apply(compiler);
    }
}

Приведенный выше код будет настроен из веб-пакета.pluginsполе, чтобы удалить экземпляры всех плагинов, а затем вызвать его.apply()метод и воляCompilerЭкземпляр передается в качестве параметра. Вот почему webpack требует, чтобы все наши плагины предоставляли.apply()Способ и оформление крючков.

Обратите внимание, и.call()так же, как здесь.apply()И это не родной метод js. Вы увидите много в исходном коде.call()а также.apply(), но это в основном не тот метод, который вы знаете.

2. Сборное время (в компиляторе)

В настоящее время уже есть некоторые высококачественные статьи, анализирующие WebPack в Интернете. Существуют также статьи о компиляции и внедрении процесса компиляции WebPack.

Compiler.run()

compilercompilation

  • compilercompilationlocalVarsrequireExtensions

3. Webpack Internal Plugin Relation

Webpack-Internal-Plugin-Relation

  • this.hooks.say = new SyncHook()
  • obj.hooks.say.tap('one', () => {...});
  • obj.hooks.say.call()

"lib/MultiCompiler.js": {
  "hooks": [
    {
      "name": "done",
      "line": 17
    },
    {
      "name": "invalid",
      "line": 18
    },
    {
      "name": "run",
      "line": 19
    },
    {
      "name": "watchClose",
      "line": 20
    },
    {
      "name": "watchRun",
      "line": 21
    }
  ],
  "taps": [
    {
      "hook": "done",
      "type": "tap",
      "plugin": "MultiCompiler",
      "line": 37
    },
    {
      "hook": "invalid",
      "type": "tap",
      "plugin": "MultiCompiler",
      "line": 48
    }
  ],
  "calls": [
    {
      "hook": "done",
      "type": "call",
      "line": 44
    }
  ]
}

Webpack-Internal-Plugin-Relation