HMR (Hot Module Replacement) — механизм горячей замены модулей без полной перезагрузки страницы. Во время разработки Vite отслеживает изменения файлов и обновляет только изменённые части приложения. Это резко ускоряет цикл разработки, поскольку браузеру не требуется заново загружать весь документ, пересоздавать DOM и повторно выполнять все модули.
В традиционных сборщиках изменение даже небольшого файла нередко приводило к полной пересборке проекта и перезагрузке страницы. Vite использует нативные ES-модули и отдельный HMR-сервер, благодаря чему обновления происходят практически мгновенно.
Основные преимущества HMR:
Архитектура HMR в Vite основана на нескольких компонентах:
После запуска dev-сервера браузер устанавливает WebSocket-соединение с сервером Vite. Это соединение используется для передачи уведомлений об изменениях файлов.
Схема работы выглядит следующим образом:
Важно различать два механизма:
| Механизм | Что происходит |
|---|---|
| Full Reload | Полная перезагрузка страницы |
| HMR | Замена отдельных модулей |
Если модуль поддерживает HMR, Vite обновляет только его. Если модуль не может быть безопасно обновлён, выполняется full reload.
Например:
console.log('Hello')
Изменение такого файла обычно обрабатывается через HMR.
Однако изменение:
index.html
часто требует полной перезагрузки страницы.
Vite не создаёт единый bundle в dev-режиме. Каждый модуль загружается браузером отдельно через ES Modules.
Пример:
import { sum } from './math.js'
console.log(sum(2, 3))
При изменении math.js Vite обновит только этот
модуль.
Это принципиально отличается от старых систем сборки, где приходилось пересобирать большой bundle.
Vite поддерживает внутренний граф зависимостей.
Например:
main.js
├── app.js
│ ├── ui.js
│ └── api.js
└── styles.css
Если изменяется ui.js, сервер понимает:
ui.js;Такой подход минимизирует количество повторных загрузок.
HMR Boundary — модуль, принимающий обновление.
Пример:
if (import.meta.hot) {
import.meta.hot.accept()
}
Этот код сообщает Vite, что модуль способен обработать обновление самостоятельно.
Без boundary обновление будет распространяться вверх по графу зависимостей.
Vite предоставляет специальный API:
import.meta.hot
Этот объект существует только в dev-режиме.
Проверка:
if (import.meta.hot) {
console.log('HMR enabled')
}
В production-сборке данный код удаляется.
Метод accept() принимает обновление модуля.
if (import.meta.hot) {
import.meta.hot.accept()
}
После изменения модуль будет заново выполнен.
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
console.log('Updated module:', newModule)
})
}
Аргумент newModule содержит новую версию модуля.
Можно принимать обновления конкретных зависимостей.
Пример:
import { value } from './store.js'
console.log(value)
if (import.meta.hot) {
import.meta.hot.accept('./store.js', (module) => {
console.log(module.value)
})
}
В этом случае обновляется только store.js.
Перед заменой модуля может потребоваться освобождение ресурсов.
Например:
Пример:
const timer = setInterval(() => {
console.log('tick')
}, 1000)
if (import.meta.hot) {
import.meta.hot.dispose(() => {
clearInterval(timer)
})
}
Без dispose старые ресурсы будут накапливаться.
Vite позволяет сохранять данные между заменами модулей.
Используется объект:
import.meta.hot.data
Пример:
let counter = import.meta.hot?.data.counter || 0
counter++
console.log(counter)
if (import.meta.hot) {
import.meta.hot.dispose((data) => {
data.counter = counter
})
}
Значение сохранится между обновлениями.
Иногда модуль не способен корректно обработать HMR.
В таком случае можно вызвать:
import.meta.hot.invalidate()
Пример:
if (import.meta.hot) {
import.meta.hot.accept((module) => {
if (!module) {
import.meta.hot.invalidate()
}
})
}
После invalidate Vite инициирует полную перезагрузку.
Одно из самых быстрых обновлений в Vite — CSS HMR.
При изменении:
body {
background: black;
}
браузер обновляет стили без перезагрузки страницы и без потери состояния JavaScript.
Это достигается через динамическую замену <style>
или <link>.
Пример CSS Module:
.title {
color: red;
}
Импорт:
import styles from './App.module.css'
При изменении CSS Vite обновит только соответствующие стили.
Компонент не будет полностью пересоздан.
В React Vite использует Fast Refresh.
Fast Refresh:
Пример:
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onCl ick={() => setCount(count + 1)}>
{count}
</button>
)
}
После изменения JSX состояние count сохраняется.
Состояние может быть потеряно:
Например:
if (condition) {
useEffect(() => {})
}
Такой код нарушает правила hooks и может ломать Fast Refresh.
В Vue Vite обновляет:
Изменение шаблона:
<template>
<h1>Hello</h1>
</template>
обычно не уничтожает состояние компонента.
Но изменение логики компонента может потребовать его пересоздания.
Svelte использует собственную интеграцию HMR.
Поддерживаются:
Однако некоторые изменения всё равно вызывают полную перезагрузку компонента.
Vite использует WebSocket для передачи HMR-событий.
Пример внутреннего события:
{
"type": "update",
"updates": [
{
"path": "/src/App.jsx",
"type": "js-update"
}
]
}
Браузер получает сообщение и динамически импортирует новую версию модуля.
После обновления Vite выполняет повторный import:
import('/src/App.js?t=1710000000000')
Timestamp используется для обхода HTTP-кеша.
Vite активно использует HTTP-кэширование.
Но HMR должен загружать свежие версии файлов.
Для этого:
JSON-файлы тоже поддерживают горячую замену.
Пример:
{
"title": "Hello"
}
Импорт:
import data from './data.json'
Изменение JSON обновит модуль без полной перезагрузки.
Через плагины Vite способен обновлять:
Если плагин реализует HMR API, обновления происходят автоматически.
Плагин может управлять HMR самостоятельно.
Пример:
export default function myPlugin() {
return {
name: 'my-plugin',
handleHotUpdate(ctx) {
console.log(ctx.file)
}
}
}
handleHotUpdate() вызывается при изменении файлов.
Объект ctx содержит:
{
file,
modules,
server,
timestamp,
read
}
Описание полей:
| Поле | Назначение |
|---|---|
| file | изменённый файл |
| modules | связанные модули |
| server | экземпляр dev server |
| timestamp | время обновления |
| read | чтение содержимого файла |
Плагин может отправлять обновления вручную.
Пример:
server.ws.send({
type: 'custom',
event: 'my:update',
data: {
message: 'updated'
}
})
На клиенте:
if (import.meta.hot) {
import.meta.hot.on('my:update', (data) => {
console.log(data)
})
}
Это позволяет создавать собственные dev-инструменты.
Высокая скорость HMR в Vite достигается благодаря:
В больших проектах разница особенно заметна.
Не все изменения можно безопасно заменить «на лету».
Проблемные случаи:
Некоторые изменения требуют полной перезагрузки сервера.
Плохой пример:
window.globalState = {}
После HMR состояние может стать непредсказуемым.
Лучше изолировать side effects:
export function createState() {
return {}
}
Singleton часто создаёт проблемы.
Пример:
export const socket = new WebSocket(url)
После HMR могут появиться дублирующиеся соединения.
Правильнее:
let socket
export function getSocket() {
if (!socket) {
socket = new WebSocket(url)
}
return socket
}
И очищать соединение через dispose.
Если модуль содержит синтаксическую ошибку:
const =
Vite:
Это делает процесс разработки значительно удобнее.
Встроенный overlay отображает:
После исправления overlay исчезает автоматически.
Раздел server.hmr в конфигурации:
export default defineConfig({
server: {
hmr: {
protocol: 'ws',
host: 'localhost',
port: 24678
}
}
})
| Параметр | Назначение |
|---|---|
| protocol | ws или wss |
| host | адрес сервера |
| port | порт WebSocket |
| clientPort | порт клиента |
| overlay | показ overlay |
export default defineConfig({
server: {
hmr: {
overlay: false
}
}
})
При использовании HTTPS:
server: {
https: true,
hmr: {
protocol: 'wss'
}
}
Используется защищённое WebSocket-соединение.
В контейнерах часто возникают проблемы с file watching.
Решение:
server: {
watch: {
usePolling: true
}
}
Polling менее эффективен, но стабильнее в виртуализированных окружениях.
В WSL возможны:
Иногда помогает polling:
watch: {
usePolling: true
}
В monorepo HMR особенно полезен.
Vite способен обновлять:
Пример:
packages/
ui/
core/
apps/
dashboard/
Изменение ui может мгновенно обновить
dashboard.
| HMR | Live Reload |
|---|---|
| обновляет модуль | перезагружает страницу |
| сохраняет состояние | теряет состояние |
| быстрее | медленнее |
| точечные изменения | полная перезагрузка |
Live Reload — более простой механизм.
HMR значительно сложнее, но намного эффективнее.
Ключевая причина — отсутствие необходимости пересобирать bundle при каждом изменении.
Вместо этого:
Именно эта архитектура делает Vite одним из самых быстрых инструментов разработки frontend-приложений.