Skip to content

ライフサイクルイベント ​

アダプターとdefineMicroAppは、マイクロアプリがマウント、アンマウント、または失敗したときにページに通知します。DOMイベントをリッスンする、whenMounted(name)を待機する、isMounted(name)で確認する、のいずれかの方法を使えます。DOMをポーリングしたり、__vue_app__のようなフレームワークの内部を読み取ったりする代わりに、アイランドがインタラクティブになった時点でアニメーションを開始する、スケルトンを非表示にする、エラーを報告する、といった用途に使ってください。

イベント ​

DOMイベントディスパッチされるタイミング
tuvix:mountedアプリがレンダリングまたはハイドレートされ、そのフレームワークのマウントコールバックが実行された後。
tuvix:unmountedアプリが破棄され、コンテナが空になった後。
tuvix:errormountまたはunmountがエラーをスローしたとき、またはReactアプリがマウント後のレンダリング中にエラーをスローしたとき。

各イベントは、アプリのコンテナ上でディスパッチされるCustomEvent<LifecycleDetail>です。バブリングし、composedであるため、Shadow Rootの内部からでもdocument上のリスナーが受け取ります。コンテナがDOMに存在しなくなった場合(モジュールがunmountedを通知する前にReactがアイランドを削除した場合)、イベントは代わりにdocument上でディスパッチされ、detail.containerは引き続きその要素を指します。

ts
import type { LifecycleDetail } from '@tuvix.js/loader';

const detail: LifecycleDetail = {
  name: 'footer', // the app name
  container: document.body, // the element the app rendered into
  hydrated: true, // true when the adapter adopted server markup
  error: undefined, // the thrown value, on 'error' only
};

onLifecycleでリッスンする ​

ts
import { onLifecycle } from 'tuvix.js';

const stopMounted = onLifecycle('mounted', ({ name, hydrated }) => {
  console.log(`${name} is interactive (hydrated: ${hydrated})`);
});

const stopErrors = onLifecycle('error', ({ name, error }) => {
  console.error(`${name} failed`, error);
});

// later
stopMounted();
stopErrors();

onLifecycle(type, handler, target = document)は、リスナーを削除する関数を返します。1つの領域だけを対象にするには、targetとしてコンテナを渡します。通常のaddEventListener('tuvix:mounted', ...)も使えます。

whenMountedで待機する ​

ts
import { whenMounted } from 'tuvix.js';

const controller = new AbortController();
setTimeout(() => controller.abort(new Error('footer did not mount in 5 s')), 5000);

const { container } = await whenMounted('footer', { signal: controller.signal });
container.classList.add('is-ready');
  • アプリがすでにマウントされている場合は即座に解決するため、コードの実行とマウントの順序は問題になりません。
  • マウントが失敗してもリジェクトしません。タイムアウト付きのsignalを渡すか、tuvix:errorもリッスンしてください。
  • シグナルが中断されると、Promiseはシグナルの理由でリジェクトします。

isMountedで確認する ​

ts
import { isMounted } from 'tuvix.js';

if (isMounted('footer')) {
  console.log('footer is on the page');
}

フレームワークの検出を置き換える ​

ライフサイクルイベントがない場合、ホストはフレームワークのプライベートプロパティをポーリングするなどして、アイランドの準備ができたタイミングを推測する必要があります:

ts
// Breaks on Svelte, Angular and any Vue release that renames the property.
const timer = setInterval(() => {
  if ((container as any).__vue_app__) {
    clearInterval(timer);
    startReveal(container);
  }
}, 50);

ライフサイクルイベントを使えば、1つのリスナーですべてのアダプターに対応できます:

ts
import { onLifecycle } from 'tuvix.js';

declare function startReveal(container: HTMLElement): void;

onLifecycle('mounted', ({ name, container }) => {
  if (name === 'footer') startReveal(container);
});

イベントをディスパッチするもの ​

ソースディスパッチするイベント
createReactMicroApp、createVueMicroApp、createSvelteMicroApp、createAngularMicroApp(およびSsrバリアント)3つすべて
TuvixReactApp3つすべて
TuvixIsland自身はディスパッチしません。ホストするモジュールがディスパッチします
defineMicroApp3つすべて。ssr: trueの場合、mountの前にコンテナがマークアップを保持していればhydratedはtrueです
通常の{ mount, unmount }オブジェクトなし(emitLifecycleを呼び出さない限り)
オーケストレーターなし。代わりにイベントバス上でtuvix:app:*イベントを発行します

defineMicroAppは、mountが解決した後にtuvix:mountedを、unmountが解決した後にtuvix:unmountedをディスパッチし、どちらかがエラーをスローしたときはtuvix:errorをディスパッチしてから再スローします。mountがまだ実行中のコンテナに対するunmountは、その完了を待ちます:

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

export default defineMicroApp({
  name: 'clock',
  ssr: true, // the server rendered the time into the container
  mount({ container }) {
    container.textContent = new Date().toISOString();
  },
  unmount({ container }) {
    container.replaceChildren();
  },
});

通常のオブジェクトとして書かれたモジュールは、イベントを自分でディスパッチできます:

ts
import { emitLifecycle, type MicroAppModule } from '@tuvix.js/loader';

export const clock: MicroAppModule = {
  mount({ container }) {
    container.textContent = new Date().toISOString();
    emitLifecycle('mounted', { name: 'clock', container, hydrated: false });
  },
  unmount({ container }) {
    container.replaceChildren();
    emitLifecycle('unmounted', { name: 'clock', container, hydrated: false });
  },
};

エッジケース ​

  • 1つのモジュールを複数のコンテナにマウントできます。isMounted(name)はいずれかがマウントされている間trueのままで、whenMounted(name)はまだマウントされているうち最も早いコンテナで解決します。それらを区別する必要がある場合は、配置ごとに別の名前を付けてください。
  • 切り離されたコンテナのtuvix:unmountedイベントは、documentのリスナーに1回届きます。targetにコンテナを指定したonLifecycleは、イベントがその要素を通過しなくなるため、これを受け取りません。
  • 状態はwindow.__TUVIX_RUNTIME__に保持されるため、別々にバンドルされた@tuvix.js/loaderのコピー間で共有されます。
  • サーバー上にはDOMもイベントもありません。サーバーでwhenMountedを呼び出さないでください。解決されることはありません。

できないこと ​

  • 読み込みの進捗は通知しません。bootstrapping、mountingなどはオーケストレーターのonStatusChangeで扱います。
  • 再試行や復旧は行いません。オーケストレーターのfallback、またはTuvixIslandとTuvixReactAppのfallbackを使ってください。