Ir al contenido

Diagnóstico

Una página con una app React, una app Vue y una app Lit tiene tres juegos de devtools, cada uno ciego a los otros dos. Estas son las llamadas que ven la página entera a la vez.

getSandboxInfo(appName: string): {
requestedMode: 'module' | 'strict' | 'eval';
actualMode: 'module' | 'strict' | 'eval';
isolationLevel: 'none' | 'iframe';
mounted: boolean;
} | null

Informa del aislamiento de JS bajo el que la app se está ejecutando de verdad, en contraposición al que pediste.

await wu.mount('cart', '#cart');
const info = wu.getSandboxInfo('cart');
if (info.actualMode !== info.requestedMode) {
console.warn('Sandbox degradado, probablemente un problema de CORS', info);
}
Campo Significado
requestedMode Lo que pidió la configuración de la app o el init() global.
actualMode Lo que se está ejecutando. Difiere de requestedMode cuando strict cayó a eval.
isolationLevel 'iframe' tanto para strict como para eval; 'none' para module.
mounted true si está montada, false si está en estado keep-alive (oculta).

Casos límite. Devuelve null si la app no está montada — ni en el registro de montadas ni en el de ocultas. Llámalo después de que mount() resuelva, no antes.

Fíjate en el cambio de la v2.7: el modo eval ahora informa isolationLevel: 'iframe', porque ejecuta los scripts en el realm del iframe en lugar de bajo trampas de Proxy sobre el window real. El antiguo valor 'proxy-trap' se mantiene en las declaraciones de tipos por compatibilidad, pero ya no se emite.

El nombre “sandbox” es un nivel, no una garantía:

Modo Aislamiento Realidad
module (por defecto) Ninguno El código se ejecuta en la ventana principal. Los efectos secundarios se rastrean para que el desmontaje pueda limpiarlos; la app puede seguir contaminando globales a voluntad.
strict Iframe Una ventana separada, más un import() real: tree shaking, source maps y HMR funcionan todos.
eval Iframe Descarga, analiza y ejecuta los scripts clásicos de la app dentro del iframe. Para bundles UMD/IIFE; no puede ejecutar módulos ES. Pese al nombre, desde la v2.7 no se hace ningún eval: no hace falta unsafe-eval.
inspect(opts?: { events?: number }): Snapshot

Una instantánea estructurada y completa de la página, que agrega estado que de otro modo vive en cinco registros distintos. Es la columna vertebral de datos del inspector visual y del puente window.__WU_DEVTOOLS__, y es útil por sí sola.

const snap = wu.inspect({ events: 50 });
console.log(snap.summary); // { registered: 5, defined: 4, mounted: 3, hidden: 1 }
console.table(snap.apps);

Forma:

{
version: '2.7.2',
timestamp: 1730000000000,
summary: { registered, defined, mounted, hidden },
apps: [ /* primero las montadas, luego las ocultas */ ],
defined: ['cart', 'header'], // apps que llamaron a wu.define()
registered: ['cart', 'header'], // apps conocidas por init()
capabilities: [{ name, version, app }],
events: {
recent: [{ type, appName, timestamp }],
stats: { emitted, subscriptions, rejected, ... },
},
store: {
snapshot: { /* el store entero */ },
metrics: { reads, writes, ... },
},
}

Cada entrada de apps:

Campo Significado
name Nombre de la app.
status 'mounted' u 'hidden'.
framework De la configuración de la app, o null.
containerSelector Dónde vive.
mountedAt Marca de tiempo.
liveProps Si el adaptador expone un slot update; es decir, si wu.update() va a funcionar.
props Las últimas props empujadas, o null.
sandbox El objeto de getSandboxInfo().

opts.events es 25 por defecto.

Casos límite. inspect() nunca lanza. Cada subconsulta —contratos, estadísticas de eventos, instantánea del store, métricas del store, historial— está protegida individualmente, degradando a null o [] en lugar de fallar. La instantánea del store es el objeto de estado vivo por referencia, así que trátalo como de solo lectura. version es null cuando la versión de compilación no se sustituyó.

showInspector(): Promise<{ close: () => void } | null>
hideInspector(): Promise<void>

Abre un overlay visual: las apps y su estado, las capacidades, los eventos recientes, un volcado del store y un control deslizante de timeline. Se renderiza dentro de su propio Shadow DOM, así que no puede chocar con los estilos de las apps, y es un chunk perezoso que no cuesta nada hasta que se abre.

const inspector = await wu.showInspector();
// ...
inspector.close();
// o
await wu.hideInspector();

El overlay se refresca aproximadamente una vez por segundo, y se autolimpia si su nodo anfitrión se elimina desde fuera (un cambio de ruta de la SPA, un body.innerHTML = '').

Abrirlo también resuelve el chunk de la timeline, que es por lo que el botón de grabar se arma de inmediato mientras que un wu.timeline.record() programático puede que no. Resolver el chunk no inicia la grabación.

Casos límite. showInspector() devuelve null fuera de un navegador. Es idempotente: llamarlo dos veces devuelve un handle al mismo overlay. Cerrar el inspector no detiene una grabación activa. hideInspector() nunca lanza, incluso cuando nunca se abrió nada.

También hay un pequeño global siempre presente para herramientas externas:

window.__WU_DEVTOOLS__.isWu; // true
window.__WU_DEVTOOLS__.inspect(); // igual que wu.inspect()
window.__WU_DEVTOOLS__.show();
window.__WU_DEVTOOLS__.subscribe(cb); // todos los eventos del bus
getStats(): {
registered: number;
defined: number;
mounted: number;
hidden: number;
apps: string[];
}

