Meeko DevTools
DevTools permite examinar y editar cada instancia hidratada durante el desarrollo con Vite. Hay dos interfaces sobre el mismo runtime: un panel dentro de la página y un panel de Chrome DevTools. Ambas consumen el hook window.__MEEKO_DEVTOOLS__, que sólo existe en desarrollo.
Panel dentro de la página#
Desde 1.8, la forma recomendada es activarlo en meeko.config.ts, como devtools: true en Nuxt:
// meeko.config.ts
import { defineMeekoConfig } from "@meekojs/meeko/framework";
export default defineMeekoConfig({ devtools: true });Con esa opción, meeko dev monta el panel en todas las páginas sin código de la aplicación. Nunca lo añade meeko build, meeko start, meeko preview ni una página prerenderizada: el bundle de producción no contiene @meekojs/meeko/devtools-ui. Por defecto es false en 1.x, para que quien actualiza no vea un panel nuevo sin pedirlo. { position: "bottom-left" } lo fija en otra esquina ("bottom-right", la de por defecto, "bottom-left", "top-right" o "top-left"), y { panel: false } lo desactiva sin borrar las opciones. Un valor que no es válido detiene meeko dev, build y check con un mensaje que nombra la opción; véase la referencia de devtools. La extensión de Chrome y el puente del editor funcionan con o sin la opción, porque leen el mismo hook.
Como alternativa, una aplicación puede montarlo por su cuenta, por ejemplo en otro contenedor; no combine las dos formas, o puede haber dos paneles:
if (import.meta.env.DEV) {
const { mountDevtoolsPanel } = await import("@meekojs/meeko/devtools-ui");
const panel = mountDevtoolsPanel({ position: "top-right" });
// panel.unmount() libera la interfaz cuando deje de usarse.
}El ejemplo Vite del repositorio lo monta así en modo desarrollo (pnpm run build y pnpm run vite:dev).
Aspecto, minimizar y mover#
- Colores de Meeko. El panel usa los tokens del sitio de Meeko (acento azul, superficies neutras e Inter si la página la tiene, con
system-uide respaldo). Es oscuro o claro según el sistema; si<html>tienedata-theme="light"o"dark"(la opcióntheme), sigue a la página y cambia con ella. Los tokens viven dentro de su Shadow DOM: ni el CSS de la aplicación lo altera ni el panel altera la aplicación. - Minimizar. El botón Minimizar (o
Alt+Mayús+Men cualquier parte de la página) pliega el panel a una pastilla con el logo y, en rosa, el número de eventos nuevos desde que se plegó. Un clic en la pastilla o el mismo atajo lo restauran. El foco pasa a la pastilla o al botón, y los lectores de pantalla oyen «Restaurar Meeko DevTools · N eventos nuevos». - Mover. El asa de la cabecera (los seis puntos) lleva el panel a la esquina más cercana al soltarla; con el asa enfocada, las flechas lo mueven de esquina. La pastilla también se arrastra.
- Memoria. El estado plegado y la esquina elegida se guardan en
localStorage(meeko:devtools-panel) del origen de la aplicación y sobreviven a las recargas. Si cambiapositionen la configuración, manda la nueva. SinlocalStorage(modo privado, almacenamiento bloqueado) el panel funciona igual, sin memoria.
El panel ofrece:
- un árbol de instancias;
- el detalle de props y señales;
- un editor de señales;
- una línea de tiempo filtrable;
- navegación y preparación de datos con duración y origen, sin copiar sus valores;
- resaltado del DOM;
- un punto de parada por instancia.
Cada evento conserva el nombre y el archivo .meeko del componente y una copia segura del nuevo valor. Al seleccionar un evento de una instancia activa se abre su detalle. Los estilos viven en Shadow DOM para no alterar los de la aplicación.
Navegación y datos#
El filtro Navegación y datos de ambos paneles muestra las operaciones de la aplicación, separadas de las señales y del ciclo de vida de componentes:
| Evento | Fases | Qué mide |
|---|---|---|
navigation |
start → commit, cancel o error |
Desde que comienza una navegación cliente hasta confirmar su vista, cancelarla o fallar. |
data |
start → ready, cancel o error |
Preparación de módulos y datos antes del commit de la vista. |
data con source: "cache" |
ready |
Lectura satisfecha por la caché local de recursos, sin repetir la petición. |
Cada operación tiene un operationId que enlaza inicio y final, el patrón declarado de la ruta (por ejemplo, /users/[id]) y durationMs al finalizar. El origen de datos es server, client o static según el transporte de la ruta; las lecturas de recursos locales se identifican como cache. La duración de una navegación no espera al final visual de una View Transition.
Estos eventos no incluyen la URL resuelta, query, fragmento, parámetros, cabeceras, mensajes de error ni resultados del loader. Los eventos de caché tampoco incluyen claves de recursos ni valores. Las props y señales siguen siendo inspeccionables por sus mecanismos existentes: esta limitación se refiere a los metadatos de navegación y datos. Sin un runtime de desarrollo que instale el hook no se registran estas operaciones. Se conserva el límite compartido de los últimos 100 eventos.
Panel de Chrome DevTools#
- Abre
chrome://extensions, activa el modo de desarrollador y carga la carpetapackages/devtoolsdel repositorio como extensión descomprimida. - Abre una aplicación Meeko servida por Vite en desarrollo y después las herramientas de desarrollador de Chrome.
- En la pestaña Meeko:
- selecciona una instancia para ver sus props, sus señales y su línea de tiempo;
- filtra por señales o por instancia, y pulsa un evento para volver a su componente activo;
- edita una señal desde Editar señal;
- usa Resaltar para ubicar la instancia en la página;
- activa el punto de parada y provoca una actualización de su señal.
La extensión consulta el hook de la página inspeccionada. No necesita cuenta, servicio remoto, permisos de host ni scripts de contenido. Usa los mismos colores que el panel dentro de la página y sigue el tema claro u oscuro de Chrome DevTools. Su versión (1.0.0) es independiente de la del paquete npm; la instalación, el empaquetado y las pruebas se describen en packages/devtools/README.md.
Protocolo#
El hook y cada captura declaran protocolVersion: 1. Los tipos públicos se exportan desde @meekojs/meeko:
DevtoolsHook,DevtoolsSnapshot,DevtoolsInstance,DevtoolsEvent,DevtoolsEventKindyDevtoolsValue;DevtoolsActivity, para los metadatos opcionales de navegación y datos;DevtoolsReadSiteyDevtoolsReadKind, para las lecturas;DevtoolsWriteSite,DevtoolsAssignmentSiteyDevtoolsEditSite, para las escrituras;DevtoolsCommandResultyDevtoolsProtocolVersion;DevtoolsSignalSite(desde1.1), para la declaración de una señal.
Desde 1.1, cada DevtoolsInstance de la captura puede llevar el campo opcional signalSites: un objeto que asocia el nombre de cada señal de signals con su declaración en el .meeko ({ file, line, column }, con línea y columna desde 1). Sólo aparece cuando la compilación de desarrollo conoce esa posición; un cliente del protocolo debe tratarlo como opcional.
El hook expone:
| Método | Efecto |
|---|---|
snapshot() |
Captura serializable de instancias, eventos recientes y selección. |
subscribe(listener) |
Avisa de cada cambio; devuelve la función para cancelar la suscripción. |
select(id) / highlight(id) |
Selecciona o resalta una instancia; null las retira. |
setBreakpoint(id, enabled) |
Activa o desactiva el punto de parada de una instancia. |
setSignal(instanceId, name, value) |
Sustituye el valor de una señal editable y devuelve { ok: true } o { ok: false, error }. |
La versión sólo cambia con incompatibilidades y, según la política de estabilidad, sólo en una versión major de Meeko. Dentro del protocolo 1 pueden aparecer campos o tipos de evento nuevos. Los dos paneles rechazan un hook o una captura con otra versión, o sin ella (runtimes anteriores a 1.0), y muestran un mensaje que indica la versión encontrada y la admitida. Nunca leen datos de un protocolo que no conocen. El panel integrado viaja en el mismo paquete que el runtime. La extensión de Chrome necesita un @meekojs/meeko que implemente el protocolo 1.
packages/meeko/src/devtools-protocol.ts es la única fuente de los tipos. La extensión usa una copia generada en packages/devtools/types/protocol.d.ts, que su tsc con checkJs comprueba sobre protocol.js y panel.js. Para regenerarla después de cambiar el protocolo:
pnpm run devtools:typespnpm run devtools:check falla si la copia queda desactualizada.
Editar señales#
Los dos paneles muestran un formulario Editar señal para la instancia seleccionada. Funciona así:
- Elige una señal editable de la instancia. Los
computedy los valores que no son señales no aparecen. - Escribe el nuevo valor en JSON y pulsa Aplicar.
- El panel valida el JSON antes de enviarlo y muestra el error junto al botón si no es válido.
- El runtime vuelve a validar el valor y lo asigna con
signal.value = value. Los efectos, la plantilla y el punto de parada reaccionan como ante cualquier otra escritura.
Reglas:
- Sólo existe en desarrollo, porque el hook no se crea en SSR ni en producción.
- El valor sustituye el valor completo de la señal; no se fusiona con el anterior.
- El valor debe ser JSON estricto:
null, booleanos, cadenas, números finitos, arrays y objetos planos, sin ciclos ni accesores. - El texto inicial del editor es la copia acotada que muestra el panel, no necesariamente el valor original completo. Si el valor real era más profundo o largo que la copia, aplicar ese texto sin cambios lo reemplaza por la versión truncada.
setSignalresponde{ ok: false, error }si la instancia ya no está activa, si el nombre no es una señal editable propia o si el valor no es JSON válido.- Si un observador lanza al propagar el cambio, el valor ya quedó escrito y la respuesta es
{ ok: false }con el error. - El evento resultante se marca como «Escrita desde DevTools» (
write: { kind: "devtools" }).
Puente del editor#
Desde 1.1, meeko dev publica los valores de las señales de las páginas abiertas para un editor local, que los muestra junto al código y puede editarlos (véase la extensión de VS Code). El puente sólo existe en desarrollo y su protocolo no forma parte del contrato público (véase la política de estabilidad).
Funcionamiento#
-
Al escuchar en loopback,
meeko devgenera un token aleatorio por sesión y escribe un descriptor propio de su puerto,.meeko/dev/editor-bridge-<puerto>.json(con permisos0600donde el sistema los admite):json{ "protocol": 1, "url": "http://127.0.0.1:4173/__meeko/editor-bridge", "token": "…", "pid": 12345, "root": "/ruta/al/proyecto", "startedAt": "2026-10-26T10:00:00.000Z" }Varios
meeko devdel mismo proyecto, en puertos distintos, conviven sin sustituirse el descriptor, y el editor se conecta a todos. Al cerrarse, cada servidor borra el suyo si sigue siendo de su sesión. Al arrancar, borra los descriptores huérfanos de procesos que ya no existen (por ejemplo, de unmeeko devterminado a la fuerza), incluido el.meeko/dev/editor-bridge.jsonúnico de las versiones de desarrollo anteriores. El puerto es el quemeeko devusa de verdad: si4173estaba ocupado y tomó4174, el descriptor eseditor-bridge-4174.json. Cuandomeeko devse reinicia porque cambió un@meekojs/meekoenlazado, el servidor nuevo escucha en el mismo puerto y escribe un descriptor con otra sesión y otro token; el editor vuelve a conectarse con él. -
El editor lee los descriptores y abre
GET <url>/eventsen cada uno, un stream SSE con los eventoshello(protocol,session,root),snapshot(todas las señales) yupdate(señales nuevas o cambiadas y las que desaparecen enremoved). Si no había otro editor conectado, las pestañas abiertas no estaban enviando valores: el servidor les pide su estado y espera a que todas respondan, o como mucho unos 300 ms, antes de enviarsnapshot; sin pestañas lo envía al momento. Lo que llega durante esa espera va en la propia instantánea, no en unupdate. -
Para editar una señal, el editor envía
POST <url>/commandcon{ "type": "setSignal", "id", "value" }. El servidor reenvía la orden a la pestaña que tiene la instancia y responde con su resultado,{ ok: true }o{ ok: false, error }, comosetSignaldel hook. -
Entre el navegador y
meeko devlos mensajes viajan por el WebSocket de HMR de Vite. El navegador sólo envía valores mientras hay al menos un editor conectado.
Cada señal lleva un id estable mientras vive la instancia (pestaña/instancia/nombre), el componente, el archivo .meeko, la línea y columna de su declaración (de signalSites) y su valor en JSON, copiado con los mismos límites que DevTools. Si la señal es el mismo objeto que otra de la pestaña, como el modelo que un hijo recibe del padre con defineModel, lleva además alias con la señal principal (id, instanceId, component, name, file y su línea y columna). La principal es la que declara la señal, no la que la recibe prestada, o la primera registrada. El editor muestra el alias junto a la principal y envía las ediciones a ella. Un valor de más de 4096 caracteres se recorta y se marca truncated; ese texto ya no es JSON válido. El navegador agrupa los cambios cada 100 ms y limita cada mensaje a unos 128 KB. El cuerpo de un comando admite como mucho 1 MB y una orden que la pestaña no responde en 5 s falla.
Seguridad#
Cada petición al puente debe cumplir todas estas condiciones; si no, responde 403 o 401 sin procesarla:
- Loopback. La conexión llega desde una dirección de loopback (
127.0.0.0/8o::1). Host. La cabeceraHostnombra un host de loopback (localhost,127.x.x.xo[::1]), lo que corta los ataques de DNS rebinding.- Sin
Origin. Una petición con cabeceraOriginse rechaza, aunque venga de loopback. Los editores son procesos locales y no la envían; una página abierta en el navegador siempre la envía, así que ninguna puede usar el puente. Además, el plugin de Vite añade**/.meeko/dev/**aserver.fs.deny, así que ninguna página puede leer el descriptor ni su token. - Token. La cabecera
Authorization: Bearer <token>lleva el token del descriptor, que se compara en tiempo constante.
meeko dev sólo arranca el puente cuando el servidor escucha en loopback o en todas las interfaces (0.0.0.0 o ::); en ese caso el descriptor apunta a 127.0.0.1. Con otra dirección no hay puente. Para desactivarlo, arranque con MEEKO_EDITOR_BRIDGE=0; desde 1.7.1, sin puente meeko dev lo dice tras la URL y lo anota en .meeko/dev/server.json (editorBridge: false y editorBridgeReason), y la extensión de VS Code lo muestra en su barra de estado:
$env:MEEKO_EDITOR_BRIDGE = "0"; npm run devSi el puente no puede arrancar, meeko dev lo informa en la consola y la aplicación sigue funcionando sin valores en vivo.
Ubicaciones en la línea de tiempo#
Las ubicaciones sólo se generan en desarrollo, cuando el compilador trabaja con hot. En producción el código compilado no incluye instrumentación y el HTML de SSR es idéntico.
Lecturas#
Cada evento de señal se puede desplegar para ver las expresiones que dependían de ella en esa instancia, con archivo, línea, columna y tipo de lectura:
| Tipo | Origen |
|---|---|
text |
Interpolación {...} de la plantilla. |
attribute |
Atributo enlazado (bind:). |
computed |
Función pasada a computed(...) en el script .meeko. |
if |
Condición if:when. |
each |
Lista each:of. |
prop |
Prop pasada a un componente hijo (name indica la prop). |
El seguimiento usa las dependencias activas de cada instancia, así que una rama que ya no lee la señal deja de aparecer. Una lectura indica dónde se usa la señal, no quién la escribió.
Escrituras#
Cada evento de señal indica también qué escribió el nuevo valor, cuando se conoce:
- Escrita en
archivo:línea:columna: es una asignación a.valuedel archivo.meeko. Cubre=,??=,||=y&&=, las asignaciones compuestas (+=,-=, …) y++/--. Vale tanto en el script como en las expresioneson:de la plantilla. - Escrita desde DevTools: es una edición hecha con
setSignal.
La atribución dura hasta el final de la tarea síncrona en curso y sólo se aplica si el valor coincide con el escrito. Una señal que cambia en cascada a partir de otra no hereda su origen.
Alcance y límites#
- El hook aparece sólo al hidratar componentes compilados por Vite en desarrollo. No se crea durante SSR ni en el build de producción.
- Una instancia aparece con el estado «Hidratado» al completar su hidratación. Al desmontarse desaparece del árbol y deja un evento de desmontaje.
- Los valores mostrados son copias acotadas y serializables, para que ambos paneles puedan transportarlos sin ejecutar código de la aplicación:
- profundidad 4, 50 elementos o claves por nivel y 500 caracteres por cadena;
- funciones, nodos DOM y accesores se resumen;
- la línea de tiempo conserva los 100 eventos más recientes.
- Algunas escrituras no tienen ubicación:
- las hechas fuera de un archivo
.meeko, por ejemplo en un módulo.tsimportado; - las que no tienen un destino estable, como
make().value = 1oitems[0].value = 1(sólo se instrumentan identificadores,thisy cadenas de propiedades con punto); - las asignaciones compuestas y los
++/--que contienen unawaito unyielddirecto. Las asignaciones simples conawaitsí se ubican.
- las hechas fuera de un archivo
- Una lectura sólo se ubica como
computedcuando se llama al identificadorcomputedcon una función literal como argumento. Un alias o una función pasada por referencia no reciben ubicación. - El punto de parada se dispara con los cambios de las señales editables del componente seleccionado. Para que el navegador pause, mantén abiertas las herramientas de desarrollo.
- DevTools no depura paso a paso cada expresión de plantilla.