Skip to content

ホットモジュールリプレースメント (HMR) を使用すると、アプリケーションの実行中のモジュールを完全なページリロードなしで更新できます。これにより、アプリケーションの状態が保持され、開発エクスペリエンスが向上します。

NOTE

HMR は、Bun のフルスタック開発サーバーを使用する際にデフォルトで有効になります。

import.meta.hot API リファレンス

Bun は、Vite の import.meta.hot API をモデルにしたクライアントサイド HMR 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()Vite の 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 のみが更新された場合、1 つのファイルのみが再評価され、foo.ts のカウンターは再利用されます。

これは import.meta.hot.data と組み合わせて使用し、以前のモジュールから新しいモジュールに状態を転送できます。

コールバック付き

1 つのコールバックを指定すると、import.meta.hot.accept は Vite と同様に機能します。このモジュールのインポーターをパッチ適用する代わりに、新しいモジュールでコールバックを呼び出します。

ts
export const count = 0;

import.meta.hot.accept(newModule => {
  if (newModule) {
    // newModule は構文エラーが発生した場合 undefined です
    console.log("updated: count is now ", newModule.count);
  }
});

他のモジュールの accept

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

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

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

依存関係のモジュールが accept 可能であることを示します。依存関係が更新されると、新しいモジュールでコールバックが呼び出されます。

複数の依存関係付き

ts
import.meta.hot.accept(["./foo", "./bar"], newModules => {
  // newModules は各項目が更新されたモジュールに対応する配列です
  // またはそのモジュールがエラーを起こした場合 undefined です
});

複数の依存関係のモジュールが accept 可能であることを示します。このバリアントは依存関係の配列を受け入れ、コールバックは更新されたモジュールを受け取り、エラーがあった場合は undefined を受け取ります。

import.meta.hot.data

import.meta.hot.data は、ホットリプレースメント中のモジュールインスタンス間で状態を維持し、以前のバージョンから新しいバージョンへのデータ転送を可能にします。import.meta.hot.data に書き込まれると、Bun はこのモジュールを自己 accept 可能としてマークします(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 />); // 既存のルートを使い回す

本番環境では、data{} にインライン化されるため、状態ホルダーとして使用できません。

import.meta.hot.dispose()

on-dispose コールバックを追加します。これは以下で呼び出されます:

  • モジュールが別のコピーに置き換えられる直前(次が読み込まれる前)
  • モジュールがデタッチされた後(このモジュールへのすべてのインポートを削除、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 ランタイムからのイベントをリッスンするために使用されます。イベント名は、プラグインが互いに競合しないようにプレフィックスが付いています。

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

ファイルが置き換えられると、そのすべてのイベントリスナーは自動的に削除されます。

組み込みイベント

イベント発行時
bun:beforeUpdateホットアップデートが適用される前。
bun:afterUpdateホットアップデートが適用された後。
bun:beforeFullReload完全なページリロードが発生する前。
bun:beforePruneprune コールバックが呼び出される前。
bun:invalidateimport.meta.hot.invalidate() でモジュールが無効化されたとき
bun:errorビルドまたはランタイムエラーが発生したとき
bun:ws:disconnectHMR WebSocket 接続が失われたとき。これは開発サーバーがオフラインであることを示す可能性があります。
bun:ws:connectHMR WebSocket が接続または再接続したとき。

NOTE

Vite との互換性のため、上記のイベントは `bun:*` プレフィックスの代わりに `vite:*` プレフィックスでも利用できます。

Bun by www.bunjs.com.cn 編集