La contrapartida barata de inspect(): recuentos y nombres, nada más.

const { mounted, hidden, apps } = wu.getStats();
console.log(`${mounted} montadas, ${hidden} ocultas, de ${apps.length} conocidas`);

Casos límite. apps lista los nombres registrados, que no es lo mismo que los nombres montados. Nunca lanza.

getAppInfo(appName: string): {
registered: object | undefined;
manifest: object | undefined;
mounted: object | undefined;
definition: object | undefined;
}

Todo lo que el framework sabe de una app, directamente de los cuatro registros.

const info = wu.getAppInfo('cart');
info.registered; // la config tal y como se registró, incluidos manifiesto y estado
info.manifest; // el wu.json analizado
info.mounted; // el registro de montaje vivo, o undefined
info.definition; // el objeto de ciclo de vida que la app pasó a wu.define()

Casos límite. Nunca lanza y siempre devuelve un objeto: para una app desconocida simplemente tiene cuatro campos undefined, así que comprueba los campos en lugar del resultado. Los valores son referencias internas vivas, no copias. mounted es undefined para una app oculta con keep-alive, aunque la app siga existiendo; contrástalo con wu.isHidden(appName).

startMeasure(name: string, appName?: string): void
endMeasure(name: string, appName?: string): number
generateReport(): object

appName es 'global' por defecto. endMeasure() devuelve la duración en milisegundos.

import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework';
startMeasure('checkout-flow', 'cart');
await doCheckout();
const ms = endMeasure('checkout-flow', 'cart');

El framework acota mount, show y load por su cuenta, así que esos aparecen sin que hagas nada. Nada más es automático: aquí no hay recolección de tiempos de navegación ni de recursos.

generateReport() devuelve:

{
timestamp: 1730000000000,
totalMeasurements: 42,
apps: {
cart: {
measurementCount: 12,
stats: {
mount: { count: 3, avg: 214.5, min: 180.2, max: 260.1, last: 203.4 },
},
},
},
}

Casos límite. endMeasure() devuelve 0 cuando no existe una marca de inicio coincidente, en silencio — esto es normal bajo el doble montaje de React StrictMode, así que no se trata como un error. Un startMeasure() repetido con la misma clave sobrescribe silenciosamente el inicio anterior. Las mediciones se limitan a 1000 por app (FIFO), y los objetos stats del informe son referencias vivas, no copias. Las apps sin mediciones nunca aparecen en el informe.

Existen umbrales para mount (3000 ms), unmount (1000 ms) y load (5000 ms); superar uno registra un aviso. Ajústalos con wu.performance.configure({ thresholds: { mount: 5000 } }).

Cuando el navegador lo soporta (lo habitual), cada medición aterriza también en la User Timing API bajo un prefijo wu:, así que aparece en el panel de rendimiento de Chrome DevTools junto a todo lo demás.

getDevToolsEntries(opts?: { type?: 'mark' | 'measure' | 'all'; appName?: string }): PerformanceEntry[]
observe(callback: (entry: PerformanceEntry) => void, opts?: { type?: 'mark' | 'measure' | 'all' }): () => void
const stop = wu.performance.observe((entry) => {
if (entry.duration > 500) report(entry);
}, { type: 'measure' });

Casos límite. getDevToolsEntries() devuelve [] donde User Timing no está disponible, y con type: 'all' devuelve primero todas las marcas y luego todas las mediciones, en lugar de fusionarlas cronológicamente. observe() devuelve una baja no-op cuando falta PerformanceObserver o cuando la callback no es una función, y usa type: 'measure' por defecto. Ambos están acotados a las entradas wu:, y clearMetrics() solo limpia las entradas bajo ese prefijo: las entradas ajenas nunca se tocan.

Otros métodos: getMetrics(appName) (objeto vivo o null), getAllMetrics(), clearMetrics(appName?) y configure().

tagStyleAsApp(el: HTMLElement, appName: string): void

Marca un elemento <style> o <link> como perteneciente a una app concreta, para que el modo de estilos fully-isolated (alias own-only) lo recoja sea cual sea el bundler que lo produjo.

const style = document.createElement('style');
style.textContent = ':root { --brand: #06f; }';
wu.tagStyleAsApp(style, 'cart');
document.head.appendChild(style);

Antes de la v2.1 solo se reconocía el atributo data-vite-dev-id de Vite, así que quienes usaban webpack y esbuild tenían que recurrir a un fallback por patrón de URL. Esta es la versión explícita e independiente del bundler: asigna data-wu-app="<appName>", que es lo primero que comprueba el puente de estilos.

Casos límite. Es un no-op silencioso —sin lanzar— cuando el es falsy, no tiene setAttribute, o appName está vacío. No valida que la app exista, y sobrescribe cualquier valor existente. Solo tiene efecto para apps que usan el modo de estilos fully-isolated; en los demás modos el atributo es inerte.

Una nota sobre lo que te dicen estas llamadas

Sección titulada «Una nota sobre lo que te dicen estas llamadas»

inspect() y getStats() informan de la contabilidad interna del propio framework. Te dicen lo que Wu cree, que no siempre es lo que contiene el DOM: una app que reventó dentro de su propio mount() después de que el framework la registrara puede aparecer como mounted con una shadow root vacía. WuApp.verify() contrasta ambas cosas, y getSandboxInfo() es la única llamada que informa de algo que el framework descubrió en lugar de algo que le contaron.