module.hot API: accept, decline, dispose

module.hot является частью механизма Hot Module Replacement (HMR) в Webpack и предоставляет API для управления жизненным циклом модулей во время их горячей замены без полной перезагрузки страницы. В рантайме Webpack каждый модуль может получать доступ к объекту module.hot, который существует только при включённом HMR. Через него определяется, как именно модуль реагирует на обновления зависимостей, как очищает ресурсы и как принимает новые версии кода.

Hot Module Replacement относится к категории Hot Module Replacement и позволяет обновлять отдельные части приложения, сохраняя состояние остальной системы. Поведение определяется не только конфигурацией devServer, но и тем, как каждый модуль реализует обработчики через module.hot.


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

if (module.hot) {
  // HMR логика
}

Каждый модуль рассматривается как узел графа зависимостей. Когда изменяется исходный файл, Webpack строит обновлённый модуль и анализирует цепочку зависимостей, чтобы определить границы обновления. Если модуль объявил обработчик accept, он становится точкой остановки распространения обновления.


module.hot.accept

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

Базовая форма

module.hot.accept();

В этом варианте модуль принимает обновления самого себя. Webpack заменяет код модуля, но не перезапускает приложение.

Приём зависимостей

Наиболее распространённый сценарий — отслеживание зависимостей:

module.hot.accept('./math.js', function () {
  const updatedMath = require('./math.js');
  console.log(updatedMath.sum(2, 2));
});

Здесь происходит следующее:

  • Webpack фиксирует зависимость ‘./math.js’ как наблюдаемую
  • при изменении файла модуль обновляется
  • callback выполняется после замены
  • разработчик вручную получает новую версию зависимости

Несколько зависимостей

module.hot.accept(['./a.js', './b.js'], () => {
  const a = require('./a.js');
  const b = require('./b.js');
});

Такой подход применяется при координированных обновлениях модулей, связанных общей логикой.

Важные особенности accept

  • accept не перезапускает модуль автоматически
  • состояние модуля сохраняется, если оно не пересоздаётся вручную
  • ответственность за повторное подключение логики лежит на разработчике
  • цепочка обновлений может останавливаться на первом accept

Гранулярное поведение accept и границы обновления

Если модуль не вызывает accept для изменённой зависимости, обновление поднимается вверх по дереву. Webpack ищет ближайший модуль, который способен принять обновление.

Пример поведения:

A → B → C

Если изменился C:

  • C не имеет accept → проверяется B
  • B имеет accept → обновление применяется на уровне B
  • A не затрагивается

Если ни один модуль не принимает обновление, происходит полный reload страницы.


module.hot.decline

decline используется для явного запрета принятия обновлений от зависимостей. Это инструмент управления границами HMR.

Базовое использование

module.hot.decline('./api.js');

Это означает, что любые изменения в ‘./api.js’ не должны приводить к частичному обновлению этого модуля.

Полный отказ

module.hot.decline();

В этом случае модуль полностью отказывается от HMR и заставляет систему выполнять полный reload при любых изменениях в его зависимости.

Поведение decline в графе модулей

decline устанавливает жесткую границу:

  • обновление не может пройти через модуль
  • Webpack не ищет accept выше по цепочке для этой ветки
  • инициируется full reload при попытке обновления

Сценарии применения decline

decline используется в случаях, когда:

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

module.hot.dispose

dispose применяется для очистки ресурсов перед заменой модуля. Это ключевой механизм предотвращения утечек памяти и дублирования побочных эффектов.

Базовая форма

module.hot.dispose(function (data) {
  clearInterval(timer);
  removeEventListener('resize', handler);
});

dispose вызывается:

  • перед тем как старый модуль будет заменён новым
  • перед удалением модуля из графа

Передача состояния через dispose

Одной из особенностей dispose является возможность передачи состояния между версиями модуля:

module.hot.dispose(function (data) {
  data.counter = counter;
});

Затем в новом модуле:

if (module.hot.data) {
  counter = module.hot.data.counter;
}

Таким образом:

  • старый модуль сохраняет состояние в data
  • новый модуль восстанавливает его
  • достигается непрерывность состояния UI

Типичные сценарии использования dispose

Очистка таймеров

let interval = setInterval(() => {}, 1000);

module.hot.dispose(() => {
  clearInterval(interval);
});

Удаление DOM-обработчиков

function handler() {}

window.addEventListener('scroll', handler);

module.hot.dispose(() => {
  window.removeEventListener('scroll', handler);
});

Сброс глобальных подписок

const subscription = store.subscribe(listener);

module.hot.dispose(() => {
  subscription.unsubscribe();
});

Взаимодействие accept и dispose

accept и dispose часто используются совместно:

function render() {
  console.log('render');
}

render();

if (module.hot) {
  module.hot.accept('./view.js', () => {
    render();
  });

  module.hot.dispose(() => {
    console.log('cleanup');
  });
}

Порядок выполнения при обновлении:

  1. dispose старого модуля
  2. замена кода модуля
  3. выполнение accept callback
  4. восстановление состояния (при наличии module.hot.data)

Поведение при ошибках обновления

Если в цепочке модулей:

  • отсутствует accept
  • или возникает ошибка выполнения обновлённого кода

Webpack может:

  • откатить обновление
  • выполнить full reload
  • вывести runtime error overlay (в dev server)

decline в таких случаях предотвращает частичное обновление и переводит систему в режим полной перезагрузки.


Границы применимости module.hot API

module.hot применяется только к:

  • JavaScript модулям
  • CSS через соответствующие loaders (частично)
  • модулям с поддержкой HMR runtime

Не поддерживаются:

  • серверные модули Node без HMR runtime
  • модули без участия в графе Webpack
  • код, выполняющийся вне bundle

Архитектурная роль API в графе модулей

module.hot формирует слой управления над стандартным графом зависимостей:

  • accept задаёт точки локальной стабилизации
  • decline формирует жёсткие границы распространения
  • dispose обеспечивает корректное разрушение состояния

Эти три механизма позволяют Webpack выполнять инкрементальную замену модулей без разрушения всего runtime, сохраняя консистентность приложения и минимизируя стоимость пересборки пользовательского интерфейса.