Migrar a 0.5
0.5 corrige errores que cambiaban el comportamiento observable, así que algunas de estas correcciones rompen código de 0.4. Esta página enumera cada cambio incompatible por paquete, con el código que hay que cambiar. Actualiza todos los paquetes tuvix.js y @tuvix.js/* a ^0.5.0 a la vez; no se admite mezclar adaptadores de 0.4 y 0.5 en una misma página.
pnpm up --latest "tuvix.js" "@tuvix.js/*"Los proyectos creados con create-tuvix-app antes de 0.5 dependen de ^0.1.0, que nunca se resuelve a 0.4 ni a 0.5. Edita esos rangos a mano.
Core
El orquestador usa el bus global
0.4 daba a cada orquestador un bus privado, separado de getGlobalBus(). En 0.5, el bus del orquestador es getGlobalBus() salvo que pases opciones en eventBus, y destroy() deja vivo el bus global.
// 0.4: two buses; apps listening on getGlobalBus() missed orchestrator events
const orchestrator = createOrchestrator();
orchestrator.getEventBus() === getGlobalBus(); // false// 0.5
import { createOrchestrator, getGlobalBus } from 'tuvix.js';
const shared = createOrchestrator();
console.log(shared.getEventBus() === getGlobalBus()); // true
const isolated = createOrchestrator({ eventBus: {} }); // private bus, as in 0.4
console.log(isolated.getEventBus() === getGlobalBus()); // falseSi exponías el bus del orquestador en window para que las apps lo encontraran, puedes quitarlo y usar el bus del contexto de montaje o getGlobalBus().
Los cambios de ruta no tocan las apps manuales
0.4 desmontaba en el siguiente cambio de ruta una app que no tenía ni activeWhen ni una ruta del router, y también las apps con mountWhenVisible. En 0.5, las reconciliaciones solo afectan a apps controladas por activeWhen o por una ruta. Si dependías de una navegación para eliminar una app montada manualmente, desmóntala tú mismo:
import { createOrchestrator } from 'tuvix.js';
const orchestrator = createOrchestrator();
await orchestrator.unmountApp('cookie-banner');sandbox: true renderiza en un shadow root
En 0.4, sandbox en register no hacía nada. En 0.5, sandbox: true monta la app dentro de un shadow root y carga ahí los styles de su entrada. El CSS de la página deja de llegar a la app, y los estilos de la entrada de la app dejan de llegar a la página. Quita sandbox: true de las apps que dependen de los estilos de la página. Consulta Sandboxing.
prefetch: 'hover'
0.4 precargaba todas las apps con el primer movimiento del ratón en cualquier parte de la página. 0.5 precarga una app cuando el puntero entra en un enlace del mismo origen cuya ruta la activa, o cuando el foco llega a él. Usa 'idle' o 'immediate' para descargar todas las apps de antemano.
OrchestratorConfig.shared ya no existe
El orquestador nunca leía shared. Elimínalo y comparte dependencias con @tuvix.js/module-federation o con import maps.
Patrones de activeWhen
Las cadenas de activeWhen ahora usan matchPath. Una query string o un hash ya no desmontan una app, los segmentos :param funcionan, y caracteres como . y ( coinciden de forma literal.
// 0.4: `.` was a regex wildcard and matched '/v1x0'
activeWhen: '/v1.0/*'
// 0.4: query strings broke matching, so people wrote functions
activeWhen: (path) => path.split('?')[0].startsWith('/dashboard')import { createOrchestrator } from 'tuvix.js';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'dashboard',
entry: () => import('./apps/dashboard'),
container: '#app',
activeWhen: '/dashboard/*', // also active on /dashboard?tab=1
});Router
La ruta '/' y RouteConfig.exact
Las rutas coinciden con la ruta completa en todas partes. Una ruta '/' ahora solo se activa en /; en 0.4, getActiveApps() la trataba como cualquier ruta mientras que match() la trataba como exacta. RouteConfig.exact ya no existe, porque nunca cambiaba nada.
// 0.4
routes: [{ path: '/', app: 'shell-nav' }, { path: '/settings', app: 'settings', exact: true }]// 0.5
import { createRouter } from 'tuvix.js';
const router = createRouter({
routes: [
{ path: '/*', app: 'shell-nav' }, // every path
{ path: '/settings', app: 'settings' }, // /settings only
],
});
void router;activeWhen: '/' en register sigue coincidiendo con cualquier ruta, porque las cadenas de activeWhen usan coincidencia por prefijo.
push y replace añaden la base
// 0.4 with base: '/app'
router.push('/app/settings'); // you had to add the base yourself// 0.5
import { createRouter } from 'tuvix.js';
const router = createRouter({ base: '/app', routes: [] });
await router.push('/settings'); // URL: /app/settingsPasar /app/settings en 0.5 escribe /app/app/settings. Un guard que cancela una navegación hacia atrás o hacia delante también restaura la URL con la base.
Otros cambios del router
basesolo se elimina en un límite de segmento. Conbase: '/app',/applicationse queda como/application.pathToRegex()devuelve unRegExpen caché; no lo modifiques. Su segundo argumento,exact, ya no existe.- Los caracteres de regex en los patrones coinciden de forma literal. Un
*en mitad de un patrón coincide con un segmento. matchRoute()eisPathActive()ignoran un#hashen la ruta.
Loader
Opciones eliminadas
LoaderOptions.fetch y EntryConfig.html ya no existen. El loader nunca los leía; elimínalos de tu configuración.
// 0.4
new ModuleLoader({ fetch: myFetch });
orchestrator.register({ name: 'x', entry: { scripts: ['/x.js'], html: '<div></div>' }, container: '#x' });// 0.5
import { ModuleLoader } from '@tuvix.js/loader';
const loader = new ModuleLoader({ timeout: 10000 });
void loader;Los scripts de módulo se ejecutan una vez por URL
Después de unload(name), una app cuyo script type: 'module' se registró a sí mismo no vuelve a cargarse desde la misma URL, porque los navegadores ejecutan un script de módulo una vez por URL. Usa una entrada con función loader para las apps que descargas y vuelves a cargar:
import { createOrchestrator } from 'tuvix.js';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'editor',
entry: () => import('./apps/editor'),
container: '#editor',
});load() para un módulo en caché ahora informa su duration real en lugar de 0.
Module Federation
federatedEntry y createFederatedEntry devuelven una función loader en lugar de { scripts: [remoteEntry.js] }. La entrada de 0.4 cargaba el script del contenedor y luego fallaba con "no exports found".
// 0.4
orchestrator.register({
name: 'dashboard',
entry: federatedEntry({ name: 'dashboard', url: 'https://cdn.example.com/dashboard/remoteEntry.js' }),
container: '#main',
});// 0.5
import { createOrchestrator } from '@tuvix.js/core';
import { createFederatedLoader, federatedEntry } from '@tuvix.js/module-federation';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'dashboard',
entry: federatedEntry(
{ name: 'dashboard', url: 'https://cdn.example.com/dashboard/remoteEntry.js' },
'./App', // exposed module path
),
container: '#main',
activeWhen: '/dashboard/*',
});
const remotes = createFederatedLoader({
remotes: [{ name: 'settings', url: 'https://cdn.example.com/settings/remoteEntry.js' }],
shareScope: {}, // replaces 0.4 `shared`
});
orchestrator.register({
name: 'settings',
entry: remotes.createFederatedEntry('settings', './App'),
container: '#main',
activeWhen: '/settings/*',
});FederatedLoaderConfig.sharedse sustituye porshareScope. 0.4 nunca leíashared.loadRemoteContainerdevuelveFederatedContaineren lugar deany.
React
- El HTML de servidor de
TuvixReactAppahora contiene marcadores de comentario de Suspense (<!--$-->...<!--/$-->). Renderiza e hidrata con@tuvix.js/react0.5 en ambos lados. mountse rechaza cuando el primer renderizado lanza un error. En 0.4 el error llegaba al manejador de errores raíz de React ymountse resolvía. Ahora el orquestador muestra elfallbackde la app y llama aonError.unmounten un contenedor que el módulo nunca montó no hace nada. 0.4 lo vaciaba.
Vue
update() ya no escribe app.config.globalProperties.$tuvixProps. Ahora las props son reactivas:
<!-- 0.4 -->
<template>{{ $tuvixProps.theme }}</template><!-- 0.5 -->
<script setup lang="ts">
defineProps<{ theme?: string }>();
</script>
<template>{{ theme }}</template>Los descendientes pueden usar inject('tuvixProps'), que devuelve el mismo objeto reactivo.
VueSsrMicroAppConfigya no aceptassr.createSsrVueMicroApplo establece.unmounten un contenedor que el módulo nunca montó no hace nada.
Svelte
- Sin
ssr,createSvelteMicroAppvacía el contenedor antes de renderizar. 0.4 añadía los componentes de Svelte 3/4 después del markup existente. UsacreateSsrSvelteMicroApppara conservar e hidratar el markup del servidor. unmounten un contenedor que el módulo nunca montó, o cuyo montaje falló, no hace nada. 0.4 lo vaciaba. Los cuatro adaptadores se comportan ahora así.renderSvelteToStringya no recurre aApp.rendercuandosvelte/serverno se carga. Si se pasarender, usa Svelte 5; si no, toma el camino de Svelte 5 cuandosvelteexportamount, y llama aApp.rendersolo para Svelte 3/4. Un error de importación o de renderizado de Svelte 5 te llega directamente.
Los componentes de Svelte 5 funcionan en 0.5; en 0.4 createSvelteMicroApp fallaba con ellos. Un update() de Svelte 5 vuelve a montar el componente salvo que pases SvelteMap de svelte/reactivity en la configuración, que actualiza las props en el sitio:
import { SvelteMap } from 'svelte/reactivity';
import { createSvelteMicroApp } from '@tuvix.js/svelte';
import Profile from './Profile.svelte';
export default createSvelteMicroApp({ name: 'profile', App: Profile, SvelteMap });Angular
Nueva forma de la configuración
// 0.4
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
export default createAngularMicroApp({
name: 'admin',
module: AppModule,
platform: platformBrowserDynamic,
selector: 'app-root',
});// 0.5
import { createAngularMicroApp } from '@tuvix.js/angular';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
export default createAngularMicroApp({
name: 'admin',
component: AppComponent, // required
module: AppModule, // only when AppComponent is declared in an NgModule
});platform y compilerOptions ya no existen. Quita import '@angular/compiler' de tu entrada; el adaptador carga el compilador para los componentes JIT.
Sin zone.js por defecto
Las apps se ejecutan sin zone.js salvo que pases zone: true. En Angular 15-17, los eventos del DOM ya no disparan la detección de cambios; llama a ChangeDetectorRef.detectChanges() en los handlers, o importa zone.js en tu entrada y pasa zone: true. Angular 18+ programa la detección de cambios sin zone.js. Consulta Angular.
Las props son inputs
update() ya no escribe window.__TUVIX_ANGULAR_PROPS__. Declara propiedades @Input() en su lugar; el adaptador las establece con setInput.
// 0.4
ngOnInit() {
this.userId = (window as any).__TUVIX_ANGULAR_PROPS__?.admin?.userId;
}// 0.5
import { Component, Input } from '@angular/core';
@Component({ selector: 'app-admin', standalone: true, template: '{{ userId }}' })
export class AdminComponent {
@Input() userId = '';
}Otros cambios de Angular
createSsrAngularMicroAppya no añadeprovideClientHydration(). Vuelve a renderizar dentro del elemento host del servidor.unmounten un contenedor que el módulo nunca montó no hace nada.
Sandbox
Sandbox({ css: false }).activate(container)devuelvenully no adjunta ningún shadow root.ISandbox.activatedevuelveShadowRoot | null; comprueba si esnullantes de llamar aaddStyle.- El modo estricto de
JsSandboxlanza unTypeErrorante una escritura bloqueada. 0.4 registraba un aviso y guardaba el valor de todos modos. Añade aallowedGlobalslas variables globales en las que escribe tu código.
import { createSandbox } from '@tuvix.js/sandbox';
declare const container: HTMLElement;
const sandbox = createSandbox({ css: true, js: false });
const shadow = sandbox.activate(container);
if (shadow) sandbox.css.addStyle(shadow, ':host { display: block; }');Server
createServerRenderer().render()devuelvestatusCode: 502cuando al menos una app coincide con la ruta y fallan todas las peticiones de fragmentos. Sigue devolviendo 200 cuando ninguna app coincide o cuando un fragmento tiene éxito.createMiddlewareenvía ese estado. El renderizador de streaming siempre envía 200.createMiddlewareycreateStreamingMiddlewarellaman anext(error)en lugar denext()cuando el renderizado lanza un error, así que Express dirige la petición a tu manejador de errores.
Correcciones compatibles que conviene conocer
require('@tuvix.js/core')y los demás puntos de entrada CommonJS funcionan. En 0.4, cinco paquetes apuntabanrequirea archivos que el build nunca generaba.getGlobalBus()guarda su instancia enwindow, así que las micro apps empaquetadas por separado comparten un único bus.- Los adaptadores y
defineMicroAppdespachantuvix:mounted,tuvix:unmountedytuvix:error. Consulta Eventos de ciclo de vida. - Un
bootstrap()que lanza un error se vuelve a ejecutar en el siguiente montaje. 0.4 lo omitía a partir de ese momento. register()después destart()monta la app de inmediato cuando la ruta actual la activa, y empieza a observar una app conmountWhenVisible.- Cada fallo del ciclo de vida emite
tuvix:app:erroren el bus con{ name, error, phase }. 0.4 solo lo emitía para fallos de carga y de montaje. maxListenersavisa conconsole.warnuna vez por evento, con o sindebug.orchestrator.connectRouter()sustituye a los puentes desetTimeout+reconcile(). Consulta Router externo.