マイクロアプリ
マイクロアプリは、mount関数とunmount関数を持つモジュールです。シェルはコンテナ要素とプロパティを渡してmountを呼び出し、アプリはその要素内にレンダリングします。アプリがページから離れるべきときにシェルがunmountを呼び出し、アプリはレンダリングしたものを削除します。
モジュールの形状
import type { MicroAppModule } from '@tuvix.js/loader';
const app: MicroAppModule = {
bootstrap: async () => {},
mount: async ({ container, props, name, bus }) => {
container.textContent = `${name ?? 'app'} mounted`;
void props;
void bus;
},
update: async ({ props, container }) => {
if (container) container.dataset.props = JSON.stringify(props);
},
unmount: async ({ container }) => {
container.replaceChildren();
},
};| フック | 必須 | 呼び出されるタイミング |
|---|---|---|
bootstrap() | いいえ | 最初の読み込み後、最初のmountの前に1回。 |
mount({ container, props, name, bus }) | はい | アプリがアクティブになるたび。 |
update({ props, container }) | いいえ | アプリがマウントされている間にシェルがupdateAppPropsを呼び出したとき。 |
unmount({ container }) | はい | アプリが非アクティブになるたび。 |
各フックはPromiseを返すことができます。オーケストレーターは次のステップに進む前にそれを待ちます。
手書きで作成する
defineMicroAppはフックを検証し、モジュールをnameで登録し、コンテナ上でtuvix:mounted、tuvix:unmounted、tuvix:errorをディスパッチして、モジュールを返します:
import { defineMicroApp } from '@tuvix.js/core';
let off: (() => void) | undefined;
export default defineMicroApp({
name: 'notifications',
mount({ container, props, bus }) {
const list = document.createElement('ul');
container.replaceChildren(list);
off = bus?.on<{ text: string }>('notify', ({ text }) => {
const item = document.createElement('li');
item.textContent = text;
list.append(item);
});
container.dataset.user = String(props?.user ?? '');
},
unmount({ container }) {
off?.();
off = undefined;
container.replaceChildren();
},
});マウントコンテキストのbusを使ってください。これはオーケストレーターのバスで、シェルがeventBusオプションを渡していない限りgetGlobalBus()です。
アダプターを使う
アダプターはコンポーネントからモジュールを構築し、本来なら自分で書く必要がある細かい処理を引き受けます:
- コンテナごとに1つのフレームワークインスタンスを保持するため、1つのモジュールを複数の場所にマウントできます。
updateは再マウントせずに新しいプロパティで再レンダリングします。mountの読み込み中に届いたunmountは、完了を待ってから空のコンテナを残します。- コンテナ上で
tuvix:mounted、tuvix:unmounted、tuvix:errorイベントを発生させます。 ssr: trueを渡すと、サーバーレンダリングされたマークアップをハイドレートします。
import { createReactMicroApp } from '@tuvix.js/react';
function Cart({ items }: { items: number }) {
return <p>{items} items</p>;
}
export default createReactMicroApp({ name: 'cart', App: Cart });モジュールをローダーに公開する
オーケストレーターは、アプリのentryを通じてモジュールを取得します:
import { createOrchestrator } from '@tuvix.js/core';
const orchestrator = createOrchestrator();
// Bundled with the shell, code-split by your bundler.
orchestrator.register({
name: 'cart',
entry: () => import('./apps/cart'),
container: '#cart',
activeWhen: '/shop/*',
});
// Deployed on its own. The script registers itself by name.
orchestrator.register({
name: 'notifications',
entry: { scripts: ['https://cdn.example.com/notifications/main.js'], type: 'module' },
container: '#notifications',
activeWhen: '/',
});URLのエントリーの場合、モジュールはregisterModule(name, module)を呼び出す(アダプターとdefineMicroAppは呼び出します)か、mount関数を持つUMDグローバルを公開する必要があります。ローダー関数は、モジュールまたは{ default: module }に解決されるだけで十分です。
プロパティを渡す
import { createOrchestrator } from '@tuvix.js/core';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'profile',
entry: () => import('./apps/profile'),
container: '#app',
activeWhen: '/profile/*',
props: { userId: '42', theme: 'dark' },
});
await orchestrator.updateAppProps('profile', { theme: 'light' });
// The app now has { userId: '42', theme: 'light' }.- プロパティはシャローマージされます。キーを削除するには、
undefinedを設定します。 - アプリがマウント済みで
updateを持つ場合、オーケストレーターはそれを呼び出し、完了後に解決します。updateがリジェクトされると、アプリはerrorステータスになり、onErrorが呼び出されます。 - アプリがマウントされていない場合、または
updateを持たない場合、オーケストレーターは次回のマウントのためにマージ後のプロパティを保存します。アプリを再マウントすることはありません。
1つのモジュールを複数の場所にマウントする
アダプターはコンテナごとに別々のインスタンスを保持します。TuvixIslandや独自のコードから、同じモジュールで2つのコンテナに対してmountを呼び出せます:
import { createVueMicroApp } from '@tuvix.js/vue';
import Price from './Price.vue';
const price = createVueMicroApp({ name: 'price', App: Price });
const first = document.querySelector<HTMLElement>('#price-1');
const second = document.querySelector<HTMLElement>('#price-2');
if (first && second) {
await price.mount({ container: first, props: { sku: 'a' } });
await price.mount({ container: second, props: { sku: 'b' } });
await price.update?.({ props: { sku: 'c' }, container: second }); // only #price-2
}containerを指定しない場合、updateはそのモジュールのすべてのマウントを更新します。オーケストレーターは常にcontainerを渡します。
ビルドとデプロイ
各アプリをESモジュールバンドルとしてビルドしてtype: 'module'で登録するか、IIFE/UMDバンドルとしてビルドしてそのURLをクラシックスクリプトとして登録します。Viteの場合:
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: { entry: 'src/main.ts', formats: ['es'], fileName: 'main' },
// Leave dependencies bundled unless the shell provides them.
},
});フレームワークのコピーに注意してください。それぞれがReactをバンドルする2つのマイクロアプリは、Reactを2回配信します。各アプリが独自のルートをレンダリングするため、ページは問題なく動作します。1つのコピーを共有するには、import maps、Module Federation(@tuvix.js/module-federation)を使うか、ローダー関数のエントリーを通じてシェルにアプリをバンドルさせます。
マイクロアプリがしてはいけないこと
- コンテナの外に書き込むこと。Tuvix.jsはこれを止めず、そのコンテナに次に入るアプリはそれを想定していません。
unmountの後にリスナー、タイマー、サブスクリプションを残すこと。bootstrapが複数回実行されることを前提にすること。bootstrapは読み込まれたモジュールごとに1回だけ実行されます。