Skip to content

Замена горячих модулей (HMR) позволяет обновлять модули в работающем приложении без необходимости полной перезагрузки страницы. Это сохраняет состояние приложения и улучшает опыт разработки.

NOTE

HMR включен по умолчанию при использовании полнофункционального dev-сервера Bun.

Справочник API import.meta.hot

Bun реализует клиентский HMR API по образцу Vite's import.meta.hot API. Его можно проверить с помощью if (import.meta.hot), удаляя его из продакшен-сборки.

ts
if (import.meta.hot) {
  // HMR API доступны.
}

Однако эта проверка часто не нужна так как Bun будет удалять мертвый код для всех HMR API в продакшен-сборках.

ts
// Этот вызов функции будет полностью удален в продакшене!
import.meta.hot.dispose(() => {
  console.log("dispose");
});

NOTE

HMR API все еще находится в разработке. Некоторые функции отсутствуют. HMR можно отключить в `Bun.serve` установив опцию development в `{ hmr: false }`.

Методы API

МетодСтатусПримечания
hot.accept()Указывает что горячее обновление может быть заменено плавно.
hot.dataСохраняет данные между вычислениями модулей.
hot.dispose()Добавляет функцию обратного вызова которая выполняется когда модуль собирается быть заменен.
hot.invalidate()
hot.on()Прикрепляет обработчик событий
hot.off()Удаляет обработчик событий из on.
hot.send()
hot.prune()🚧ПРИМЕЧАНИЕ: Обратный вызов в настоящее время никогда не вызывается.
hot.decline()No-op для соответствия Vite's import.meta.hot

import.meta.hot.accept()

Метод accept() указывает что модуль может быть горячо заменен. При вызове без аргументов он указывает что этот модуль может быть заменен просто повторным вычислением файла. После горячего обновления импортеры этого модуля будут автоматически обновлены.

ts
// index.ts
import { getCount } from "./foo.ts";

console.log("count is ", getCount());

import.meta.hot.accept();

export function getNegativeCount() {
  return -getCount();
}

Это создает границу горячего перезагружения для всех файлов которые импортирует index.ts. Это означает что всякий раз когда foo.ts или любая из его зависимостей сохраняется обновление будет подниматься до index.ts который будет повторно вычислен. Файлы которые импортируют index.ts будут затем обновлены для импорта новой версии getNegativeCount(). Если обновляется только index.ts только один файл будет повторно вычислен и счетчик в foo.ts будет переиспользован.

Это может использоваться в комбинации с import.meta.hot.data для передачи состояния из предыдущего модуля в новый.

С обратным вызовом

При предоставлении одного обратного вызова import.meta.hot.accept будет функционировать как в Vite. Вместо обновления импортеров этого модуля он вызовет обратный вызов с новым модулем.

ts
export const count = 0;

import.meta.hot.accept(newModule => {
  if (newModule) {
    // newModule имеет значение undefined когда произошла SyntaxError
    console.log("updated: count is now ", newModule.count);
  }
});

Принятие других модулей

ts
import { count } from "./foo";

import.meta.hot.accept("./foo", () => {
  if (!newModule) return;

  console.log("updated: count is now ", count);
});

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

С несколькими зависимостями

ts
import.meta.hot.accept(["./foo", "./bar"], newModules => {
  // newModules это массив где каждый элемент соответствует обновленному модулю
  // или undefined если этот модуль имел синтаксическую ошибку
});

Указывает что модули нескольких зависимостей могут быть приняты. Этот вариант принимает массив зависимостей где обратный вызов получит обновленные модули и undefined для любых которые имели ошибки.

import.meta.hot.data

import.meta.hot.data поддерживает состояние между экземплярами модулей во время горячей замены позволяя передачу данных из предыдущих версий в новые. Когда в import.meta.hot.data записывается Bun также помечает этот модуль как способный принимать себя (эквивалент вызова import.meta.hot.accept()).

tsx
import { createRoot } from "react-dom/client";
import { App } from "./app";

const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // переиспользовать существующий root

В продакшене data встраивается как {} что означает что он не может использоваться как держатель состояния.

import.meta.hot.dispose()

Прикрепляет обратный вызов on-dispose. Это вызывается:

  • Just перед заменой модуля другой копией (до загрузки следующего)
  • После отсоединения модуля (удаление всех импортов в этот модуль см. import.meta.hot.prune())
ts
const sideEffect = setupSideEffect();

import.meta.hot.dispose(() => {
  sideEffect.cleanup();
});

Возврат promise задержит замену модуля до тех пор пока модуль не будет удален. Все обратные вызовы dispose вызываются параллельно.

import.meta.hot.prune()

Прикрепляет обратный вызов on-prune. Это вызывается когда все импорты в этот модуль удалены но модуль был ранее загружен.

Это может использоваться для очистки ресурсов которые были созданы при загрузке модуля. В отличие от import.meta.hot.dispose() это гораздо лучше сочетается с accept и data для управления ресурсами с состоянием. Полный пример управления WebSocket:

ts
import { something } from "./something";

// Инициализировать или переиспользовать WebSocket соединение
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));

// Если импорт модуля удален очистить WebSocket соединение.
import.meta.hot.prune(() => {
  ws.close();
});

import.meta.hot.on() и off()

on() и off() используются для прослушивания событий от HMR runtime. Имена событий имеют префикс чтобы плагины не конфликтовали друг с другом.

ts
import.meta.hot.on("bun:beforeUpdate", () => {
  console.log("before a hot update");
});

Когда файл заменяется все его обработчики событий автоматически удаляются.

Встроенные события

СобытиеВызывается когда
bun:beforeUpdateперед применением горячего обновления.
bun:afterUpdateпосле применения горячего обновления.
bun:beforeFullReloadперед полной перезагрузкой страницы.
bun:beforePruneперед вызовом обратных вызовов prune.
bun:invalidateкогда модуль аннулируется с import.meta.hot.invalidate()
bun:errorкогда происходит ошибка сборки или времени выполнения
bun:ws:disconnectкогда соединение HMR WebSocket потеряно. Это может указывать что dev-сервер офлайн.
bun:ws:connectкогда HMR WebSocket подключается или переподключается.

NOTE

Для совместимости с Vite вышеуказанные события также доступны с префиксом `vite:*` вместо `bun:*`.

Bun от www.bunjs.com.cn