Hot Module Replacement (HMR) — технология, позволяющая обновлять отдельные модули приложения во время разработки без полной перезагрузки страницы. В экосистеме Parcel HMR встроен по умолчанию и не требует дополнительной настройки для большинства проектов.
Основная задача HMR заключается в ускорении процесса разработки. Вместо полной пересборки приложения и обновления страницы изменяется только тот модуль, который был отредактирован. При этом текущее состояние приложения зачастую сохраняется.
Без HMR типичный цикл разработки выглядит следующим образом:
С использованием HMR процесс становится значительно эффективнее:
Система HMR в Parcel состоит из нескольких компонентов:
Общая схема работы выглядит так:
Изменение файла
│
▼
Parcel обнаруживает изменение
│
▼
Пересборка зависимых модулей
│
▼
Отправка уведомления через WebSocket
│
▼
Получение обновления браузером
│
▼
Замена модуля без перезагрузки страницы
После запуска сервера разработки Parcel начинает наблюдать за файловой системой.
Например, имеется структура проекта:
src/
├─ index.js
├─ app.js
└─ styles.css
Если изменяется файл:
// app.js
export function greeting() {
return "Hello";
}
Parcel немедленно фиксирует изменение и определяет, какие части графа зависимостей необходимо пересобрать.
В отличие от старых инструментов сборки, Parcel анализирует связи между модулями и старается минимизировать объём работы.
Для корректной работы HMR Parcel поддерживает внутренний граф зависимостей.
Рассмотрим пример:
// index.js
import { start } from "./app";
start();
// app.js
import "./styles.css";
export function start() {
console.log("Application started");
}
Граф будет выглядеть следующим образом:
index.js
│
▼
app.js
│
▼
styles.css
Когда изменяется styles.css, Parcel понимает, что
обновление требуется только для CSS-модуля, а не для всего
приложения.
Если изменяется app.js, обновляется только данный модуль
и его непосредственные потребители.
Для передачи уведомлений браузеру Parcel использует WebSocket.
После открытия страницы браузер автоматически устанавливает соединение с сервером разработки.
Схема взаимодействия:
Parcel Dev Server
│
│ WebSocket
▼
Browser
Когда сборка завершена, сервер отправляет специальное сообщение:
{
"type": "update",
"assets": [
{
"id": "app.js"
}
]
}
Браузер получает уведомление и запускает механизм обновления модулей.
Преимущество WebSocket заключается в постоянном соединении между клиентом и сервером, благодаря чему обновления доставляются практически мгновенно.
Во время разработки Parcel автоматически внедряет в сборку специальный код — HMR Runtime.
Этот код отвечает за:
Разработчику обычно не требуется взаимодействовать с этим механизмом напрямую.
Предположим, существует модуль:
// math.js
export function sum(a, b) {
return a + b;
}
И потребитель:
import { sum } from "./math";
console.log(sum(2, 3));
После изменения функции:
export function sum(a, b) {
return a + b + 100;
}
Parcel выполняет следующие действия:
math.js.Страница при этом не перезагружается.
CSS является одним из наиболее удобных объектов для горячей замены.
Например:
.title {
color: red;
}
После изменения:
.title {
color: blue;
}
Parcel:
Полная перезагрузка страницы не требуется.
Пользователь видит изменение практически мгновенно.
Одной из главных проблем горячей замены является сохранение состояния приложения.
Рассмотрим пример:
let counter = 0;
button.addEventListener("click", () => {
counter++;
});
При полной перезагрузке страницы значение переменной будет потеряно:
counter = 0
После HMR состояние может сохраниться, если обновляется только часть приложения и механизм замены модулей не уничтожает существующие объекты.
Однако гарантировать сохранение состояния во всех случаях невозможно.
Многое зависит от архитектуры приложения и используемого фреймворка.
Parcel предоставляет объект:
module.hot
Через него можно управлять процессом обновления.
Проверка доступности:
if (module.hot) {
console.log("HMR enabled");
}
Такой код выполняется только в режиме разработки.
Наиболее важный метод API — accept().
Пример:
if (module.hot) {
module.hot.accept();
}
Этот вызов сообщает Parcel, что модуль способен принимать обновления без полной перезагрузки приложения.
Когда файл изменяется, Parcel заменяет его новой версией.
Можно выполнить дополнительный код после получения обновления.
if (module.hot) {
module.hot.accept(() => {
console.log("Module updated");
});
}
После каждой горячей замены будет вызываться указанный обработчик.
Это полезно для повторной инициализации компонентов.
API HMR позволяет сохранять данные перед уничтожением модуля.
Пример:
let state = {
count: 5
};
if (module.hot) {
module.hot.dispose((data) => {
data.state = state;
});
}
Перед удалением модуля Parcel вызывает обработчик
dispose().
Объект data сохраняется и может быть использован новой
версией модуля.
После обновления сохранённые данные можно восстановить.
let state = {
count: 0
};
if (module.hot && module.hot.data) {
state = module.hot.data.state;
}
Такой механизм часто применяется при разработке сложных интерфейсов.
Метод dispose() вызывается непосредственно перед заменой
модуля.
Типичный пример:
const timer = setInterval(() => {
console.log("tick");
}, 1000);
if (module.hot) {
module.hot.dispose(() => {
clearInterval(timer);
});
}
Без очистки старый таймер продолжил бы работать после обновления модуля.
Это привело бы к накоплению обработчиков и утечкам памяти.
Не каждое изменение может быть обработано через HMR.
Например:
В таких ситуациях Parcel автоматически выполняет:
Full Page Reload
То есть обычную перезагрузку страницы.
Это гарантирует корректную работу приложения даже при невозможности горячей замены.
При использовании React горячая замена особенно полезна.
Изменение компонента:
export default function Button() {
return <button>Save</button>;
}
После редактирования:
export default function Button() {
return <button>Save changes</button>;
}
Компонент обновляется практически мгновенно.
В современных версиях React дополнительно применяется механизм Fast Refresh, который позволяет сохранять локальное состояние компонентов значительно эффективнее, чем классический HMR.
Parcel одинаково работает как с JavaScript, так и с TypeScript.
Пример:
export function multiply(
a: number,
b: number
): number {
return a * b;
}
После изменения файла:
export function multiply(
a: number,
b: number
): number {
return a * b * 10;
}
Parcel пересобирает только изменённый модуль и отправляет обновление через HMR.
Дополнительная настройка обычно не требуется.
Горячая замена поддерживается и для популярных препроцессоров:
Пример SCSS-файла:
$primary: #1976d2;
.button {
background: $primary;
}
После изменения переменной Parcel выполняет перекомпиляцию и обновляет стили без перезагрузки страницы.
Основные достоинства встроенной реализации:
Несмотря на преимущества, HMR не является универсальным решением.
Существуют ситуации, когда обновление может привести к непредсказуемому состоянию приложения:
Поэтому корректная поддержка HMR требует аккуратного управления
жизненным циклом модулей и очистки ресурсов через
dispose().
Полный цикл горячей замены в Parcel выглядит следующим образом:
Изменение файла
│
▼
Отслеживание изменения
│
▼
Пересборка модуля
│
▼
Отправка обновления через WebSocket
│
▼
Получение обновления браузером
│
▼
Вызов dispose()
│
▼
Удаление старого модуля
│
▼
Загрузка новой версии
│
▼
Вызов accept()
│
▼
Продолжение работы приложения
Именно такая последовательность обеспечивает быструю обратную связь во время разработки и делает Parcel одним из наиболее удобных сборщиков для современных JavaScript-проектов.