Ir al contenido

Apps

wu.mount('cart', '#slot') es la API imperativa. wu.app() es lo mismo envuelto en un objeto que recuerda su propio nombre, URL y contenedor — útil cuando un shell gestiona un puñado de apps y prefieres no repetir literales de cadena por todas partes.

app(name: string, config: {
url: string;
container?: string;
keepAlive?: boolean;
autoInit?: boolean;
}): WuApp

Crea un wrapper WuApp. Es síncrono y, por defecto, registra la app en el core de inmediato.

Campo Tipo Por defecto Significado
url string URL base desde la que se sirve la app. No se valida.
container string undefined Selector de contenedor por defecto para mount().
keepAlive boolean false Ocultar en lugar de destruir al hacer unmount().
autoInit boolean true Registrar en el core dentro del constructor. Solo un false literal lo desactiva.
import { wu } from 'wu-framework';
const cart = wu.app('cart', {
url: 'https://cdn.example.com/cart',
container: '#cart-slot',
keepAlive: true,
});
await cart.mount();

Casos límite. config no tiene valor por defecto: wu.app('cart') lanza un TypeError al leer config.url. La factoría no mantiene ningún registro de wrappers — llamar a wu.app('cart', ...) dos veces devuelve dos objetos distintos que comparten una sola entrada en el core, y el segundo registro es un no-op silencioso porque el nombre ya está ocupado. Eso implica también que la url del segundo wrapper se ignora.

El registro anticipado que hace autoInit solo escribe un registro { name, url, keepAlive, status: 'registered' }; no descarga el manifiesto. El manifiesto se carga de forma perezosa en el primer mount().

Todos los métodos son async. Al esperarlos, todos devuelven this —así que se encadenan— excepto destroy(), que devuelve undefined.

mount(container?: string): Promise<this>

Monta la app. Si se llama sin argumento, recurre al container indicado en la construcción.

await cart.mount(); // usa config.container
await cart.mount('#other'); // destino explícito

Arranca lo que falte: si el core nunca se inicializó, llama primero a wu.init({ apps: [{ name, url }] }); si el core conoce la app pero no tiene su manifiesto, llama a wu.registerApp(). Después delega en wu.mount(), así que todo lo de esa página —RBAC, conteo de referencias, reintentos— sigue aplicándose.

Casos límite. Lanza Container not specified for app: <name> cuando ni el argumento ni la configuración aportan un selector. Al tener éxito, el contenedor resuelto se memoriza en this.container, que es lo que hace que un reload() o remount() posterior sin argumento acabe en el sitio correcto. Una asimetría que conviene saber: la vía del init() implícito solo reenvía name y url — un keepAlive puesto en el wrapper no se propaga al core en ese caso concreto.

unmount(options?: { keepAlive?: boolean; force?: boolean }): Promise<this>

Delega en wu.unmount() con las mismas opciones.

await cart.unmount();
await cart.unmount({ force: true });

Casos límite. Nunca lanza. Si la app no está montada ni en estado keep-alive, registra ⚠️ App <name> is not mounted y devuelve this sin tocar el core.

hide(): Promise<this>
show(): Promise<this>

Controles de keep-alive, que delegan en wu.hide() / wu.show().

await editor.hide();
await editor.show();

Casos límite. Ambos son no-op con un aviso cuando falla la precondición: hide() sobre una app desmontada, show() sobre una app que no está oculta. Ninguno lanza.

remount(container?: string): Promise<this>

Desmontaje forzado seguido de un montaje limpio. A diferencia de un unmount() + mount() normal, el desmontaje es forzado, así que salta el temporizador de gracia de 60 ms y cualquier ajuste de keep-alive.

await cart.remount(); // mismo contenedor
await cart.remount('#drawer'); // mover la app

Casos límite. Hereda el lanzamiento de mount() cuando no se puede resolver ningún contenedor, pero después de que el desmontaje ya haya ocurrido — así que un fallo aquí deja la app desmontada, no en su estado anterior.

reload(): Promise<this>

Desmontar, limpiar las cachés del cargador y del manifiesto para esta app, y montar de nuevo. Esta es la llamada que realmente vuelve a descargar el bundle; remount() no lo hace.

await cart.reload();

Casos límite. Usa un unmount() no forzado, así que con keepAlive la app se oculta en lugar de destruirse y el mount() posterior mostrará la instancia cacheada — es decir, reload() no recarga una app keep-alive. Fuérzalo antes si necesitas un refresco de verdad. El vaciado de cachés es de mejor esfuerzo: loader.clearCache y manifest.clearCache solo se llaman si existen.

destroy(): Promise<void>

Fuerza el desmontaje y elimina la app del registro del core por completo.

await cart.destroy();

