Hot Module Replacement опирается на структуру графа модулей, где каждое изменение проходит через цепочку зависимостей до ближайшей точки, способной принять обновление. Эти точки и формируют границы обновления. Поведение системы определяется не самим фактом изменения файла, а тем, как модуль связан с другими и где в цепочке задана явная готовность к приёму обновлений.
Каждый модуль в Webpack представляет узел графа зависимостей. При изменении исходного файла происходит пересборка затронутого модуля и всех зависимых от него узлов. Далее начинается фаза определения границ:
Таким образом граница обновления — это ближайший предок в графе
модулей, использующий module.hot.accept.
Основной механизм фиксации границы обновления — регистрация обработчика принятия изменений.
if (module.hot) {
module.hot.accept('./dep', function () {
// логика обновления
});
}
В этом случае модуль становится точкой остановки распространения
изменений. Любое изменение в ./dep или его зависимостях не
поднимается выше, а обрабатывается внутри текущего модуля.
Граница формируется не только на уровне одного импортируемого файла, но и на уровне поддерева зависимостей.
Если модуль не объявил accept, обновление поднимается
выше по цепочке импортов. Этот процесс называется bubbling.
Сценарий:
util.jsservice.js импортирует util.jsservice.js не имеет acceptapp.jsapp.js имеет accept, он становится
границейЭто создаёт иерархию устойчивости к изменениям, где устойчивость определяется явно, а не автоматически.
Некоторые типы модулей создают границы автоматически. Наиболее типичный пример — стили.
CSS-модули, обработанные через style-loader, обычно
регистрируют accept внутри себя. Это позволяет:
<style> теги в DOMТакие модули становятся изолированными HMR-блоками, где граница совпадает с самим модулем стилей.
Границы обновления делятся на два уровня:
Фиксируются внутри одного модуля:
module.hot.accept('./view', () => {
renderView();
});
Определяются корневыми модулями приложения:
Если глобальная граница отсутствует, любое изменение приводит к полной перезагрузке приложения.
Граница может быть не только объявлена, но и запрещена.
module.hot.decline('./critical-module');
Это означает, что любые изменения в указанной зависимости не будут обрабатываться в рамках HMR. Поведение:
Decline используется для модулей, где частичная замена опасна:
Границы HMR подчиняются строгим правилам графа зависимостей:
Эти правила обеспечивают детерминированное поведение обновлений независимо от структуры проекта.
В сложных приложениях один модуль может входить сразу в несколько зон ответственности. Например:
Если разные части графа задают разные accept-точки, HMR
runtime выбирает ближайшую границу относительно изменённого узла. Это
означает:
В архитектурах на базе компонентов (React, Vue, Svelte через Webpack loader’ы) границы часто совпадают с компонентами верхнего уровня.
Типичная структура:
Такой подход позволяет:
Граница обновления влияет на сохранение состояния:
module.hot.disposemodule.hot.dispose((data) => {
data.state = currentState;
});
На этапе принятия обновления это состояние может быть восстановлено, что делает границы ключевым механизмом управления жизненным циклом runtime.
При отсутствии ожидаемого обновления анализируется цепочка:
accept выше по графуdeclineТипичные ситуации:
Если ни один модуль не объявляет accept:
Это поведение делает систему предсказуемой: частичное обновление возможно только при явном указании границ.
Организация кода напрямую влияет на эффективность HMR:
Граф модулей становится ключевым фактором производительности HMR, а границы — его управляющим механизмом.