Ir al contenido

Ciclo de vida

Una app registra un objeto de ciclo de vida. Solo mount es obligatorio; el resto son capacidades opcionales que Wu comprueba antes de llamarlas.

wu.define('cart', {
mount(container) { /* obligatorio */ },
unmount(container) { },
activate(container) { /* keep-alive: visible */ },
deactivate(container) { /* keep-alive: oculta */ },
update(container, props) { /* props en vivo, v2.2+ */ },
hydrate(container, { appName, props }) { /* SSR, v2.7+ */ },
});

Un wu.define() sin mount lanza un error de inmediato.

Slot Se llama cuando Desde
mount(container) La app se pone en pantalla 1.0
unmount(container) La app se destruye 1.0
deactivate(container) Una app keep-alive se oculta 2.0
activate(container) Una app keep-alive se vuelve a mostrar 2.0
update(container, props) wu.update() envía props nuevas 2.2
hydrate(container, ctx) El contenedor tiene marcado renderizado en servidor 2.7

container es el <div class="wu-app-root"> dentro del shadow root de la app.

wu.mount()
[registrada] ─────────────────► [montada]
▲ │ ▲
│ │ │ wu.mount() / wu.show()
│ refs → 0, gracia 60 ms │ │ (activate)
│ (unmount) │ │
│ keepAlive│ │
│ (deactivate) │
│ ▼ │
└──────────────────────── [oculta]
unmount({ force: true })

montada y oculta son registros separados. Una app oculta sigue plenamente viva: su DOM, su estado JS, sus temporizadores y su iframe persisten; lo único que cambia es que el display del contenedor anfitrión pasa a none.

Conteo de referencias y temporizador de gracia

Sección titulada «Conteo de referencias y temporizador de gracia»

Cada mount() incrementa un contador; cada unmount() lo decrementa. El desmontaje solo se ejecuta cuando el contador llega a 0 y un temporizador de gracia de 60 ms se dispara sin que haya habido un remontaje entre medias.

React StrictMode en desarrollo:
mount() → refs 0→1
unmount() → refs 1→0, temporizador de gracia armado (60 ms)
mount() → refs 0→1, temporizador cancelado ← sin desmontaje, sin parpadeo

Por esto los efectos de doble invocación de StrictMode y las reentradas de Suspense son inofensivos, y por esto no necesitas desactivar StrictMode para usar Wu.

Consecuencias que conviene tener presentes:

  • Tres montajes necesitan tres desmontajes. Un desmontaje parcial no hace nada y registra unmount skipped: N ref(s) still active.
  • { force: true } se salta todo: sin comprobar referencias, sin periodo de gracia, desmontaje inmediato. Úsalo en tests y en shells que necesitan un desmontaje determinista.
  • mount() se deduplica en vuelo. Dos montajes concurrentes de la misma app comparten una sola promesa.
  • Un montaje concurrente durante el desmontaje espera a que el desmontaje termine, en lugar de ver una app a medio desmontar como “ya montada”.
mount(container) {
this.root = createRoot(container);
this.root.render(<App />);
}

Cuando se llama a mount, Wu ya ha comprobado el RBAC, creado el shadow root, inyectado los estilos y esperado a stylesReady, cargado el bundle de entrada si hacía falta y esperado a wu.define().

Un montaje fallido se reintenta hasta tres veces cuando el error boundary pide un reintento, con backoff (1 s, 2 s) y una limpieza completa del sandbox entre intentos.

unmount(container) {
this.root?.unmount();
this.root = null;
}

Después de que tu unmount retorne, Wu destruye el sandbox de iframe (si lo hay), limpia el shadow root, desconecta los observadores de estilos, cancela las suscripciones a cambios de estilo y revoca los contratos de capacidades de la app.

Wu no puede limpiarlo todo por ti. En modo module deshace los temporizadores, intervalos, frames de animación y listeners que observó durante la carga. Todo lo que tu app cree después — un WebSocket, un IntersectionObserver, una raíz de framework — es cosa tuya y debes liberarlo en unmount.

deactivate(container) { this.poll?.pause(); },
activate(container) { this.poll?.resume(); },

