Hot Module Replacement (HMR) permite que você atualize módulos em uma aplicação em execução sem precisar de um recarregamento completo da página. Isso preserva o estado da aplicação e melhora a experiência de desenvolvimento.
NOTE
HMR é habilitado por padrão ao usar o servidor de desenvolvimento full-stack do Bun.Referência da API import.meta.hot
O Bun implementa uma API HMR do lado do cliente modelada após a API import.meta.hot do Vite. Ela pode ser verificada com if (import.meta.hot), fazendo tree-shaking em produção.
if (import.meta.hot) {
// APIs HMR estão disponíveis.
}No entanto, esta verificação geralmente não é necessária pois o Bun irá eliminar código morto de todas as chamadas às APIs HMR em builds de produção.
// Esta chamada de função inteira será removida em produção!
import.meta.hot.dispose(() => {
console.log("dispose");
});NOTE
A API HMR ainda é um trabalho em andamento. Alguns recursos estão faltando. HMR pode ser desabilitado em `Bun.serve` definindo a opção development para `{ hmr: false }`.Métodos da API
| Método | Status | Notas |
|---|---|---|
hot.accept() | ✅ | Indica que uma atualização hot pode ser substituída graciosamente. |
hot.data | ✅ | Persiste dados entre avaliações de módulo. |
hot.dispose() | ✅ | Adiciona uma função callback para executar quando um módulo está prestes a ser substituído. |
hot.invalidate() | ❌ | |
hot.on() | ✅ | Anexa um event listener |
hot.off() | ✅ | Remove um event listener do on. |
hot.send() | ❌ | |
hot.prune() | 🚧 | NOTA: Callback nunca é chamado atualmente. |
hot.decline() | ✅ | No-op para corresponder ao import.meta.hot do Vite |
import.meta.hot.accept()
O método accept() indica que um módulo pode ser hot-replaced. Quando chamado sem argumentos, indica que este módulo pode ser substituído simplesmente reavaliando o arquivo. Após uma atualização hot, os importadores deste módulo serão automaticamente corrigidos.
// index.ts
import { getCount } from "./foo.ts";
console.log("count is ", getCount());
import.meta.hot.accept();
export function getNegativeCount() {
return -getCount();
}Isso cria uma fronteira de hot-reloading para todos os arquivos que index.ts importa. Isso significa que sempre que foo.ts ou qualquer uma de suas dependências forem salvas, a atualização irá subir até index.ts ser reavaliado. Arquivos que importam index.ts serão então corrigidos para importar a nova versão de getNegativeCount(). Se apenas index.ts for atualizado, apenas um arquivo será reavaliado e o contador em foo.ts é reutilizado.
Isso pode ser usado em combinação com import.meta.hot.data para transferir estado do módulo anterior para o novo.
Com callback
Quando fornecido um callback, import.meta.hot.accept funcionará como funciona no Vite. Em vez de corrigir os importadores deste módulo, ele chamará o callback com o novo módulo.
export const count = 0;
import.meta.hot.accept(newModule => {
if (newModule) {
// newModule é undefined quando ocorre SyntaxError
console.log("updated: count is now ", newModule.count);
}
});Aceitando outros módulos
import { count } from "./foo";
import.meta.hot.accept("./foo", () => {
if (!newModule) return;
console.log("updated: count is now ", count);
});Indica que o módulo de uma dependência pode ser aceito. Quando a dependência é atualizada, o callback será chamado com o novo módulo.
Com múltiplas dependências
import.meta.hot.accept(["./foo", "./bar"], newModules => {
// newModules é um array onde cada item corresponde ao módulo atualizado
// ou undefined se aquele módulo teve um erro de sintaxe
});Indica que os módulos de múltiplas dependências podem ser aceitos. Esta variante aceita um array de dependências, onde o callback receberá os módulos atualizados e undefined para qualquer um que teve erros.
import.meta.hot.data
import.meta.hot.data mantém estado entre instâncias de módulo durante substituição hot, habilitando transferência de dados de versões anteriores para novas. Quando import.meta.hot.data é escrito, o Bun também marcará este módulo como capaz de auto-aceitar (equivalente a chamar import.meta.hot.accept()).
import { createRoot } from "react-dom/client";
import { App } from "./app";
const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // reutiliza um root existenteEm produção, data é inline para {}, significando que não pode ser usado como um holder de estado.
import.meta.hot.dispose()
Anexa um callback on-dispose. Isso é chamado:
- Logo antes do módulo ser substituído por outra cópia (antes do próximo ser carregado)
- Após o módulo ser desanexado (removendo todos os imports para este módulo, veja
import.meta.hot.prune())
const sideEffect = setupSideEffect();
import.meta.hot.dispose(() => {
sideEffect.cleanup();
});Retornar uma promise irá atrasar a substituição do módulo até que o módulo seja descartado. Todos os callbacks dispose são chamados em paralelo.
import.meta.hot.prune()
Anexa um callback on-prune. Isso é chamado quando todos os imports para este módulo são removidos, mas o módulo foi previamente carregado.
Isso pode ser usado para limpar recursos que foram criados quando o módulo foi carregado. Diferentemente de import.meta.hot.dispose(), isso combina muito melhor com accept e data para gerenciar recursos stateful. Um exemplo completo gerenciando um WebSocket:
import { something } from "./something";
// Inicializa ou reutiliza uma conexão WebSocket
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// Se o import do módulo for removido, limpa a conexão WebSocket.
import.meta.hot.prune(() => {
ws.close();
});import.meta.hot.on() e off()
on() e off() são usados para ouvir eventos do runtime HMR. Nomes de eventos são prefixados com um prefixo para que plugins não entrem em conflito uns com os outros.
import.meta.hot.on("bun:beforeUpdate", () => {
console.log("before a hot update");
});Quando um arquivo é substituído, todos os seus event listeners são automaticamente removidos.
Eventos built-in
| Event | Emitido quando |
|---|---|
bun:beforeUpdate | antes de uma atualização hot ser aplicada. |
bun:afterUpdate | após uma atualização hot ser aplicada. |
bun:beforeFullReload | antes de um recarregamento completo da página acontecer. |
bun:beforePrune | antes dos callbacks prune serem chamados. |
bun:invalidate | quando um módulo é invalidado com import.meta.hot.invalidate() |
bun:error | quando ocorre um erro de build ou runtime |
bun:ws:disconnect | quando a conexão WebSocket HMR é perdida. Isso pode indicar que o servidor de desenvolvimento está offline. |
bun:ws:connect | quando o WebSocket HMR conecta ou reconecta. |
NOTE
Para compatibilidade com Vite, os eventos acima também estão disponíveis via prefixo `vite:*` em vez de `bun:*`.