Skip to content
🛠️
@tuvix.js/devtools
DevTools
オーケストレーター向けのページ内デバッグオーバーレイです。登録済みおよびマウント済みのマイクロアプリ、現在のルート、最新のイベントバスのトラフィックを一覧表示します。

インストール ​

bash
npm install -D @tuvix.js/devtools
# or
pnpm add -D @tuvix.js/devtools

使いどころ ​

オーケストレーターが把握しているマイクロアプリ、そのうち現在マウントされているもの、バスを流れるイベントを確認したいときに、開発環境でインストールします。オーバーレイはページの右下隅に表示され、1秒ごとに更新されます。

コンポーネントツリー、ネットワークのタイミング、イベントのペイロードをUIで確認したい場合は、ブラウザのDevToolsやフレームワークのdevtools拡張機能を使ってください。このパッケージが表示するのは、アプリ名、1つのルート文字列、タイムスタンプ付きのイベント名です。ペイロードはロガーに保持され、コンソールから読み取れます。

クイックスタート ​

ts
import { createOrchestrator } from '@tuvix.js/core';

const orchestrator = createOrchestrator({
  router: {
    mode: 'history',
    routes: [
      { path: '/dashboard/*', app: 'dashboard' },
      { path: '/settings/*', app: 'settings' },
    ],
  },
});

// Install before register() so the log records tuvix:app:registered.
if (process.env.NODE_ENV !== 'production') {
  const { installDevTools } = await import('@tuvix.js/devtools');
  installDevTools(orchestrator);
}

orchestrator.register({ name: 'dashboard', entry: '/apps/dashboard.js', container: '#main' });
orchestrator.register({ name: 'settings', entry: '/apps/settings.js', container: '#main' });

await orchestrator.start();

/dashboardを開くと、オーバーレイにはdashboardが緑の点で、settingsが白の点で表示されます。Route行には/dashboardと表示され、Eventsリストにはdashboardのマウントに伴うtuvix:app:status-changeとtuvix:app:mountのイベントが表示されます。

パネルに表示される内容 ​

installDevToolsはdocument.bodyに<div id="tuvix-devtools">を1つ追加します。この要素には、丸いトグルボタン(右下隅から12pxの位置に固定、z-index: 99999)と、その上に幅280pxのパネルが含まれます。パネルには3つのセクションがあります。

セクションソース内容
Appsorchestrator.getRegisteredApps()とgetMountedApps()登録済みアプリごとに1行。緑の点はステータスがmountedであることを示します。白の点は、loadingやerrorを含むそれ以外のすべてのステータスを示します。
Routeorchestrator.getRouter()?.currentPathルーターの現在のパス。baseプレフィックスを含まず、クエリ文字列を含みます。オーケストレーターにルーターがない場合は/を表示します。
Eventsorchestrator.getEventBus()にアタッチされたEventLogger最新の10件のイベントを新しい順に表示します。各行にはローカル時刻、ダッシュ、イベント名が表示されます。

デフォルトではオーケストレーターはgetGlobalBus()を使うため、Eventsリストにはオーケストレーター自身のイベントに加えて、マイクロアプリが共有バスに発行したイベントも表示されます。

イベント発行されるタイミング
tuvix:app:registeredregister()がアプリを追加したとき
tuvix:app:bootstrap, tuvix:app:mount, tuvix:app:unmountアプリがそのライフサイクルのステップを通過したとき
tuvix:app:errorアプリの読み込み、ブートストラップ、マウントのいずれかが失敗したとき
tuvix:app:status-changeアプリのステータスが変わったとき。1回のマウントで複数回発生します。
tuvix:route:changeルーターがナビゲーションしたとき
tuvix:started, tuvix:destroyedstart()とdestroy()が完了したとき

パネルはwindow.__TUVIX_DEVTOOLS__ = { panel, logger, orchestrator }も公開します。パネルに表示されないペイロードを読むには、ブラウザのコンソールで__TUVIX_DEVTOOLS__.logger.getEntries()を実行してください。

API ​

installDevTools ​

ts
import type { DevToolsOptions } from '@tuvix.js/devtools';
import type { IEventBus } from '@tuvix.js/core';

