Kurulum
npm install @tuvix.js/react react react-dom
# or pnpm add @tuvix.js/react react react-domPaket React 18 veya daha yeni bir sürüm ister (react ve react-dom için peer aralığı >=18.0.0). @tuvix.js/loader ve @tuvix.js/event-bus paketlerine bağımlıdır.
Ne zaman kullanılır
Paket iki yönü kapsar:
- Mikro uygulama olarak React.
createReactMicroAppvecreateSsrReactMicroApp, bir React bileşenini orchestrator ile loader'ın beklediği{ bootstrap, mount, unmount, update }modülüne sarar. Bir Tuvix shell'inin yüklediği mikro uygulamalardan biri React uygulamasıysa bunları kullanın. - Host olarak React.
TuvixIslandherhangi bir mikro uygulama modülünü React sayfasının içinde mount eder ve yeniden render'larda DOM'unu bozmadan korur.TuvixReactAppbir React mikro uygulamasını host ağacının içinde, satır içinde render eder. Shell'iniz TanStack Start, Next.js veya Remix gibi bir React SSR framework'üyse bunları kullanın.
Shell'iniz React içermeyen düz HTML ise yalnızca ilk ikiliye ve @tuvix.js/core paketine ihtiyacınız var. Vue, Svelte veya Angular mikro uygulaması için modülü @tuvix.js/vue, @tuvix.js/svelte ya da @tuvix.js/angular ile oluşturun; TuvixIsland bunların hepsini barındırabilir.
Hızlı başlangıç
Mikro uygulama modülünü oluşturun:
// src/dashboard.tsx
import { createReactMicroApp } from '@tuvix.js/react';
function Dashboard({ userId }: { userId: string }) {
return <h1>Dashboard for {userId}</h1>;
}
export default createReactMicroApp({ name: 'dashboard', App: Dashboard });Modülü shell'e kaydedin. Orchestrator, route eşleştiğinde mount fonksiyonunu, props'u değiştirdiğinizde update fonksiyonunu çağırır:
// src/shell.ts
import { createOrchestrator } from '@tuvix.js/core';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'dashboard',
entry: () => import('./dashboard'),
container: '#app',
activeWhen: '/dashboard/*',
props: { userId: 'u_42' },
});
await orchestrator.start();
// Re-renders <Dashboard userId="u_43" />; component state survives.
await orchestrator.updateAppProps('dashboard', { userId: 'u_43' });API
createReactMicroApp
function createReactMicroApp<P extends object = Record<string, unknown>>(
config: ReactMicroAppConfig<P>
): MicroAppModule;| Seçenek | Tip | Varsayılan | Anlamı |
|---|---|---|---|
name | string | zorunlu | Modülün @tuvix.js/loader paketindeki registerModule ile kaydedildiği ad. Lifecycle olayları bu adı taşır. |
App | ComponentType<P> | zorunlu | Kök bileşen. Mount props'unu kendi props'u olarak alır; bu yüzden { page: 'privacy' | 'terms' } gibi zorunlu props'u olan bir bileşen tip denetiminden geçer. |
bootstrap | () => void | Promise<void> | yok | Tek seferlik kurulum. Modülün bootstrap() fonksiyonu bunu bekler. |
ssr | boolean | false | Container eleman ya da boşluk dışı metin içeriyorsa sunucu markup'ını hydrateRoot ile hydrate eder. Yalnızca boşluk veya yorum içeren container sıfırdan bir createRoot render'ı alır. |
Dönen modülün dört metodu vardır:
mount({ container, props })o container için bir React root oluşturur. React ilk render'ı commit ettikten sonra resolve olur, ardından container üzerindehydrated: trueveyafalseiletuvix:mountedolayını tetikler.Appilk render'da suspend olursamount, içerik commit edilene kadar bekler.update({ props, container })Appbileşenini o container'dapropsile yeniden render eder.containerverilmezse modülün mount ettiği her container'ı yeniden render eder. Ağacın yapısı değişmediği için React bileşen state'ini korur.unmount({ container })root'u unmount eder, container'ıreplaceChildren()ile boşaltır vetuvix:unmountedolayını tetikler.bootstrap()config.bootstrapfonksiyonunu çalıştırır.
Her container kendi root'unu alır, böylece tek bir modül aynı anda birkaç yere mount edilebilir:
import { createReactMicroApp } from '@tuvix.js/react';
function Badge({ label }: { label: string }) {
return <span className="badge">{label}</span>;
}
const badge = createReactMicroApp({ name: 'badge', App: Badge });
const left = document.createElement('div');
const right = document.createElement('div');
document.body.append(left, right);
await badge.mount({ container: left, props: { label: 'New' } });
await badge.mount({ container: right, props: { label: 'Sale' } });
// Only the right badge changes.
await badge.update?.({ props: { label: 'Sold out' }, container: right });
await badge.unmount({ container: left });Modül react-dom/client paketini mount içinde lazy olarak import eder. Bir sunucu bundle'ı modülü import edebilir (örneğin App bileşenini TuvixReactApp ile paylaşmak için) ve istemci renderer'ını içeri çekmez.
createSsrReactMicroApp
function createSsrReactMicroApp<P extends object = Record<string, unknown>>(
config: Omit<ReactMicroAppConfig<P>, 'ssr'>
): MicroAppModule;createReactMicroApp({ ...config, ssr: true }) ile aynıdır. Seçenekler: yukarıdaki tablodaki name, App, bootstrap.
Container, sunucunun aynı bileşen için render ettiği HTML'i zaten içeriyorsa bunu istemcide kullanın. Render ettiği ağaç TuvixReactApp ağacıyla aynıdır (error boundary artı <Suspense>), bu yüzden TuvixReactApp markup'ını uyumsuzluk olmadan hydrate eder:
import { createSsrReactMicroApp } from '@tuvix.js/react';
function LegalPage({ page }: { page: 'privacy' | 'terms' }) {
return <article>{page === 'privacy' ? 'Privacy policy' : 'Terms of use'}</article>;
}
export default createSsrReactMicroApp({ name: 'legal-app', App: LegalPage });TuvixIsland
function TuvixIsland(props: TuvixIslandProps): ReactElement;| Prop | Tip | Varsayılan | Anlamı |
|---|---|---|---|
name | string | zorunlu | Mikro uygulama adı. Island bunu data-tuvix-app özniteliğine yazar ve app.mount fonksiyonuna geçirir. |
app | MicroAppModule | zorunlu | Mount edilecek modül: createVueMicroApp, createSvelteMicroApp, createAngularMicroApp, createReactMicroApp sonuçları, bunların createSsr* varyantları ya da elle yazılmış bir { mount, unmount, update? } nesnesi. |
props | Record<string, unknown> | {} | Modülün props'u. Island her render'da bunları sığ (shallow) karşılaştırır. |
ssrHtml | string | yok | Island'ın sunucuda render edilmiş HTML'i, örneğin renderVueToString çıktısı. Yalnızca sunucuda verin. |
fallback | ReactNode | null | mount veya update reject olduğunda island'ın yerine render edilir. |
as | keyof JSX.IntrinsicElements | 'div' | Sarmalayıcı elemanın etiketi. |
className | string | yok | Sarmalayıcı elemanın class adı. |
TuvixIsland, React olmayan bir mikro uygulamayı (veya ayrı bundle edilmiş bir React uygulamasını) bir React sayfasının içinde barındırır. <div dangerouslySetInnerHTML={{ __html: html }} /> render eden elle yazılmış bir köprü, parent ilk kez yeniden render olduğunda bozulur: React yeni bir { __html } nesnesi görür, innerHTML değerini yeniden atar ve mikro uygulamanın eklediği DOM'u ve listener'ları yok eder. TuvixIsland bunu önler:
- Sunucu render'ı.
ssrHtmliçeriğini inner HTML olarak kullanıp<div data-tuvix-app={name} data-tuvix-island={id}>render eder.idReact'inuseIdhook'undan gelir, böylece sunucu ve istemci aynı değeri hesaplar. - Hydration. İstemcideki ilk render'da kendi elemanını sayfada
data-tuvix-islandüzerinden bulur ve inner HTML'i DOM'dan geri okur.ssrHtmliçeriğini istemciye ikinci kez göndermezsiniz ve istemci render'ı sunucu render'ıyla eşleşir. - Kararlı DOM. Island,
{ __html }nesnesiniuseStateiçinde bir kez oluşturur. Sonraki render'lar aynı nesneyi kullanır, bu yüzden React island'ın çocuklarını hiçbir zaman yeniden yazmaz. Parent yeniden render olduğunda mikro uygulamanın node'ları ve listener'ları yerinde kalır. - Mount. İlk commit'ten sonra island
app.mount({ container, props, name })çağrısını yapar.ssr: trueile oluşturulmuş bir modül (createSsrVueMicroAppve benzerleri) bu noktada sunucu DOM'unu sahiplenir. SSR'sız bir modül container'a sıfırdan render eder. - Props.
propssığ karşılaştırmada değişirse (farklı anahtarlar ya daObject.iskontrolünden geçmeyen bir değer) islandapp.update({ props, container })çağrısını yapar.updatefonksiyonu olmayan bir modül önceunmountedilir, container boşaltılır ve yenidenmountedilir. Aynı değerlere sahip satır içi bir nesne literal'i update tetiklemez. - Kimlik. Yeni bir
nameveyaappeski modülü unmount eder ve yenisini mount eder. - Kaldırma. Island ağaçtan çıktığında
app.unmount({ container })çağrısını yapar ve container'ı boşaltır. - Sıralama. Mount, update ve unmount çağrıları art arda çalışır.
mountbeklemedeyken gelen bir unmount, onun bitmesini bekler. - Hatalar.
mountveyaupdatereject olursa island, sarmalayıcının yerinefallbackrender eder. Adaptörlertuvix:errorolayını kendileri tetikler; island ek bir olay eklemez. Daha sonranameveyaappdeğişirse island yeniden dener.
React layout'u içinde Vue footer'ı olan eksiksiz bir sunucu ve istemci kurulumu:
// src/Layout.tsx (shared by server and client)
import { TuvixIsland } from '@tuvix.js/react';
import { createSsrVueMicroApp } from '@tuvix.js/vue';
import Footer from './Footer.vue';
const footer = createSsrVueMicroApp({ name: 'footer', App: Footer });
export function Layout({ year, footerHtml }: { year: number; footerHtml?: string }) {
return (
<main>
<h1>Home</h1>
<TuvixIsland
name="footer"
app={footer}
props={{ year }}
ssrHtml={footerHtml}
fallback={<footer>© {year}</footer>}
as="section"
className="site-footer"
/>
</main>
);
}// src/entry-server.tsx (runs on the server only)
import { renderToString } from 'react-dom/server';
import { renderVueToString } from '@tuvix.js/vue/server';
import { Layout } from './Layout';
import Footer from './Footer.vue';
export async function renderPage(): Promise<string> {
const year = 2026;
const footerHtml = await renderVueToString(Footer, { year });
const body = renderToString(<Layout year={year} footerHtml={footerHtml} />);
return `<!doctype html><html><body><div id="root">${body}</div><script type="module" src="/src/entry-client.tsx"></script></body></html>`;
}// src/entry-client.tsx
import { hydrateRoot } from 'react-dom/client';
import { Layout } from './Layout';
// No footerHtml here: the island reads the footer HTML from the page.
hydrateRoot(document.getElementById('root')!, <Layout year={2026} />);Sunucu HTML'i tarayıcıya herhangi bir JavaScript'ten önce ulaşır. Footer'ın stil dosyalarını <head> içine yazdığınızda footer ilk boyamada görünür ve stilli olur. Bu <link> etiketlerini @tuvix.js/vite üretir.
TuvixReactApp
function TuvixReactApp<P extends object>(props: TuvixReactAppProps<P>): ReactElement;| Prop | Tip | Varsayılan | Anlamı |
|---|---|---|---|
name | string | zorunlu | Mikro uygulama adı, data-tuvix-app özniteliğine yazılır. createSsrReactMicroApp fonksiyonuna verdiğiniz name ile aynı olmalı. |
App | ComponentType<P> | zorunlu | Render edilecek bileşen. |
fallback | ReactNode | null | App suspend olduğunda ve hata fırlattıktan sonra gösterilir. |
| diğer props | P | P gerektiriyorsa zorunlu | App bileşenine geçirilir. |
TuvixReactApp bir React mikro uygulamasını host'un React ağacının parçası olarak satır içinde render eder; böylece HTML'ini SSR framework'ünüz üretir. <div data-tuvix-app={name}> çıktısı verir ve App bileşenini bir error boundary ile bir <Suspense fallback> içine sarar:
- Sunucu hataları yerel kalır. Sunucu render'ı sırasında
Appiçinde fırlatılan bir hata, React'in yalnızca bu uygulama içinfallbackrender etmesine yol açar. Sayfanın geri kalanı render edilir ve ReactAppbileşenini istemcide yeniden dener. - İstemci hataları yerel kalır. Error boundary istemcideki render hatalarını yakalar,
fallbackgösterir ve sarmalayıcı üzerindetuvix:errorolayını tetikler. - Lifecycle olayları. İlk commit'ten sonra
tuvix:mountedolayını tetikler. Sarmalayıcı sunucu HTML'inden geldiysehydrateddeğeritrueolur. Bileşen ağaçtan çıktığında, React sarmalayıcıyı ayırmadan öncetuvix:unmountedolayını tetikler; böylecedocumentüzerindeki bir listener olayı alır. Gösterilmekte olan bir<Suspense>sınırı yeniden suspend olursa uygulama önceunmounted, içerik geri geldiğinde demountedbildirir.
import { TuvixReactApp } from '@tuvix.js/react';
function LegalPage({ page }: { page: 'privacy' | 'terms' }) {
return <article>{page === 'privacy' ? 'Privacy policy' : 'Terms of use'}</article>;
}
export function PrivacyRoute() {
return (
<TuvixReactApp
name="legal-app"
App={LegalPage}
page="privacy"
fallback={<p>This section failed to load.</p>}
/>
);
}Vue, Svelte, Angular veya vanilla bir mikro uygulamayı barındırmak için TuvixIsland kullanın.
useTuvixBus
function useTuvixBus<T = unknown>(
bus: IEventBus,
event: string,
handler: EventHandler<T>
): void;Bir effect içinde handler fonksiyonunu bus üzerindeki event olayına abone eder ve unmount sırasında aboneliği kaldırır. Hook en güncel handler fonksiyonunu bir ref'te tutar, bu yüzden güncel state okuyan bir handler her render'da yeniden abone olmaz. Yeni bir bus veya event aboneliği yeniler. Abonelik yalnızca istemcide vardır; effect'ler sunucu render'ı sırasında çalışmaz.
Adaptörler orchestrator'ın bus'ını App bileşenine geçirmez. Orchestrator, siz eventBus seçeneklerini vermediyseniz getGlobalBus() kullanır; bus'ı oradan okuyun:
import { useState } from 'react';
import { useTuvixBus } from '@tuvix.js/react';
import { getGlobalBus } from '@tuvix.js/event-bus';
export function CartIcon() {
const [count, setCount] = useState(0);
useTuvixBus<{ items: unknown[] }>(getGlobalBus(), 'cart:updated', ({ items }) => {
setCount(items.length);
});
return <span>{count}</span>;
}useTuvixProps
function useTuvixProps<T extends Record<string, unknown>>(
initialProps: T,
bus?: IEventBus,
updateEvent?: string
): T;| Argüman | Tip | Varsayılan | Anlamı |
|---|---|---|---|
initialProps | T | zorunlu | Başlangıç değeri. Hook bunu ilk render'da okur, sonraki değişiklikleri yok sayar. |
bus | IEventBus | yok | Dinlenecek bus. Verilmezse hook initialProps değerini değiştirmeden döndürür. |
updateEvent | string | 'tuvix:props:update' | Hook'un payload'unu mevcut props ile birleştirdiği olay ({ ...prev, ...payload }). |
Ne orchestrator ne de adaptörler tuvix:props:update olayını yayınlar. Bu olayı kendiniz yayınlarsınız. createReactMicroApp ile oluşturulmuş bir modülün orchestrator props'u için bu hook'a ihtiyacı yoktur: update({ props }), App bileşenini yeni props ile yeniden render eder.
import { useTuvixProps } from '@tuvix.js/react';
import { getGlobalBus } from '@tuvix.js/event-bus';
export function ThemeLabel() {
const props = useTuvixProps({ theme: 'light' }, getGlobalBus());
return <p>Theme: {props.theme}</p>;
}
// Elsewhere: getGlobalBus().emit('tuvix:props:update', { theme: 'dark' });Tipler
| Tip | Anlamı |
|---|---|
ReactMicroAppConfig<P> | createReactMicroApp config'i: name, App, bootstrap?, ssr?. |
TuvixIslandProps | TuvixIsland props'u: name, app, props?, ssrHtml?, fallback?, as?, className?. |
TuvixReactAppProps<P> | TuvixReactApp props'u: name, App, fallback? ve App bileşeninin props'u. |
Sınır durumlar
- İlk render hata fırlatır.
mounthatayla reject olur, root'u unmount eder, container'ı boşaltır vetuvix:errorolayını tetikler. 0.4'temountresolve oluyordu ve hata React'in root hata işleyicisine ulaşıyordu. - Sonraki bir render hata fırlatır. Error boundary uygulamayı boşaltır ve
tuvix:errorolayını tetikler. Host sayfa çalışmaya devam eder. Uygulama, siz unmount edip yeniden mount edene kadar boş kalır;updateonu geri getirmez. - Mount sırasında unmount.
mounthâlâreact-dom/clientpaketini yüklerken ya da ilk commit'i beklerken gelen birunmount, onun bitmesini bekler, container'ı boş bırakır vemountedolayını tetiklemez. - Aynı container'a iki kez mount. Modülün zaten tuttuğu bir container üzerindeki ikinci
mount, eski root'u unmount eder vetuvix:unmountedolayını tetikler; ardından yeni props ile sıfırdan render eder vetuvix:mountedolayını tetikler. Bileşen state'i sıfırlanır. Vue, Svelte ve Angular adaptörleri de aynısını yapar. State'i korumak içinupdateçağırın. - Bilinmeyen container. Modülün hiç mount etmediği bir container üzerinde
unmountveupdatehiçbir şey yapmaz. - Props'un tamamı değişir. Modülün
updatefonksiyonuAppbileşenini yalnızca verdiğiniz props ile render eder.orchestrator.updateAppPropsiseupdateçağrısından önce yeni props'u kayıtlı olanların üzerine birleştirir. ssr: trueile yalnızca boşluk içeren container.hasServerMarkupboşluk metnini ve yorumları yok saydığı için modül sıfırdan render eder.- React island'ı kaldırır. React, modül
tuvix:unmountedbildirmeden önce island'ın elemanını ayırır.emitLifecyclede olayı ayrılmış eleman yerinedocumentüzerinde tetikler; böylecedocumentüzerindeki bir listener (veya@tuvix.js/loaderpaketindenonLifecycle) olayı yine alır vedetail.containerkaldırılan elemanı gösterir. Container'ın kendisindeki ya da bir atasındaki listener olayı almaz.isMounted(name), uygulama başka bir container'da mount edili olduğu sürecetruekalır, sonuncusu unmount edildiğindefalseolur. - Geliştirme ortamında
<StrictMode>altındaki island. React mount effect'ini, ardından onun cleanup'ını, sonra effect'i yeniden çalıştırır. Island modül çağrılarını kuyruğa alır ve cleanup'ı zaten çalışmış olan ilk çalıştırmayı atlar; böylece modül bir kez mount edilir ve sunucu DOM'unu sahiplenir. - Başarısız mount sonrası yeni uygulama.
mountreject olduğunda island sunucu HTML'ini atar. Ardından farklı birappveyanameverirseniz yeni modül, başarısız uygulamanın markup'ını sahiplenmek yerine boş bir elemana mount edilir. - Island'da
ssrHtmldeğerini değiştirmek. IslandssrHtmldeğerini yalnızca ilk render'da okur. Yeni içeriğipropsüzerinden geçirin. - Aynı ad iki kez.
registerModulebir ad altında en son oluşturulan modülü tutar.
Ne yapmaz
- Modülleri URL ile yüklemez. Modülü import edin ya da
@tuvix.js/corepaketinin birentryüzerinden yüklemesine izin verin. - Bir mikro uygulamayı string'e render etmez. Sunucuda
TuvixReactAppile birliktereact-dom/serverpaketindenrenderToStringkullanın ya da framework'ün kendi renderer'ını (@tuvix.js/vue/serverpaketindenrenderVueToString, Svelte ve Angular karşılıkları) kullanıp sonucuTuvixIslandbileşeninessrHtmlolarak verin. MountContext.busveyaMountContext.namedeğeriniAppbileşenine geçirmez.@tuvix.js/event-buspaketindengetGlobalBus()kullanın.- Island'ın CSS'ini yüklemez. Sunucuda render edilmiş island'ların stil dosyası linklerini yazmak için
@tuvix.js/vitekullanın. - React 17 ve öncesini desteklemez.
0.4 sürümünden geçiş
TuvixReactApp sunucu HTML'inde Suspense işaretleri var. Sarmalayıcı artık uygulamanın çevresinde <!--$-->…<!--/$--> içerir. @tuvix.js/react paketini sunucuda ve istemcide birlikte yükseltin, aksi halde hydration uyumsuzluk hatasıyla başarısız olur.
İlk render hata fırlatınca mount reject olur. mount çağrısını await edip resolve olmasını bekliyorsanız hatayı yakalayın:
import { createReactMicroApp } from '@tuvix.js/react';
function Broken(): never {
throw new Error('boom');
}
const app = createReactMicroApp({ name: 'broken', App: Broken });
const container = document.createElement('div');
try {
await app.mount({ container });
} catch (error) {
// 0.4: never reached. 0.5: the render error; the container is empty.
container.textContent = 'This section failed to load.';
}Bilinmeyen bir container üzerinde unmount hiçbir şey yapmaz. 0.4'te container'ı temizliyordu. Buna güveniyorsanız container.replaceChildren() çağrısını kendiniz yapın.
Config nesnesi. Bu sayfanın önceki sürümleri createReactMicroApp(App) kullanımını gösteriyordu. Fonksiyon bir config nesnesi alır: createReactMicroApp({ name: 'dashboard', App }).
Daha fazla adım adım anlatım için React rehberine bakın.