Arquitectura
Wu tiene cuatro piezas móviles y una regla: las apps nunca se importan entre sí.
┌─ Shell (la raíz de confianza) ────────────────────────────────────────┐│ wu.init({ apps }) ──► obtiene <url>/wu.json de cada app ││ ││ wu.mount('cart', '#cart') ││ │ ││ ▼ ││ ┌─ Contenedor anfitrión #cart ─────────────────────────────────────┐ ││ │ shadowRoot (attachShadow, delegatesFocus) │ ││ │ ├─ <style> estilos base del sandbox │ ││ │ ├─ estilos inyectados (según styleMode) │ ││ │ └─ <div class="wu-app-root"> ← el contenedor de la app │ ││ └──────────────────────────────────────────────────────────────────┘ ││ ││ El contexto de ejecución JS depende del modo de sandbox: ││ module → ventana principal ││ strict/eval → iframe oculto del mismo origen ││ ││ ┌─ Sustrato (compartido por todas las apps y todos los modos) ─────┐ ││ │ eventBus · store · contratos de capacidades · timeline · hooks │ ││ └──────────────────────────────────────────────────────────────────┘ │└───────────────────────────────────────────────────────────────────────┘Las cuatro piezas
Sección titulada «Las cuatro piezas»1. El shell
Sección titulada «1. El shell»La página que llama a wu.init() y wu.mount(). Es dueña del layout, el
enrutado y la autenticación. Es la raíz de confianza: todo lo que hace Wu ocurre
dentro del origen del shell, por invitación del shell.
2. El manifiesto
Sección titulada «2. El manifiesto»<appUrl>/wu.json — la declaración que hace la app de su propio nombre, punto
de entrada, modo de estilos, exports, imports y roles requeridos. Se obtiene
durante init(), se valida y se cachea en memoria. Los campos que Wu no
reconoce se descartan. Mira El manifiesto.
3. El sandbox
Sección titulada «3. El sandbox»Dos capas independientes, siempre presentes las dos:
- Un shadow root para el DOM y el CSS. Todas las apps reciben uno, en todos los modos, sin forma de desactivarlo (salvo que el Shadow DOM no esté disponible, lo que activa un contenedor de respaldo degradado).
- Un contexto de ejecución JS, elegido por el
modo
sandbox: la ventana principal (module) o un iframe oculto del mismo origen (strict,eval).
La capa de CSS es encapsulación de verdad. La capa de JS es contención para tu propio código: aísla globales y garantiza la limpieza, pero no es una barrera frente a código que intenta escapar. Mira el modelo de amenazas.
4. El sustrato
Sección titulada «4. El sustrato»Un bus de eventos, un store compartido y contratos de capacidades versionados, todos viviendo en la ventana del shell y accesibles desde cualquier app sin importar el modo de sandbox. Esta es la única forma sancionada de que las apps interactúen. Mira Comunicación.
Qué hace realmente mount()
Sección titulada «Qué hace realmente mount()»wu.mount('cart', '#cart') │ ├─ 1. Comprobación RBAC — ¿can(appName)? ───► lanza WU_ACCESS_DENIED │ ├─ 2. Incrementa el contador de referencias; cancela cualquier desmontaje │ pendiente │ ├─ 3. Crea el sandbox │ ├─ ¿hay marcado del servidor? → lo adopta (ruta de hidratación) │ └─ si no → attachShadow + <div class="wu-app-root"> │ ├─ 4. Inyecta estilos según styleMode; espera a sandbox.stylesReady │ ├─ 5. Carga la app (solo si aún no se ha registrado) │ ├─ resuelve la URL de entry (directa, o sondeo HEAD 8× en paralelo) │ ├─ module → import() en la ventana principal │ ├─ strict → import() dentro de un iframe oculto │ └─ eval → obtiene el HTML, lo parsea, ejecuta los <script> clásicos │ en el iframe │ ├─ 6. Espera a wu.define('cart', …) (timeout de 10 s) │ ├─ 7. Llama a lifecycle.hydrate(...) si se adoptó marcado del servidor, │ y si no a lifecycle.mount(container) │ └─ 8. Registra el montaje, emite app:mounted, ejecuta los hooks afterMountLos pasos 5 y 6 solo ocurren en el primer montaje. Una vez que una app ha
llamado a wu.define(), su ciclo de vida queda cacheado y los remontajes saltan
directamente al paso 7 — porque, de todas formas, un navegador no vuelve a
ejecutar un módulo ES ya cacheado.
Si algún paso lanza un error, decide el error boundary: reintentar (hasta tres intentos, con backoff y una limpieza completa del sandbox entre ellos), recuperarse en silencio o relanzar.
Dos ejes independientes, más un tercero
Sección titulada «Dos ejes independientes, más un tercero»Tres cosas se configuran por separado y se combinan libremente:
| Eje | Dónde se define | Valores |
|---|---|---|
| Sandbox JS | configuración de init() o config por app |
module, strict, eval |
| Aislamiento CSS | solo en wu.json |
shared, isolated, fully-isolated |
| Comunicación | — | siempre disponible, idéntica en todos los modos |
La asimetría de la fila central es deliberada y sorprende a todo el mundo una
vez: styleMode es una propiedad de la app, así que la app lo declara en su
propio manifiesto. Definirlo en la configuración de init() del shell no hace
nada.
Qué no hace Wu
Sección titulada «Qué no hace Wu»No hay negociación de versiones para los módulos de framework. Wu sí
reutiliza el runtime cuando el shell lo pone en el global — seis adapters miran
window.React y equivalentes antes de importar el suyo — pero nada compara
versiones, así que una major distinta se adopta en silencio. No hay registro de
módulos ni share scope al estilo de Module Federation. Lo que ofrece en su lugar
son contratos de capacidades: las apps publican objetos vivos bajo un rango
semver, y un desajuste falla de forma ruidosa en el consume(). Eso sí es
negociación de verdad, pero para capacidades entre apps, no para módulos de
framework. Ver comunicación.
No se integra con el build. No hay plugin de webpack para Wu, ni plugin de Vite, ni configuración de bundler compartida. Una app es una URL que sirve un manifiesto y un bundle. El acoplamiento entre shell y app es HTTP, no el grafo de build.
No hay imports entre apps. Los campos exports/imports de wu.json
describen rutas de componentes para wu.use(), no son un enlazador. Una app no
puede meterse en el scope de módulos de otra.
Dónde vive el código
Sección titulada «Dónde vive el código»| Asunto | Módulo |
|---|---|
| Orquestación, mount/unmount, selección de sandbox | wu-core.js |
| Creación del shadow root, inyección de estilos | wu-sandbox.js, wu-style-bridge.js |
Rastreo de efectos secundarios en modo module |
wu-proxy-sandbox.js |
Realm de iframe para strict / eval |
wu-iframe-sandbox.js (perezoso) |
Parseo de HTML para eval |
wu-html-parser.js (perezoso) |
| Obtención y validación del manifiesto | wu-manifest.js |
| Sustrato | wu-event-bus.js, wu-store.js, wu-contracts.js |
| Puntos de extensión | wu-hooks.js, wu-plugin.js |
| Renderizado en servidor | src/server/ |
Todo lo marcado como perezoso es un chunk aparte en el build ESM y solo se carga cuando se usa realmente esa funcionalidad.
