Skip to content

HMR API — import.meta.hot

The per-module Hot Module Replacement API. It's present only in dev — always guard usage with if (import.meta.hot) so the block tree-shakes out of the production build. The API mirrors the widely-used shape, so framework integrations and plugins target a familiar surface.

For the conceptual walkthrough, see the HMR guide. This page is the reference.

ts
if (import.meta.hot) {
  import.meta.hot.accept((mod) => {
    // re-run with the updated module
  });
}

Types come from vantris/client.

accept

Mark this module (or specific dependencies) as an update boundary.

ts
// Self-accept, no callback: re-execute this module on change.
accept(): void;

// Self-accept with the updated module namespace.
accept(cb: (mod: ModuleNamespace | undefined) => void): void;

// Accept updates from a single dependency.
accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void;

// Accept updates from several dependencies.
accept(deps: readonly string[], cb: (mods: Array<ModuleNamespace | undefined>) => void): void;

ModuleNamespace is the re-imported module's exports (or undefined if it failed to load).

Self-accept:

ts
export function render() { /* … */ }
render();

if (import.meta.hot) {
  import.meta.hot.accept((mod) => mod?.render());
}

Accept a dependency:

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

if (import.meta.hot) {
  import.meta.hot.accept("./config.ts", (newConfig) => {
    applyConfig(newConfig?.config);
  });
}

dispose

Run before the module is replaced — tear down timers, listeners, and other side effects created by the current version. The callback receives the module's persistent data object.

ts
dispose(cb: (data: Record<string, unknown>) => void): void;
ts
const timer = setInterval(tick, 1000);

if (import.meta.hot) {
  import.meta.hot.dispose(() => clearInterval(timer));
}

prune

Run when the module is removed from the graph (no longer imported by anything). Use it for cleanup that should happen on removal, not on every update.

ts
prune(cb: (data: Record<string, unknown>) => void): void;

invalidate

Give up on HMR for this update and reload the page. Call it when the module can't safely apply an in-place update. The optional message is logged.

ts
invalidate(message?: string): void;
ts
if (import.meta.hot) {
  import.meta.hot.accept((mod) => {
    if (!mod) import.meta.hot!.invalidate("failed to re-import");
  });
}

decline

Opt this module out of HMR entirely — any change to it always triggers a full reload.

ts
decline(): void;

data

An object that persists across updates of this module. Write to it in dispose to hand state to the next version, and read from it at module top level to restore.

ts
readonly data: Record<string, unknown>;
ts
if (import.meta.hot) {
  let count = (import.meta.hot.data.count as number) ?? 0;
  import.meta.hot.dispose((data) => { data.count = count; });
}

Custom events — on / off / send

A two-way channel shared with the dev server and any server-side plugin.

ts
on(event: string, cb: (data: unknown) => void): void;
off(event: string, cb: (data: unknown) => void): void;
send(event: string, data?: unknown): void;

Listen for an event pushed by a plugin's handleHotUpdate or configureServer:

ts
if (import.meta.hot) {
  import.meta.hot.on("data:changed", ({ file }) => refetch(file));
}

Send an event back to the server:

ts
if (import.meta.hot) {
  import.meta.hot.send("client:ready", { at: Date.now() });
}

The full interface

ts
interface VantrisHotContext {
  readonly data: Record<string, unknown>;

  accept(): void;
  accept(cb: (mod: ModuleNamespace | undefined) => void): void;
  accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void;
  accept(deps: readonly string[], cb: (mods: Array<ModuleNamespace | undefined>) => void): void;

  dispose(cb: (data: Record<string, unknown>) => void): void;
  prune(cb: (data: Record<string, unknown>) => void): void;
  invalidate(message?: string): void;
  decline(): void;

  on(event: string, cb: (data: unknown) => void): void;
  off(event: string, cb: (data: unknown) => void): void;
  send(event: string, data?: unknown): void;
}

Server-side HMR types

When writing a plugin's handleHotUpdate, you work with the server-side HMR types, exported from vantris:

  • HotUpdateContextfile, the affected modules, and a server handle (send, …).
  • ModuleNode / ModuleInfo — nodes in the server module graph.
  • ModuleGraph — the graph of importer edges and accept boundaries.
  • HmrEngine — turns a change into update/reload/prune payloads.
  • HmrPayload / ClientMessage / Update — the wire protocol types.
ts
import type { HotUpdateContext, ModuleNode } from "vantris";

See the JavaScript API → HMR and the Plugin API → handleHotUpdate.

Production behaviour

import.meta.hot is undefined in a production build. Because you guard every use with if (import.meta.hot), the guarded code is statically dead and removed by tree shaking — none of your HMR wiring ships to users.

Released under the MIT License.