Ir al contenido

Añadir un shell

El shell es la página que aloja las micro-apps. Es dueño del layout, del enrutado y de la raíz de confianza. Llama a wu.init() una vez y después a wu.mount() por cada app que quiere en pantalla.

Un shell puede ser cualquier cosa que ejecute JavaScript en un navegador: un archivo HTML estático, un sitio Astro, una app de Next o una SPA de React. No necesita compartir framework con ninguna de las apps que aloja.

<!DOCTYPE html>
<html>
<body>
<div id="cart"></div>
<div id="catalog"></div>
<script type="module">
import { wu } from 'wu-framework';
await wu.init({
apps: [
{ name: 'cart', url: 'http://localhost:5173' },
{ name: 'catalog', url: 'http://localhost:5174' },
],
});
await wu.mount('cart', '#cart');
await wu.mount('catalog', '#catalog');
</script>
</body>
</html>

init() obtiene por adelantado el wu.json de cada app y la registra. mount() crea el shadow root, carga el bundle de entrada, espera a wu.define() y ejecuta el mount de la app.

Campo Tipo Por defecto Significado
apps WuAppConfig[] [] Apps a registrar
sandbox 'module' | 'strict' | 'eval' 'module' Modo de sandbox global
strictFallback boolean false Permite que strict degrade a eval si falla el import del iframe
overrides object Configuración de overrides por cookie para QA

init() es idempotente: llamarlo por segunda vez registra un aviso y retorna. Eso es lo que lo hace seguro dentro de un efecto de React o de un layout de Astro que se renderiza en cada página.

Si el manifiesto de alguna app no supera la validación de seguridad, init() lanza un error. Un manifiesto ausente (404) no lo hace: produce un manifiesto por defecto. Mira El manifiesto.

await wu.init({
apps: [
{
name: 'cart',
url: 'https://cart.example.com',
sandbox: 'strict', // sobrescribe el modo global
strictFallback: false, // override por app
keepAlive: true, // ocultar en vez de destruir al desmontar
strategy: 'eager', // política de precarga
roles: ['admin'], // control RBAC
container: '#cart', // informativo; mount() recibe el selector
},
],
});
Campo Tipo Notas
name string Debe coincidir con lo que la app pasa a wu.define()
url string URL base; wu.json se obtiene de <url>/wu.json
sandbox 'module' | 'strict' | 'eval' Override por app del modo global
strictFallback boolean Override por app; si falta, usa el valor global
keepAlive boolean Mira Pestañas keep-alive
strategy 'lazy' | 'eager' | 'preload' | 'idle' Cuándo se descarga el bundle
roles string[] Roles con permiso para montar esta app (RBAC)

strategy controla cuándo se descarga el bundle de la app, independientemente de cuándo se monta.

Estrategia Comportamiento
lazy (por defecto) No pasa nada hasta mount()
eager Se precarga durante init()
preload Emite una pista <link rel="modulepreload"> / Speculation Rules
idle Se encola en requestIdleCallback (con respaldo en setTimeout)

También puedes controlar la precarga a mano:

wu.prefetch(['cart', 'catalog']);
wu.prefetchAll();
await wu.mount('cart', '#cart');
await wu.unmount('cart');
await wu.unmount('cart', { keepAlive: true }); // ocultar, preservar el estado
await wu.unmount('cart', { force: true }); // destruir ya, sin periodo de gracia

mount() lleva conteo de referencias con un temporizador de gracia de 60 ms en el último desmontaje, y eso es lo que hace inofensivos los efectos de doble invocación de React StrictMode y las reentradas de Suspense. Los detalles están en Ciclo de vida.

El montaje también se autorrepara: un montaje fallido se reintenta hasta tres veces con backoff cuando el error boundary pide un reintento, y el sandbox se limpia entre intentos para que no quede nada huérfano.

Montar al navegar, desmontar al salir:

const routes = {
'/cart': { app: 'cart', el: '#outlet' },
'/catalog': { app: 'catalog', el: '#outlet' },
};
let current = null;
async function navigate(path) {
const route = routes[path];
if (!route) return;
if (current && current !== route.app) await wu.unmount(current);
await wu.mount(route.app, route.el);
current = route.app;
}
window.addEventListener('popstate', () => navigate(location.pathname));

Si quieres pestañas que conserven su estado al ocultarse, usa keepAlive: true en lugar de destruir — mira Pestañas keep-alive.

Wu deliberadamente no asume estas tareas:

  • Autenticación. El shell decide quién es el usuario. El RBAC de Wu solo controla mount() en el cliente; la barrera de verdad es que tu servidor se niegue a servir el bundle.
  • Layout y enrutado. Wu monta en los selectores que le des.
  • CSS global. En el modo de estilos shared por defecto, las hojas de estilo del shell son las que heredan todas las apps. Mira Aislamiento de CSS.
  • Confianza. El shell es la raíz de confianza. Nada del lado del cliente puede proteger una página cuyo propio HTML está comprometido — mira el modelo de amenazas.
await wu.mount('cart', '#cart');
const info = wu.getSandboxInfo('cart');
// { requestedMode: 'strict', actualMode: 'strict',
// isolationLevel: 'iframe', mounted: true }
console.log(wu.inspect()); // instantánea completa de la página

Que actualMode difiera de requestedMode significa que una app strict degradó a eval — algo solo posible si activaste strictFallback: true.

Si tu shell es Astro o Next, usa los componentes incluidos en lugar de escribir a mano las llamadas a init y mount — incluida la variante renderizada en servidor. Mira Astro y Next.js.

Errores comunes — léelo antes de tu primer despliegue a producción, no después.