Los llaman wu.hide() / wu.show(), que es en lo que se traducen unmount({ keepAlive: true }) y un mount() posterior.

Ocultar no pausa nada por sí mismo. Los temporizadores siguen disparándose, las peticiones siguen saliendo, los WebSockets siguen abiertos. deactivate es donde tú bajas el ruido; el framework no lo hará por ti.

Los errores lanzados desde cualquiera de los dos slots se capturan y se registran — nunca rompen la transición de ocultar/mostrar.

Mira Pestañas keep-alive.

update(container, props) {
this.root.render(<App {...props} />);
}

Lo dirige wu.update(appName, props), que devuelve un booleano:

Situación Resultado
La app está montada y tiene slot update se llama al slot, devuelve true
La app está oculta pero tiene el slot se llama al slot, devuelve true
La app no está montada avisa, devuelve false
La app está a mitad de desmontaje avisa, devuelve false
El adaptador no tiene slot update avisa, devuelve false
El slot lanza un error lo gestiona el error boundary, devuelve false

Las props se fusionan, no se reemplazan, y el conjunto fusionado se guarda en el registro del montaje para wu.inspect(). Un hook beforeUpdate puede vetar el envío.

Los adaptadores construidos sobre createWuAdapter exponen el slot solo cuando su framework puede volver a renderizar en el sitio, que es lo que permite que wu.update() sea honesto en lugar de no hacer nada en silencio.

hydrate(container, { appName, props }) {
hydrateRoot(container, <App {...props} />);
}

Cuando el contenedor anfitrión trae marcado de wu-framework/server, mount() detecta el shadow root declarativo del servidor, lo adopta en lugar de crear uno nuevo y llama a hydrate en vez de a mount.

Las props son exactamente las props con las que renderizó el servidor, leídas de window.__WU_SSR_STATE__[appName] (publicadas por renderStateScript() o por los componentes SSR de Astro/Next). Sin volver a pedir datos.

Si la app no tiene slot hydrate, se llama a mount(container) sobre el marcado del servidor — correcto, solo que repintado. El HTML del servidor ya sirvió como marcador de posición instantáneo.

Wu también materializa a mano un <template shadowrootmode> que haya quedado pendiente, para los navegadores sin Declarative Shadow DOM nativo, así que el polyfill es doble seguro.

Mira Hidratación.

El shell puede interceptar cada transición con wu.hooks.use(phase, middleware):

const off = wu.hooks.use('beforeMount', async (ctx, next) => {
if (!(await isAuthorized(ctx.appName))) return; // no llamar a next() cancela
await next();
}, { priority: 10 });

Doce fases: beforeInit, afterInit, beforeLoad, afterLoad, beforeMount, afterMount, beforeUpdate, afterUpdate, beforeUnmount, afterUnmount, beforeDestroy, afterDestroy.

  • No llamar a next() cancela la operación.
  • Lanzar un error antes de next() también cancela: desde la v2.7 los hooks son fail-closed. Antes, un guardia de autenticación que lanzaba un error por un fallo de red dejaba que el montaje continuara. Puedes volver al comportamiento antiguo hook a hook con { failOpen: true }.
  • Una priority más alta se ejecuta antes (por defecto 0).
  • next(patch) hace una fusión superficial en el contexto para los hooks posteriores.
  • Lanzar un error después de next() se registra pero no cancela.

Mira Plugins y hooks.

Evento Cuándo
wu:app:ready (CustomEvent del DOM) Se llamó a wu.define()
app:mounted Montaje completado, con mountTime
app:unmounted Desmontaje completado
app:hidden Ocultado por keep-alive
app:shown Mostrado por keep-alive, con showTime
app:updated Se enviaron props en vivo
app:error Falló el desmontaje
access:denied + wu:access:denied (DOM) El RBAC rechazó un montaje
console.log(wu.inspect());

Devuelve los recuentos de registradas / definidas / montadas / ocultas, el estado por app, el framework, el selector del contenedor, la marca de tiempo del montaje, si soporta props en vivo, las props actuales y el modo de sandbox real — además de las capacidades, los eventos recientes y la instantánea del store.