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.
wu.getSandboxInfo()
Sección titulada «wu.getSandboxInfo()»getSandboxInfo(appName: string): { requestedMode: 'module' | 'strict' | 'eval'; actualMode: 'module' | 'strict' | 'eval'; isolationLevel: 'none' | 'iframe'; mounted: boolean;} | nullInforma 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. |
wu.inspect()
Sección titulada «wu.inspect()»inspect(opts?: { events?: number }): SnapshotUna 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ó.
wu.showInspector() / wu.hideInspector()
Sección titulada «wu.showInspector() / wu.hideInspector()»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();// oawait 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; // truewindow.__WU_DEVTOOLS__.inspect(); // igual que wu.inspect()window.__WU_DEVTOOLS__.show();window.__WU_DEVTOOLS__.subscribe(cb); // todos los eventos del buswu.getStats()
Sección titulada «wu.getStats()»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.
wu.getAppInfo()
Sección titulada «wu.getAppInfo()»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 estadoinfo.manifest; // el wu.json analizadoinfo.mounted; // el registro de montaje vivo, o undefinedinfo.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).
Rendimiento
Sección titulada «Rendimiento»startMeasure(name: string, appName?: string): voidendMeasure(name: string, appName?: string): numbergenerateReport(): objectappName 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 } }).
Integración con User Timing
Sección titulada «Integración con User Timing»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' }): () => voidconst 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().
wu.tagStyleAsApp()
Sección titulada «wu.tagStyleAsApp()»tagStyleAsApp(el: HTMLElement, appName: string): voidMarca 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.
