Fast Refresh и его ограничения

Fast Refresh в Vite представляет собой развитие классического Hot Module Replacement (HMR), ориентированное на сохранение состояния компонентов при изменениях кода. Основная цель механизма — максимально быстрое отражение изменений в браузере без полной перезагрузки страницы и без потери состояния приложения.

В основе работы лежит сочетание нативного ESM (ES Modules) и WebSocket-соединения между dev-сервером и клиентом. При изменении файла Vite пересобирает только затронутый модуль и отправляет обновление в браузер, где runtime-часть обновляет граф модулей.


Архитектура обновления модулей

Механизм Fast Refresh опирается на несколько ключевых компонентов:

  • dev-сервер Vite, отслеживающий изменения файлов через chokidar
  • модульный граф зависимостей, построенный на основе ESM
  • WebSocket канал для доставки HMR-сообщений
  • клиентский runtime HMR, внедряемый в приложение

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

  1. Изменяется файл в файловой системе
  2. Vite фиксирует изменение через watcher
  3. Пересобирается только изменённый модуль
  4. Формируется HMR update payload
  5. Сообщение отправляется через WebSocket
  6. Клиентский runtime принимает обновление и применяет его к модульному графу

Ключевое отличие Vite от традиционных бандлеров заключается в отсутствии необходимости пересобирать весь бандл — обновляется строго минимальная единица модуля.


Разделение ответственности: HMR и Fast Refresh

Hot Module Replacement — низкоуровневый механизм обновления модулей. Fast Refresh — надстройка над HMR, добавляющая семантическое понимание UI-компонентов.

HMR отвечает за:

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

Fast Refresh добавляет:

  • сохранение состояния компонентов
  • восстановление дерева UI после обновления
  • контроль корректности обновлённого модуля

В экосистеме Vite Fast Refresh чаще всего используется через интеграции:

  • React Fast Refresh
  • Vue HMR
  • Svelte HMR

Fast Refresh в React через Vite

React Fast Refresh реализован через @vitejs/plugin-react, который подключает специальный Babel/ESBuild трансформер и runtime обработки обновлений.

Основной принцип:

  • каждый React-компонент становится «refresh boundary»
  • состояние сохраняется, если модуль считается совместимым
  • при несовместимости выполняется полная перерисовка

Условная логика определения boundary:

  • модуль экспортирует React-компоненты
  • изменения не затрагивают сигнатуру компонента
  • не изменяется порядок хуков

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

export function Button() {
  const [count, setCount] = useState(0);

  return (
    <button onCl ick={() => setCount(c => c + 1)}>
      {count}
    </button>
  );
}

При изменении разметки внутри компонента Fast Refresh обновит только render-часть, сохранив count.

Если же изменить структуру хуков:

if (condition) {
  useEffect(() => {}, []);
}

система перестанет считать компонент безопасным для сохранения состояния.


Vue и реактивный HMR

Во Vue Fast Refresh реализован через встроенный HMR API и особенности SFC (Single File Components).

Каждый .vue файл разбивается на блоки:

  • <template>
  • <script>
  • <style>

Изменения обрабатываются точечно:

  • template → обновление render-функции
  • script → замена логики компонента
  • style → инъекция новых CSS без перезагрузки

Vue HMR сохраняет состояние компонента за счёт привязки экземпляра компонента к идентификатору модуля. При обновлении runtime выполняет патчинг текущего экземпляра, а не пересоздание дерева.


Механизм сохранения состояния

Сохранение состояния в Fast Refresh основано на двух принципах:

  1. Идентификация компонента как стабильной единицы
  2. Возможность повторного выполнения модуля без потери runtime-ссылок

React использует внутренний registry компонентов, где каждая функция-компонент сопоставляется с hot boundary.

Vue использует реактивную систему и привязку экземпляров к proxy-объектам.

При обновлении:

  • старый модуль сохраняет состояние через closure или internal store
  • новый модуль получает восстановленные props/state
  • выполняется reconcile без полной размонтировки

Ограничения Fast Refresh в Vite

Потеря состояния при структурных изменениях

Fast Refresh не гарантирует сохранение состояния при изменении структуры компонента:

  • изменение порядка хуков в React
  • добавление/удаление хуков в середине логики
  • изменение сигнатуры компонента

В таких случаях происходит full reload модуля.


Нарушение boundary-компонентов

Fast Refresh зависит от корректного определения границ компонентов. Нарушение этих границ приводит к сбросу состояния:

  • экспорт нескольких компонентов с динамической логикой
  • смешивание UI и non-UI кода в одном модуле
  • использование побочных эффектов на уровне модуля

Ограничения при работе с глобальным состоянием

Fast Refresh не контролирует внешние источники состояния:

  • Redux store
  • Zustand
  • глобальные singleton-объекты

Хотя UI может обновиться без перезагрузки, состояние вне компонента может не синхронизироваться с новой версией кода, особенно при изменении reducer-логики.


Ограничения при изменении CSS-in-JS и стилей

Обновление стилей происходит частично, но имеет особенности:

  • CSS Modules могут пересоздавать классы
  • styled-components могут генерировать новые hash-идентификаторы
  • возможны визуальные «мигания» при замене стилей

Несовместимость с некоторыми runtime-изменениями

Fast Refresh не способен безопасно обработать:

  • изменение импортов, влияющих на side effects
  • динамическое создание компонентов внутри функций
  • изменение структуры роутинга в одном модуле

В таких случаях Vite инициирует full page reload вместо HMR update.


Потери состояния при изменении экспорта модуля

Если модуль изменяет свой экспорт:

  • function → class
  • named export → default export
  • добавление новых экспортируемых сущностей, влияющих на границы

runtime не может сопоставить старую и новую версии, и происходит сброс дерева компонентов.


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

При ошибках в обновлённом модуле Fast Refresh работает в деградированном режиме:

  • ошибка отображается в overlay
  • предыдущая версия модуля остаётся активной
  • обновление не применяется до устранения ошибки

Это позволяет сохранить стабильность интерфейса без перезагрузки приложения.


Взаимодействие Fast Refresh с Vite plugin system

Vite плагины могут влиять на поведение Fast Refresh через:

  • трансформацию AST
  • изменение структуры модулей
  • внедрение виртуальных модулей

Некорректные плагины способны:

  • нарушить HMR boundary
  • отключить сохранение состояния
  • вызвать полные перезагрузки при каждом изменении

Особенно чувствительны плагины, изменяющие экспортную структуру файлов или добавляющие обёртки вокруг компонентов.


Поведение при цепочке зависимостей

При изменении модуля обновление распространяется по графу зависимостей:

  • обновляется изменённый модуль
  • пересобираются зависимые модули
  • выполняется проверка совместимости Fast Refresh boundary

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


Производственные ограничения и различия dev/prod

Fast Refresh работает исключительно в режиме разработки.

В production-сборке:

  • HMR отсутствует
  • состояние не сохраняется
  • применяется статическая сборка через Rollup

Это создаёт различие между dev- и prod-поведением, особенно заметное при сложной логике инициализации компонентов.


Типичные причины деградации Fast Refresh

  • нарушение правил React Hooks
  • изменение структуры экспортов в модуле
  • нестабильные side effects на уровне файла
  • некорректные Vite плагины
  • смешение UI и non-UI логики
  • глубокие изменения в дереве зависимостей

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