Протокол HMR: события и сообщения

HMR в Webpack опирается на двунаправленный канал связи между сервером разработки и клиентским рантаймом в браузере, чаще всего реализованный через WebSocket. Этот канал передаёт структурированные сообщения, описывающие состояние сборки, появление обновлений модулей и инструкции по их применению. Протокол HMR не является отдельным стандартом, он представляет собой набор соглашений поверх WebSocket между webpack-dev-server (или совместимым middleware) и HMR runtime.

После запуска dev-сервера клиентский скрипт HMR устанавливает WebSocket-соединение с сервером. В этот момент формируется контекст сборки: клиент получает идентификатор текущей сборки (hash), который используется как точка синхронизации.

Основной поток сообщений строится вокруг следующих типов событий:

  • состояние компиляции (compile / invalid / built)
  • уведомление о новой сборке (hash, ok)
  • передача информации об обновлённых модулях (hot-update)
  • ошибки сборки (errors, warnings)
  • команды управления обновлением (hot, liveReload)

Сообщение hash

Каждая успешная сборка Webpack генерирует уникальный hash. Сервер отправляет его клиенту:

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

Клиент сохраняет последний полученный hash и использует его как базу для запроса hot-update файлов.

Сообщение ok

После завершения компиляции сервер отправляет сигнал:

  • означает завершение сборки без критических ошибок
  • инициирует проверку наличия hot-обновлений
  • запускает процесс сравнения модулей между hash-версиями

На этом этапе runtime вызывает внутреннюю функцию проверки обновлений и обращается к update manifest.

Сообщение content-changed

Используется в сценариях, когда изменился контент, не затрагивающий JavaScript-граф напрямую:

  • обновление статических ресурсов
  • изменения HTML при использовании html-webpack-plugin
  • изменение зависимостей, не поддерживающих HMR

Поведение клиента зависит от конфигурации:

  • при включённом liveReload происходит полная перезагрузка страницы
  • при HMR-only режиме инициируется fallback-обновление

Сообщение errors и warnings

Сервер передаёт массив диагностических сообщений компиляции:

  • errors блокируют применение обновления
  • warnings допускают продолжение HMR-цикла

Клиентский runtime:

  • отображает overlay (если включён)
  • при ошибках прерывает apply-процесс
  • сохраняет состояние до исправления сборки

Механизм hot-update: структура сообщений

При изменении модулей Webpack формирует два ключевых артефакта:

  • hot-update.json (manifest изменений)
  • hot-update.js (код обновлённых чанков)

Сервер уведомляет клиента о наличии обновления через сообщение, содержащее:

  • новый hash
  • список затронутых чанков
  • URL для загрузки hot-update файлов

Клиент после получения этого сообщения инициирует загрузку обновлений через обычные HTTP-запросы.

Формат hot-update сообщения

Типичная структура данных включает:

  • c — список изменённых чанков
  • h — новый hash
  • r — необходимость полной перезагрузки
  • p — публичный путь к ассетам

Эта информация используется для построения URL:

  • /<publicPath>/<chunkId>.<hash>.hot-update.js
  • /<publicPath>/<hash>.hot-update.json

Событие hotDownloadManifest

После загрузки JSON-манifеста runtime получает список модулей, требующих обновления. Далее происходит переход к фазе загрузки hot-update чанков.

На этом этапе Webpack runtime:

  • сравнивает старые и новые модули
  • формирует граф зависимостей обновления
  • определяет границы HMR propagation

Событие hotDownloadUpdateChunk

Каждый изменённый чанк загружается отдельно:

  • выполняется динамический import через script tag или fetch-eval
  • код hot-update регистрируется в специальном registry
  • обновления не исполняются сразу, а буферизуются

Событие hotUpdateReady

После загрузки всех чанков runtime начинает фазу применения изменений:

  • вызывается механизм hotApply
  • определяется список модулей, поддерживающих обновление
  • строится цепочка accept handlers

module.hot и события внутри модулей

Каждый модуль может подписываться на HMR события через API:

  • module.hot.accept(deps, callback)
  • module.hot.dispose(callback)
  • module.hot.decline()

Эти обработчики становятся частью внутреннего графа событий.

accept

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

  • вызывается callback
  • происходит замена реализации модуля
  • зависимости пересобираются локально

dispose

Вызывается перед заменой модуля:

  • позволяет сохранить состояние
  • очистить ресурсы (таймеры, подписки)
  • передать данные в hot data store

decline

Запрещает hot replacement:

  • инициирует fallback до родительских модулей
  • при невозможности применения вызывает full reload

Алгоритм распространения обновлений (Hot Update Propagation)

После получения hot-update runtime строит граф распространения:

  1. старт с изменённых модулей
  2. поиск родителей в dependency graph
  3. проверка наличия accept у каждого узла
  4. остановка при достижении accept boundary

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

Событие hotApply

На этом этапе происходит фактическая замена модулей в runtime-кэше Webpack:

  • старые модули удаляются из module cache
  • новые функции заменяют реализации
  • обновляются ссылки зависимостей
  • выполняются accept callbacks

Важно, что порядок применения строго детерминирован графом зависимостей.

Событие hotDispose

Перед заменой каждого модуля вызываются dispose handlers:

  • очистка локального состояния
  • передача данных через module.hot.data
  • подготовка к замене реализации

Эти данные затем доступны новому экземпляру модуля.

Ошибки HMR-применения

Если во время apply возникает ошибка:

  • процесс откатывается
  • runtime сохраняет предыдущий стабильный state
  • инициируется full reload как fallback

Типичные причины:

  • несовместимые изменения API модуля
  • отсутствие accept boundary
  • runtime exception в dispose/accept handlers

Синхронизация hash-цепочки

Каждое обновление строго связано с предыдущим hash:

  • клиент запрашивает update только для следующего hash
  • сервер хранит историю сборок
  • при рассинхронизации выполняется full reload

Это предотвращает применение устаревших патчей.

Роль runtime bootstrap

HMR runtime внедряется в bundle как bootstrap слой:

  • перехватывает require
  • расширяет module cache
  • добавляет event bus поверх WebSocket
  • управляет жизненным циклом модулей

Он является посредником между Webpack bundle и dev-server протоколом.

Логика reconnect и деградации

При потере WebSocket-соединения:

  • клиент пытается переподключиться
  • сохраняется текущий hash
  • при восстановлении выполняется full sync
  • при невозможности синхронизации происходит reload страницы

Итеративный цикл HMR

Полный цикл сообщений выглядит следующим образом:

  1. compile start
  2. compile done
  3. hash
  4. ok
  5. hot-update manifest
  6. hot-update chunks download
  7. hotUpdateReady
  8. hotApply
  9. accept callbacks execution

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