declare function installDevTools(
  orchestrator: {
    getRegisteredApps(): string[];
    getMountedApps(): string[];
    getEventBus(): IEventBus;
    getRouter(): { currentPath: string } | null;
  },
  options?: DevToolsOptions
): () => void;

最初の引数には、これら4つのメソッドを持つ任意のオブジェクトを渡せます。@tuvix.js/coreまたはtuvix.jsのOrchestratorが該当します。

オプション型デフォルト説明
maxLogEntriesnumber200ロガーが保持するイベントの最大数。パネルが表示するのはそのうち10件で、残りは__TUVIX_DEVTOOLS__.loggerから読み取れます。
autoOpenbooleantruefalseの場合、パネルを非表示の状態でマウントします。トグルボタンは表示されたままで、それを押すとパネルが開きます。
updateIntervalnumber1000パネルを更新する間隔(ミリ秒)。

呼び出しのたびに、次の処理を行います。

  1. DevToolsPanelとEventLogger(maxLogEntries)を作成します。
  2. onAnyでロガーをorchestrator.getEventBus()にアタッチします。
  3. パネルをマウントし、autoOpenがfalseの場合は非表示にします。
  4. オーケストレーターを読み取ってpanel.update()を呼び出すsetIntervalを開始します。
  5. window.__TUVIX_DEVTOOLS__を設定します。

インターバルをクリアし、ロガーをデタッチし、パネルを削除し、window.__TUVIX_DEVTOOLS__を削除するクリーンアップ関数を返します。

ts
import { createOrchestrator } from '@tuvix.js/core';
import { installDevTools } from '@tuvix.js/devtools';

const orchestrator = createOrchestrator();
const removeDevTools = installDevTools(orchestrator, {
  autoOpen: false,
  updateInterval: 250,
  maxLogEntries: 500,
});

// Later, for example in a hot-module-replacement dispose hook:
removeDevTools();
await orchestrator.destroy();

EventLogger ​

ts
class EventLogger {
  constructor(maxEntries?: number); // default 200
  attach(bus: IEventBus): void;
  detach(): void;
  getEntries(): ReadonlyArray<{ timestamp: number; event: string; data: unknown }>;
  getRecent(count?: number): ReadonlyArray<{ timestamp: number; event: string; data: unknown }>; // default 20
  clear(): void;
}

EventLoggerはbus.onAnyを通じて、バス上のすべてのイベントを記録します。各エントリーはtimestamp(emit時点のDate.now())、event(イベント名)、data(emitされたままのペイロード。クローンはされません)を持ちます。ログがmaxEntriesを超えると、ロガーは最も古いエントリーを破棄します。

メソッド動作
attach(bus)busのすべてのイベントを購読します。バスが破棄済みの場合は例外を投げます。
detach()購読を解除します。記録済みのエントリーは残ります。
getEntries()ログ全体を古い順に返します。
getRecent(count)最後のcount件のエントリーを古い順に返します。
clear()ログを空にし、購読は維持します。

パネルなしで使い、テストでバスのトラフィックを検証したり、ページのイベントをダンプしたりできます。

ts
import { createEventBus } from '@tuvix.js/core';
import { EventLogger } from '@tuvix.js/devtools';

const bus = createEventBus();
const logger = new EventLogger(50);
logger.attach(bus);

bus.emit('cart:add', { sku: 'A-1' });

console.log(logger.getRecent(1)); // [{ timestamp: 1761..., event: 'cart:add', data: { sku: 'A-1' } }]
logger.detach();

DevToolsPanel ​

ts
class DevToolsPanel {
  constructor();
  mount(): void;
  unmount(): void;
  toggle(): void;
  update(state: {
    registeredApps: string[];
    mountedApps: string[];
    currentRoute: string;
    events: Array<{ timestamp: number; event: string }>;
  }): void;
}

DevToolsPanelはオーバーレイのDOM部分で、ポーリングは行いません。表示したい状態を渡してupdate()を呼び出します。

