Installazione
npm install @tuvix.js/svelte svelte
# or pnpm add @tuvix.js/svelte svelteIl range della peer dependency copre Svelte 3, 4 e 5. Il pacchetto ha due entry: @tuvix.js/svelte per il browser e @tuvix.js/svelte/server per il rendering lato server.
Quando usarlo
Usa questo pacchetto quando un componente Svelte deve girare come micro app Tuvix.js: l'orchestratore lo monta in un container su una route, gli passa le props, le aggiorna e lo smonta quando la route cambia. Copre anche le island: esegui il rendering del componente sul server, inserisci l'HTML nella pagina e il client lo idrata.
Non ti serve per una pagina che Svelte gestisce interamente (SvelteKit). In quel caso Svelte si monta da solo e non c'è nessun orchestratore con cui comunicare.
Avvio rapido
Un componente Svelte 5 con props:
<!-- Profile.svelte -->
<script lang="ts">
let { name, theme = 'light' }: { name: string; theme?: string } = $props();
</script>
<p class={theme}>Hello {name}</p>L'entry della micro app lo avvolge. Se passi SvelteMap, gli aggiornamenti delle props rieseguono il rendering del componente sul posto:
// profile-app.ts
import { SvelteMap } from 'svelte/reactivity';
import { createSvelteMicroApp } from '@tuvix.js/svelte';
import Profile from './Profile.svelte';
export default createSvelteMicroApp({ name: 'profile', App: Profile, SvelteMap });La shell registra l'app e avvia l'orchestratore:
// shell.ts
import { createOrchestrator } from '@tuvix.js/core';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'profile',
entry: () => import('./profile-app'),
container: '#profile',
activeWhen: '/profile',
props: { name: 'Ada' },
});
await orchestrator.start();
// Later: the component re-renders in place, no remount.
await orchestrator.updateAppProps('profile', { name: 'Grace', theme: 'dark' });Il bundle client deve risolvere svelte nella sua build per browser. @sveltejs/vite-plugin-svelte lo fa per te. Se la tua configurazione scarta la export condition browser, mount() di Svelte 5 lancia lifecycle_function_unavailable e onMount non viene mai eseguito.
API
createSvelteMicroApp
function createSvelteMicroApp(config: SvelteMicroAppConfig): MicroAppModule;| Opzione | Tipo | Default | Significato |
|---|---|---|---|
name | string | obbligatorio | Nome dell'app. L'adapter registra il modulo con questo nome tramite registerModule di @tuvix.js/loader. |
App | unknown | obbligatorio | Componente radice: un componente Svelte 5 compilato (una funzione) o una classe di componente Svelte 3/4. |
bootstrap | () => void | Promise<void> | nessuno | Setup eseguito una sola volta. Il bootstrap() del modulo lo attende. |
ssr | boolean | false | Idrata il markup del server quando il container contiene elementi o testo non composto solo da spazi. Altrimenti esegue un nuovo rendering. |
SvelteMap | classe SvelteMap da svelte/reactivity | nessuno | Solo Svelte 5. Con questa opzione, update() riesegue il rendering sul posto; senza, un update() su Svelte 5 rimonta il componente. Svelte 3/4 la ignorano. |
Al primo mount l'adapter importa svelte e controlla se esiste un export mount. Mette in cache il risultato per la pagina.
- Svelte 5 (
mountesiste): esegue il rendering conmount(), idrata conhydrate(), smonta conunmount(). Il funzionamento diupdate()dipende daSvelteMap:- Con
SvelteMap, il componente riceve un oggetto props basato su unaSvelteMap.update()scrive i nuovi valori in quella map, quindi le parti del template che leggono una prop modificata rieseguono il rendering e il componente mantiene i suoi nodi DOM e il suo stato. Funziona per i componenti con$props()e per i componenti legacy conexport letcompilati da Svelte 5. - Senza,
update()smonta l'istanza, svuota il container, monta una nuova istanza con le nuove props e invia di nuovotuvix:mountedconhydrated: false. Nodi DOM, focus e stato del componente vanno persi.
- Con
- Svelte 3/4 (nessun
mount):new App({ target, props, hydrate }),$set()e$destroy().
Il modulo restituito lavora per container. Ogni container riceve la propria istanza, quindi un modulo può eseguire il rendering in tre container contemporaneamente.
mount({ container, props })svuota il container (a meno che non esegua la hydration), esegue il rendering, lancia le callbackonMounte gli effect, poi inviatuvix:mountedsul container conhydrated: true | false. In caso di errore inviatuvix:errore rifiuta con l'errore originale.update({ props, container? })aggiorna l'istanza incontainer, oppure tutte le istanze se ometticontainer. Ogni aggiornamento sostituisce l'intero set di props: una chiave assente nelle nuove props torna al default del componente su Svelte 5 e diventaundefinedsu Svelte 3/4.unmount({ container })distrugge l'istanza, svuota il container e inviatuvix:unmounted.
import { SvelteMap } from 'svelte/reactivity';
import { createSvelteMicroApp } from '@tuvix.js/svelte';
import { onLifecycle } from '@tuvix.js/loader';
import Cart from './Cart.svelte';
const cart = createSvelteMicroApp({ name: 'cart', App: Cart, SvelteMap });
onLifecycle('mounted', ({ name, hydrated }) => {
console.log(`${name} mounted`, { hydrated });
});
const header = document.querySelector<HTMLElement>('#cart-header')!;
const sidebar = document.querySelector<HTMLElement>('#cart-sidebar')!;
await cart.mount({ container: header, props: { compact: true } });
await cart.mount({ container: sidebar, props: { compact: false } });
// Updates the sidebar instance only.
await cart.update?.({ props: { compact: true }, container: sidebar });createSsrSvelteMicroApp
function createSsrSvelteMicroApp(config: Omit<SvelteMicroAppConfig, 'ssr'>): MicroAppModule;Equivale a createSvelteMicroApp({ ...config, ssr: true }). Usalo sul client per le island di cui il server ha eseguito il rendering con renderSvelteToString o renderSvelte.
- Svelte 5:
hydrate(App, { target, props }). Il markup deve provenire dasvelte/server, perché la hydration legge i commenti marcatori (<!--[-->) che scrive. - Svelte 3/4:
new App({ target, props, hydrate: true }). Compila il componente conhydratable: true.
La hydration mantiene i nodi DOM del server e vi collega gli event handler. Un container che contiene solo spazi o commenti riceve un nuovo rendering.
import { createSsrSvelteMicroApp } from '@tuvix.js/svelte';
import Footer from './Footer.svelte';
export default createSsrSvelteMicroApp({ name: 'footer', App: Footer });renderSvelteToString
// import from '@tuvix.js/svelte/server'
function renderSvelteToString(
App: unknown,
props?: Record<string, unknown>,
options?: RenderSvelteOptions
): Promise<string>;| Opzione | Tipo | Default | Significato |
|---|---|---|---|
render | SvelteServerRender | nessuno | render da svelte/server. Senza questa opzione l'adapter importa svelte/server a runtime. |
Esegue il rendering del componente sul server e restituisce i suoi tag <style> seguiti dal suo markup. Inserisci la stringa nel container dell'island e montala con createSsrSvelteMicroApp. La stringa esclude il contenuto di <svelte:head>; usa renderSvelte quando ti serve.
La funzione sceglie la versione di Svelte a runtime. Se passi render, segue sempre il percorso Svelte 5 e chiama render(App, { props }). Senza, l'adapter importa svelte: se quel modulo esporta mount, carica svelte/server ed esegue il rendering con Svelte 5. Altrimenti chiama App.render(props) (Svelte 3/4, compilato con generate: 'ssr') e avvolge css.code in un tag <style>. L'adapter non decide mai controllando App.render, perché i componenti Svelte 5 compilati con dev: true (vite-plugin-svelte in modalità serve) contengono uno stub App.render che lancia un errore.
Chiamala da una funzione server, non da codice che gira anche nel browser. Nel browser restituisce ''.
import { render } from 'svelte/server';
import { renderSvelteToString } from '@tuvix.js/svelte/server';
import Footer from './Footer.svelte';
export async function renderFooterIsland(): Promise<string> {
const html = await renderSvelteToString(Footer, { year: 2026 }, { render });
return `<footer id="footer" data-tuvix-app="footer">${html}</footer>`;
}renderSvelte
// import from '@tuvix.js/svelte/server'
function renderSvelte(
App: unknown,
props?: Record<string, unknown>,
options?: RenderSvelteOptions
): Promise<SvelteRenderResult>;Accetta gli stessi argomenti e le stesse opzioni di renderSvelteToString e restituisce le parti separate:
| Campo | Significato |
|---|---|
html | Markup del componente. L'output di Svelte 5 contiene i commenti per la hydration. |
head | Contenuto di <svelte:head> senza i tag <style> del componente. Inseriscilo nell'<head> del documento. |
css | CSS del componente come tag <style>, oppure '' quando il compilatore non ne ha emesso. |
Nel browser tutti e tre i campi valgono ''.
import { render } from 'svelte/server';
import { renderSvelte } from '@tuvix.js/svelte/server';
import Article from './Article.svelte';
export async function renderArticlePage(slug: string): Promise<string> {
const { html, head, css } = await renderSvelte(Article, { slug }, { render });
return `<!doctype html>
<html>
<head>${head}${css}</head>
<body>
<main id="article" data-tuvix-app="article">${html}</main>
<script type="module" src="/client.js"></script>
</body>
</html>`;
}Tipi
| Tipo | Entry | Significato |
|---|---|---|
SvelteMicroAppConfig | @tuvix.js/svelte | Opzioni di createSvelteMicroApp. |
SvelteProps | @tuvix.js/svelte | Record<string, unknown>: le props che l'orchestratore passa al componente. |
RenderSvelteOptions | @tuvix.js/svelte/server | { render?: SvelteServerRender }. |
SvelteServerRender | @tuvix.js/svelte/server | Forma di render di Svelte 5 da svelte/server. |
SvelteRenderResult | @tuvix.js/svelte/server | { html, head, css }, restituito da renderSvelte. |
Rendering lato server
Un'island passa per tre fasi: rendering sul server, invio dell'HTML dentro il container, hydration sul client.
- Chiama
renderSvelteToString(orenderSvelte) in una funzione server e inserisci il risultato nell'elemento container. - Carica l'entry client, che esporta
createSsrSvelteMicroApp({ name, App }). - L'orchestratore monta l'app nello stesso container. L'adapter trova il markup e lo idrata.
CSS. Svelte 5 inserisce il CSS del componente nell'output del rendering solo se compili con css: 'injected'. Con il default css: 'external', il bundler scrive il CSS in un file e la stringa non contiene tag <style>. In Vite:
// vite.config.ts
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte({ compilerOptions: { css: 'injected' } })],
});Se mantieni css: 'external', collega il foglio di stile generato dall'head della pagina. @tuvix.js/vite indica al tuo server quali fogli di stile servono all'entry di un'island.
Una sola copia di Svelte. Passa render da svelte/server quando il bundle server include Svelte (Nitro, Vinxi, una build SSR di Vite con noExternal). Così il componente e il renderer condividono una sola copia di Svelte. Senza questa opzione, l'adapter importa svelte/server a runtime, e questo può caricare una seconda copia da node_modules. Il renderer server di Svelte usa controlli instanceof e fallisce quando il componente e il renderer provengono da copie diverse.
Casi limite
mountsu un container che contiene già un'istanza dello stesso modulo smonta prima la vecchia istanza.unmountchiamato mentremountsta ancora caricandosvelteattende la fine del mount, poi distrugge l'istanza. Non resta nessun componente orfano.unmountsu un container che questo modulo non ha mai montato, o il cui mount è fallito, non fa nulla: il container mantiene i suoi figli e non viene inviato nessun evento.updatechiamato mentre un mount è in corso lo attende, poi applica le props. Un update per un mount fallito non fa nulla.- Su Svelte 3/4, una prop assente nelle nuove props diventa
undefined. Il default diexport let name = 'x'non viene ripristinato. Su Svelte 5 si applica il default, con o senzaSvelteMap. - L'adapter non importa
svelte/reactivityda solo. Svelte 3/4 non hanno questo modulo e un bundler non riuscirebbe a fare la build di un'app Svelte 3/4 che lo referenzia, quindi l'import sta nella tua entry Svelte 5. - Un remount di Svelte 5 (senza
SvelteMap) invia di nuovotuvix:mountedma nessuntuvix:unmountednel mezzo. Se il nuovo rendering lancia un errore,update()rifiuta con quell'errore e la vecchia istanza è già stata rimossa. - Senza
ssr,mountsvuota il container prima del rendering, quindi i placeholder di caricamento scompaiono. - Con
ssr, l'adapter esegue la hydration quando il container ha un elemento o testo non composto solo da spazi. Commenti e spazi da soli causano un nuovo rendering. - L'adapter rileva la versione di Svelte una volta per pagina. Se
import('svelte')fallisce, il mount successivo riprova. - Un errore di import o di rendering di Svelte 5 sul server ti arriva come promise rifiutata. L'adapter non ricade su
App.render. renderdi Svelte 5 può restituire una promise (rendering lato server asincrono). Entrambe le funzioni server la attendono.
Cosa non fa
- Non compila i file
.svelte. Lo fa il tuo bundler con@sveltejs/vite-plugin-svelteosvelte/compiler. - Non controlla quale build di Svelte ha scelto il tuo bundler. Un bundle client che risolve
sveltenella build server fallisce al mount. - Passa solo props. Slot, snippet ed eventi dei componenti non hanno un canale dall'host. Usa
@tuvix.js/event-busper comunicare tra app. - Non scrive il contenuto di
<svelte:head>nel documento.renderSveltelo restituisce; il tuo server lo inserisce in<head>. - Non collega il CSS compilato con
css: 'external'. Vedi@tuvix.js/vite. - Non attende i dati sul server. Caricali prima e passali come props.
Migrazione dalla 0.4
- Svelte 5 funziona. Nella 0.4 l'adapter chiamava
new App()su ogni componente, quindi i componenti Svelte 5 fallivano ecreateSsrSvelteMicroAppeseguiva il rendering di una seconda copia invece di idratare. Il mount non richiede modifiche al codice. Per aggiornare le props sul posto, passaSvelteMapdasvelte/reactivitynella configurazione; senza, ogni update su Svelte 5 rimonta il componente. - Il container viene svuotato al mount. Senza
ssr, Svelte 3/4 aggiungeva il componente dopo l'eventuale markup già presente nel container. Ora l'adapter svuota prima il container. Se ti affidavi al markup che restava accanto al componente, spostalo fuori dal container. - Nessun fallback silenzioso sul server.
renderSvelteToStringricadeva suApp.renderquandosvelte/servernon si caricava. Ora segue il percorso Svelte 5 se passirendero sesvelteesportamount, chiamaApp.rendersolo su Svelte 3/4 e rifiuta in caso di errore di import o di rendering di Svelte 5. Le build di sviluppo di Svelte 5 nell'SSR di sviluppo di Vite seguono il percorso Svelte 5 anche se contengono uno stubApp.render. unmountsu un container sconosciuto non fa nulla.unmount({ container })su un container che il modulo non ha mai montato lo svuotava. Ora lascia il container intatto, come gli adapter per React, Vue e Angular.- Passa
render. Aggiungi{ render }dasvelte/servercome terzo argomento quando il bundle server include Svelte:
import { render } from 'svelte/server';
import { renderSvelteToString } from '@tuvix.js/svelte/server';
import Footer from './Footer.svelte';
// 0.4: await renderSvelteToString(Footer, { year: 2026 });
const html = await renderSvelteToString(Footer, { year: 2026 }, { render });Vedi la guida a Svelte per un'app completa.