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.
wu.app()
Sección titulada «wu.app()»app(name: string, config: { url: string; container?: string; keepAlive?: boolean; autoInit?: boolean;}): WuAppCrea 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().
La clase WuApp
Sección titulada «La clase WuApp»Todos los métodos son async. Al esperarlos, todos devuelven this —así que se
encadenan— excepto destroy(), que devuelve undefined.
mount()
Sección titulada «mount()»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.containerawait cart.mount('#other'); // destino explícitoArranca 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()
Sección titulada «unmount()»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() / show()
Sección titulada «hide() / show()»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()
Sección titulada «remount()»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 contenedorawait cart.remount('#drawer'); // mover la appCasos 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()
Sección titulada «reload()»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()
Sección titulada «destroy()»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() / stop()
Sección titulada «start() / stop()»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()
Sección titulada «verify()»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.
Propiedades
Sección titulada «Propiedades»| 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; // falseawait cart.mount();cart.isMounted; // truecart.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.
wu.registerApp()
Sección titulada «wu.registerApp()»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 sí 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.
wu.use()
Sección titulada «wu.use()»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.
Exports con nombre
Sección titulada «Exports con nombre»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.