メソッド動作
mount()#tuvix-devtoolsを開いた状態でdocument.bodyに追加します。同じインスタンスで2回目に呼び出しても何もしません。
unmount()要素を削除します。
toggle()パネル本体の表示と非表示を切り替えます。トグルボタンは残ります。mount()の前は何もしません。
update(state)Apps、Route、Eventsの各セクションを再構築します。eventsの最後の10件を新しい順に表示します。mount()の前は何もしません。
ts
import { DevToolsPanel } from '@tuvix.js/devtools';

const panel = new DevToolsPanel();
panel.mount();
panel.update({
  registeredApps: ['header', 'cart'],
  mountedApps: ['header'],
  currentRoute: '/checkout',
  events: [{ timestamp: Date.now(), event: 'cart:open' }],
});

型 ​

名前説明
DevToolsOptionsinstallDevToolsのオプション:maxLogEntries、autoOpen、updateInterval。

オーケストレーターの形とログエントリーの形はエクスポートされていません。型として必要な場合は、Parameters<typeof installDevTools>[0]とReturnType<EventLogger['getEntries']>[number]を使ってください。

本番ビルド ​

パッケージは"sideEffects": falseを宣言していますが、コードから呼び出す静的なimport { installDevTools } from '@tuvix.js/devtools'はバンドルに含まれます。クイックスタートのように、ビルド時のフラグで条件分岐し、動的なimport()で読み込んでください。バンドラーはprocess.env.NODE_ENVを文字列に置き換え、到達しない分岐とそのチャンクを取り除きます。Viteアプリでは代わりにimport.meta.env.DEVで判定できます。

本番環境でオーバーレイを有効のままにすると、3つのDOMセクションを毎秒再構築するsetInterval、すべてのバスイベントの購読、そしてページ上の任意のスクリプトにオーケストレーターを渡してしまうグローバル変数というコストがかかります。

エッジケース ​

  • register()とstart()の前にインストールしてください。ロガーが受け取るのはattachの後に発行されたイベントだけで、パネルは最初の更新(呼び出しからupdateIntervalミリ秒後)まで空のままです。
  • installDevToolsにはdocumentとwindowが必要です。サーバーレンダリング中に呼び出すとReferenceErrorを投げます。クライアント専用のコードから呼び出してください。
  • connectRouter()を使いrouterオプションを指定しない場合、getRouter()はnullを返し、Route行には/が表示されます。
  • getMountedApps()が数えるのはmountedステータスのみです。mountingやerrorのアプリには白の点が付き、パネルがステータス名を表示することはありません。
  • プライベートなバス(createOrchestrator({ eventBus: {...} }))はorchestrator.destroy()で破棄されます。その後にインストールするとonAnyから例外が投げられます。デフォルトのグローバルバスでは、クリーンアップ関数を呼び出すまで、ロガーはdestroy()の後も記録を続けます。
  • 呼び出すたびに新しいパネルが作成されます。2回呼び出すと同じ要素IDを持つ2つのオーバーレイがページに配置され、2回目の呼び出しがwindow.__TUVIX_DEVTOOLS__を上書きします。その後、1回目のクリーンアップ関数は2回目の呼び出しが設定したグローバルを削除します。
  • 1つのインスタンスでEventLogger.attach()を2回呼び出すと、保存された購読解除関数が置き換えられます。その後のdetach()は2つ目の購読しか解除しません。
  • ログがmaxLogEntriesに達すると、ロガーは新しいイベントごとに配列をコピーします。イベントの多いバスを持つページでは、maxLogEntriesを数百程度に抑えてください。
  • パネルはアプリ名、ルート、イベント名をtextContentで書き込むため、イベント名に含まれるマークアップはテキストとして表示されます。

対象外のこと ​

  • キーボードショートカットはなく、位置、サイズ、テーマのオプションもありません。オーバーレイは常にインラインスタイルで右下に表示されます。
  • イベントのペイロード、エントリーURL、ローダーのキャッシュ状態、マウントの所要時間はUIに表示されません。ペイロードは__TUVIX_DEVTOOLS__.logger.getEntries()から読み取ってください。
  • ブラウザ拡張機能はなく、React、Vue、Svelte、Angularのdevtoolsとの連携もありません。
  • タイムトラベルやイベントの再送はありません。ロガーは記録するだけで、再発行はしません。
  • 本番ビルドで自動的に取り除かれることはありません。インポートの条件分岐は自分で行ってください。