Casos límite. El único método que no devuelve this — resuelve undefined, así que no se puede encadenar. Después de él, el objeto wrapper sigue existiendo pero su app queda sin registrar; montarla de nuevo pasaría por el arranque implícito de init/registerApp dentro de mount().

start(container?: string): Promise<this>
stop(): Promise<this>

Alias de mount(container) y unmount() sin opciones, para quien prefiera ese vocabulario. Comportamiento idéntico, incluido el lanzamiento de start() cuando no se puede resolver un contenedor.

verify(): Promise<{
name: string;
mounted: boolean;
container: { found: boolean; selector: string; hasShadowDOM: boolean; hasContent: boolean };
wu: { registered: boolean; mountedInWu: boolean };
}>

Una instantánea de diagnóstico de una app: ¿existe el elemento contenedor?, ¿recibió una shadow root?, ¿hay algo dentro?, y ¿está el core de acuerdo en que la app está registrada y montada?

const report = await cart.verify();
if (report.mounted && !report.container.hasContent) {
console.warn('Wu cree que cart está montada, pero su shadow root está vacía');
}

Casos límite. Está declarado async pero no hace E/S: resuelve en el mismo tick. Lee el DOM en vivo, así que solo tiene sentido en un navegador. Cada campo se deriva de forma defensiva; no lanza si falta el contenedor, informa found: false.

Miembro Tipo Notas
name string El nombre de la app.
url string URL base.
container string Selector actual. Lo sobrescribe un mount(container) con éxito.
keepAlive boolean Tal y como se configuró.
isMounted getter → boolean Verdadero solo si el wrapper y el core lo afirman.
isHidden getter → boolean Delega en wu.isHidden(name).
info getter → object { name, url, container, mounted, status }, donde status cae a 'unknown'.
cart.isMounted; // false
await cart.mount();
cart.isMounted; // true
cart.info; // { name: 'cart', url: '...', container: '#cart-slot', mounted: true, status: 'registered' }

El getter isMounted contrasta ambos lados deliberadamente: si el core desmontó la app a espaldas del wrapper (un desmontaje forzado, un destroy()), la bandera propia del wrapper mentiría. Puede devolver undefined en lugar de false en el caso exótico de que el core no tenga ningún mapa mounted.

registerApp(appConfig: WuAppConfig): Promise<void>

Registra una app después de que init() ya se haya ejecutado — para apps descubiertas en tiempo de ejecución, desde un feature flag o una respuesta del servidor.

await wu.registerApp({
name: 'promo-banner',
url: 'https://cdn.example.com/promo',
sandbox: 'strict',
});
await wu.mount('promo-banner', '#promo');

A diferencia del registro anticipado del wrapper, este descarga el manifiesto y guarda la configuración completa junto a él, de modo que los campos por app (keepAlive, sandbox, styleMode, roles) sobreviven.

Casos límite. Se rechaza si el manifiesto no se puede cargar — aquí es donde aparece una URL mala, no en el momento del montaje. Registrar un nombre que ya existe sobrescribe la entrada anterior silenciosamente; no desmonta una instancia en marcha de esa app, que seguirá usando la configuración con la que se montó. Ten en cuenta que registerApp() no aplica overrides de QA — esos los aplica init() antes de registrar las apps, así que una app registrada en tiempo de ejecución ignora un override activo.

use(componentPath: string): Promise<any>

Carga un componente que otra app exporta a través de su manifiesto — la vía de escape para UI genuinamente compartida (un botón de un design system) que no quieres duplicar en cada bundle.

La ruta es "<appName>.<componentName>", y el componente debe estar declarado en el wu.json de la app proveedora bajo wu.exports:

{
"name": "design-system",
"wu": {
"exports": {
"Button": "./src/components/Button.js"
}
}
}
const Button = await wu.use('design-system.Button');

Casos límite. Lanza de tres formas distintas, todas rechazadas de manera síncrona:

Condición Error
La ruta no tiene punto / le falta una mitad Invalid component path: <path>. Use format "app.component"
La app proveedora nunca se registró App <appName> not registered
El manifiesto no tiene ese export Component <componentName> not exported by <appName>

El módulo del cargador es perezoso: solo se descarga la primera vez que se llama a use(), así que las páginas que nunca comparten componentes no lo pagan.

Sé honesto contigo mismo sobre el acoplamiento que esto crea. wu.use() es un import real que cruza la frontera de una app: el consumidor pasa a romperse cuando el proveedor cambia la forma de ese componente, que es justamente el modo de fallo que los micro-frontends existen para evitar. Para cualquier cosa con comportamiento en lugar de marcado, los contratos de capacidad te dan el mismo reparto con un rango de versiones y un fallo ruidoso en vez de uno silencioso.

import { app } from 'wu-framework';
const cart = app('cart', { url: '...', container: '#cart' });

registerApp y use no tienen exports independientes: llega a ellos a través del singleton wu.