Cambios de Meeko
1.9.0 — 2026-11-05#
- Credenciales configurables en los clientes API y RPC.
ClientOptions.credentialsaceptasame-origin,includeuomity conservasame-originpor defecto. Permite usar cookies HttpOnly con una API en otro origen desde el navegador sin reemplazar elfetchdel cliente. El servidor debe configurar CORS y proteger las mutaciones contra CSRF; esta opción no reenvía automáticamente las cookies de una petición SSR.
1.8.0 — 2026-11-04#
-
defineModeltambién como prop y evento. Un componente condefineModelsólo aceptaba una señal escribible, y quien venía de Vue o tenía el valor en un objeto (bind="option.props") no podía pasarlo. Ahora, sin cambiar el hijo, acepta tambiénbind:modelValueconon:update:modelValue,bind:nombreconon:update:nombrey la prop dentro debind="objeto". Con prop y evento, el padre decide cada escritura y el control vuelve al valor de la prop si no la cambia; con sólo la prop, la escritura se queda en el hijo hasta que la prop cambie; con sólo el evento, se queda en el hijo y además llama al evento. El HTML del servidor sale con el valor de la prop, que no viaja en el estado del hijo.ComponentProps,<Nombre>Props,<Nombre>Emits,meeko checky el editor los conocen, ynexttiene el tipo del modelo.modeljunto abind:modelValueuon:update:modelValueesMEEK066, y declarar la prop o el evento del modelo en el script esMEEK063. -
Un padre con
awaitya no pierde lo que se escribe. Si el manejador deon:update:modelValuedevuelve una promesa, el hijo conserva lo escrito hasta que termine la última escritura, y entonces vuelve a seguir a la prop. El cursor no salta, una escritura anterior que termina después no pisa el valor y un rechazo llega al manejador de errores en lugar de quedar sin capturar. Un manejador síncrono no cambia. -
Un modelo obligatorio que falta se explica.
meeko checky el editor decíanProperty '"default"' is missing in type '{}', con un nombre interno. Ahora dicen cómo pasarlo:model="señal"obind:modelValue="valor", y el código sigue siendoTS2741. -
bind="{ … }"en una etiqueta de componente ya no ofrece en el editor las claveson*, que luego dabanMEEK113. -
MEEK101ya no avisa de una prop leída en eldefaultdedefineModel.defineModel<string>({ default: props.initial ?? "" })la lee una vez a propósito, comosignal(props.initial). -
Atrás y adelante devuelven al lector a donde estaba. Al volver de una página corta a una larga, el navegador restauraba el desplazamiento sobre la corta y lo recortaba: de
scrollY100.000 se acababa en 6.226. Ahora cada entrada del historial recuerda su posición, que se aplica junto con el DOM de la página, también dentro de una View Transition, y espera al contenido que aún no ha llegado sin quitarle la página a quien la mueva. Una entrada con fragmento vuelve a su posición, no al ancla. Meeko ponehistory.scrollRestorationenmanualy añademeekoEntryahistory.state, donde ya no borra lo que guarde otro código. -
Un módulo de componente en un
signalo enuseStateya no llega al 500 sin aviso.signal([{ component: AppCard }])pasabameeko checky fallaba en cada render de servidor, porque el estado se envía al navegador como JSON. Ahora esMEEK136donde se escribe el valor, enmeeko checky en el editor, con la salida: guardar en el estado qué entradas se pintan y leerlas con uncomputed. Mira el valor inicial, no el tipo de la señal, así quesignal<ResolvedComponent<"AppCard"> | undefined>(undefined), que se llena en el navegador, sigue siendo válido. Un componente que nunca se renderiza en el servidor también recibe el error, aunque ahí no fallaría. El error de ejecución nombra también la señal, o la clave deuseState, y el camino hasta el módulo. -
type X = keyof typeof entriescon una constante del setup explica el problema. DabaTS2304y dejaba detrás unanycon errores confusos en la plantilla. Ahorameeko checkdaMEEK137, una sola vez, con la salida. La compilación no cambia: los tipos se borran, así que sigue funcionando en ejecución. -
Un componente genérico en una lista de módulos distintos ya no da un falso
MEEK113. Conis="option.component" bind="option.props", una prop comoformat: (item: number) => stringse comparaba conTenunknown. De un genérico en esa lista se comprueban sus nombres y sus props obligatorias. -
<component is>con módulos que llegan en los datos ybind.is="option.component" bind="option.props"dabaMEEK113: placeholder no es un atributo de la etiqueta, porque el objeto se comprobaba también como atributos de una etiqueta que eseisnunca podía ser; pasaba lo mismo con una prop, uncomputedo el parámetro de un snippet. Ahora cada clave se comprueba contra lo queispuede ser, una prop de sus componentes o un atributo de sus etiquetas. Con una lista de módulos distintos, cada entrada se comprueba contra su propio componente sin tipos auxiliares: unas props equivocadas danMEEK113enbind, con la prop que sobra o falta, en lugar de unTS2345sobre la intersección de todas. Basta con queisybindlean el mismo objeto. En ejecución no cambia nada. Véase la lista de<component is>. -
TS2367falso enis="option.kind === 'a' ? 'AppA' : 'AppB'". El código de comprobación repetía la condición deisdentro de su propia rama y TypeScript la comparaba consigo misma. Cada rama sigue estrechando sus props. -
MEEK112explica qué hacer con el nombre de un componente que llega como texto. Concomponent: "AppSelect"en los datos, el mensaje ofrece el literal en la plantilla, el módulo importado oresolveComponent, enmeeko check, en el editor y en elTypeErrorde ejecución.issigue sin interpretar nunca un texto, y el error ya no viene acompañado de unTS2345sobre el objeto debind. Además,MEEK113por una claveon*propone el aliasAppInputPropssi el componente usaComponentProps<'AppInput'>, que incluye los eventos. -
Navegar desde o hacia una página muy larga ya no la congela. Con
viewTransitions, una página de más de 4.000 elementos, de origen o de destino, navega sin transición. En una guía de 24.000 elementos con DPR alto o en un móvil, cada frame de la transición bloqueaba la página durante segundos.isTooLargeToAnimate(), nuevo en@meekojs/meeko, es la misma comprobación para quien llame adocument.startViewTransition()por su cuenta. -
@meekojs/meeko/i18npide a la vez los catálogos de una página. Cada namespace esperaba al anterior, así que cada uno costaba al visitante una ida y vuelta antes de hidratar. Ahora se piden juntos, también al navegar. No cambian la API ni el orden de fusión. Si falla un catálogo, el error sigue siendo el del primero de la lista que falla, lo anterior se conserva y lo posterior no se fusiona, aunque ya se ha pedido. -
Un schema con un import sin resolver ya no se confunde con un schema sin tipos. Con
moduleResolution: "NodeNext", un import relativo sin extensión deja el schema enany, y la generación de contratos de API, RPC y Socket pedía «tipos de Standard Schema concretos» sin nombrar la causa. Ahora dice qué import no se resuelve y remite a la ruta y amoduleResolution. -
documentHtml: el HTML de una página viaja una vez. Un dato que se pinta conhtml="..."llegaba dos veces en la primera carga: en el HTML del documento y, más pesado por el escape de JSON, en el payload de hidratación. En la guía más grande de meekojs.com eran 1,8 MiB de 3,3.documentHtml(trustHtml(html)), de@meekojs/meeko/framework, deja en el payload un marcador: la hidratación adopta el contenido sin leerlo ni reescribirlo, una navegación sigue recibiendo el valor, y volver a pintar el elemento lo pide al servidor, nunca al DOM, e inserta sólo lo que esa respuesta declara marcado; si ya no está, carga el documento de nuevo. Hay que marcarlo a propósito, porque en esa primera carga el navegador ve el marcador y no el valor: sólo sirve para lo que nada más lee. Sin la marca nada cambia. VéasedocumentHtml. -
Con cualquier
htmlWhitespaceSensitivity, también la de por defecto y en el editor sin configuración de proyecto, formatear un.meekoya no cambia lo que se renderiza. Partir<li each:of="a" each:as="b">{b}</li>en varias líneas o separar<button>a</button><button>b</button>añadía nodos de texto, también en los bordes de<template>, yHi<b> you</b>perdía su espacio. Ahora ningún espacio aparece ni desaparece en el límite entre dos nodos, en el plugin del núcleo y en@meekojs/prettier-plugin; sólo puede cambiar de forma, de un espacio a un salto de línea con sangría, que se pinta igual salvo conwhite-space: pre. El compilador no cambia, así que el HTML de una aplicación tampoco, y un archivo ya formateado conserva sus espacios y casi siempre queda igual. La salida del formato sí cambia en lo que no tenía espacios: los saltos de línea quedan dentro de las etiquetas (>{b}</li\n>). Véase editors.md. -
El texto tras el salto de línea inicial de
<pre>,<listing>o<textarea>ya no pierde caracteres. El compilador lee el texto de la fuente, y parse5 lo situaba después del primer carácter que producía:<textarea> &x</textarea>se pintaba;x, y `
-
Formatear un
.meekoconserva también los espacios junto a un<snippet>, el contenido de<pre>y<textarea>y un}}en un texto, en el plugin que usa el servidor de lenguaje, igual que en@meekojs/prettier-plugin. Sus cambios de salida están en el registro de ese paquete. -
Un
meeko devque arranca ya no recarga las páginas abiertas. Cada arranque reescribía las entradas de.meeko/generated, aunque no cambiaran, y Vite recargaba toda página abierta en ese proyecto, también las de otromeeko dev: se perdía su estado, como un formulario recién enviado. Ahora sólo se escribe una entrada que falta o que cambió. -
addPublicFile(path, contents, { headers }). Un módulo no podía dar cabeceras a un archivo público: enmeeko devesos archivos no pasan por la aplicación, yserver.headerses de cada web. Ahora el archivo las lleva enmeeko devy en los adaptadores con servidor, también si su nombre necesita codificarse en la URL (font test.woff2,fuente-ñ.woff2). Un patrón deserver.headersque alcance esa ruta sustituye sólo las cabeceras que fija, sin distinguir mayúsculas de minúsculas, y conserva el resto; viajan en el nuevoAppManifest.publicFileHeaders. El adaptadorstaticno las escribe. -
useHeaden una página de error fija su título. El títuloErrorentraba comoheadde la ruta, que gana a todo, así queuseHead({ title })enapp/error.meekoo+error.meekono tenía efecto, ni en el SSR ni al navegar: las webs que traducían el título del 404 mostrabanError. AhoraErrores el título por defecto de esas páginas, por encima de sus layouts y por debajo de ellas. Elheaddel payload de una URL sin ruta deja de llevarlo. -
modelen un<input type="range">con una señal numérica. DabaTS2322, y un envoltorio con un rango tenía que escribirbind:valueyon:input, que no recogen lo que el usuario movió antes de hidratar. Ahora una señal que guarda un número recibe números, y una con texto sigue recibiendo texto. Además, un valor que sólo ha saneado el navegador (un rango que recorta suvalueamax, un correo sin espacios) ya no cuenta como una edición anterior a la hidratación: la señal conserva lo que le dio la aplicación. -
Lo que el usuario movió antes de hidratar ya no se pierde en una lista. Si hidratar volvía a ejecutar el
modelde un control, como un radio dentro de uneach:cuyas filas reciben un objeto nuevo, se escribía en él el valor anterior de la señal y lo editado se perdía en silencio, en el control y en el modelo. Ahora se lee primero lo que hizo el usuario. -
modelen una casilla o un radio deja pasar elvaluedel spread.<input type="checkbox" model="on" bind="rest">descartaba elvaluederest, así que el formulario enviabaonen lugar de ese valor. El modelo sólo controlachecked. En un control de texto, el modelo sigue decidiendovalue. -
meeko devya no termina si su primer servidor falla en plena reconstrucción de un paquete enlazado. La escritura sólo contaba tras 100 ms sin eventos, así que durante una ráfaga el supervisor creía que no se había escrito nada y salía con el código del servidor. Ahora compara los archivos antes de decidir, y vuelve a arrancarlo cuando el paquete deja de escribirse. Un evento que no escribió nada, como una lectura en Windows, sigue sin contar: el servidor roto termina con su código. -
meeko devcon un Meeko enlazado ya no avisa ni se reinicia sin un build. El vigilante dedistllamaba achange()ante unrename, un evento sin nombre, un archivo que desaparecía o un error del vigilante, aunque nada se hubiera escrito: conMEEKO_LINK_WATCH=0salía «Meeko enlazado cambió» sin que nadie compilara, y el supervisor del modo normal reiniciaba un servidor sano. Ahora, pasado un periodo de calma, compara una huella de las rutas, fechas de modificación y tamaños dedisty depackage.jsoncon la última conocida, y sólo si difiere avisa o reinicia; sigue valiendo borrar y recreardist, y un build que escribe mientras se compara se detecta con su propio evento. La huella inicial se toma al empezar a vigilar, antes de lanzar el servidor; en el modo sin supervisor, un build escrito después de arrancar el proceso y antes de vigilar se avisa una vez. Vale también para los demás paquetes enlazados que vigila el supervisor. -
La primera navegación desde un 404 de una URL con ruta. Tras hidratar la página de error de
notFound(), o el404.htmlde un host estático en una ruta dinámica, el navegador creía tener los layouts de esa ruta en lugar de los de la página de error: podía pintar la página siguiente dentro del layout equivocado. Ahora parte de los layouts que hidrató. -
meeko checky los autoimports de navegador en un.tsdeapp/.resolveComponent,useHead,useSeoMeta,useTheme,useLayout,useForm,useStorage,ClientErroreisClientErrordabanTS2304en un módulo deapp/composablesoapp/utils, aunque ahí están permitidos y funcionan: el módulo también entra en el programa detsconfig.server.json, donde esos nombres no existen.meeko checkya no da por buenos sus diagnósticos en ese proyecto, y el código de servidor que los usa sigue fallando.tsc -p .meeko/tsconfig.server.jsona mano los sigue mostrando. -
.mtsen los autoimports. Sus exports se recogían y se declaraban, pero el import generado apuntaba a.js, así que usarlos dabaTS2307enmeeko checkymeeko buildy un500enmeeko dev. Ahora funcionan como un.ts. Un.ctssólo sirve para tipos: un valor conexportsigue dandoTS1287. -
Button/Index.meekoes<Button>. Una carpeta con suIndex.meeko(oindex.meeko) se llamaba<ButtonIndex>. Ahora se llama como la carpeta, conButtonProps,ButtonEmits,ButtonSnippetsy demás, enapp/components, en los layers y en las carpetas de los módulos con su prefijo, y también enresolveComponenteis.Index.meekoen la raíz sigue siendo<Index>. El nombre anterior y sus tipos funcionan en1.xcon el avisoMEEK134enmeeko check,meeko buildy el editor, y se retiran en2.0. SiButton.meekoyButton/Index.meekoexisten a la vez, también en fuentes distintas, nada cambia de archivo: conservan<Button>y<ButtonIndex>, con el avisoMEEK135en elIndex.meeko. Tampoco cambia de archivo un nombre con unIndexanidado: conButton/Index.meekoyButton/Index/Index.meeko,<ButtonIndex>sigue siendo del primero y el segundo sigue siendo<ButtonIndexIndex>, conMEEK135. Fuera de un.meeko,meeko checkno avisa del nombre anterior:ButtonIndexPropsen un.tscompila sinMEEK134. -
El editor formatea igual que
prettier --check.@meekojs/meeko/prettier, el plugin que usa el servidor de lenguaje, ordena conmeekoSortClasseselclassde una etiqueta de componente y las cadenas debind:class, y añademeekoClassFunctions(tv,clsx,cn…), como@meekojs/prettier-plugin. Antes, al guardar quedaban sin ordenar y la comprobación de Prettier fallaba justo después. Quien ya usemeekoSortClassesverá ordenados esos valores al guardar; con"off"no cambia nada. ConhtmlWhitespaceSensitivity: "ignore", tampoco añade ya un espacio visible entre un texto y un elemento en línea (<code>x</code>.). -
El
404de una página en el servidor lleva layout.notFound()en SSR respondía la página de error sola: sin cabecera, pie,<main>ni enlace para saltar, aunque en una navegación de cliente sí iba dentro del layout. Ahora da el mismo documento que una URL sin ruta, con los layouts raíz, y se hidrata; con un+error.meekomás cercano, va dentro de los layouts de la página, sin hidratar. Si un layout falla al pintarlo sigue saliendo la página de error sola, y con otros errores no cambia nada, porque un layout puede ser lo que ha fallado. -
layoutPropscon<script server>de nivel superior:datatipado yexport const layoutProps.layoutProps: ({ data }) => …teníadatacomounknown, aunque la guía decía que son los exports; ahora lo son también para los tipos, si las opciones son un objeto literal sin spread. Además, un dato para el layout sólo llegaba allí exportándolo a la página, así que viajaba dos veces. Conexport const layoutProps, comohead, las props se tipan con sus valores, se comprueban contra el layout, también el heredado (MEEK_PAGE_LAYOUT_PROPS), y no entran en los datos de la página. Con las dos formas a la vez esMEEK072. -
Cambio incompatible de tipos, excepción aprobada:
<Nombre>Propsya no incluye los callbacks dedefineEmits. Un envoltorio de un componente no se podía escribir:BaseButtonPropstraíaonClick, que chocaba con sudefineEmits(TS2344) o llegaba abind="rest"(MEEK113). AhoraBaseButtonPropsson sólo las props,BaseButtonEmitses el mapa dedefineEmits, yComponentProps<"BaseButton">y elComponentPropsdel módulo siguen con todo. Quien lea o escribaonXenBaseButtonPropscambia ese tipo porComponentProps<"BaseButton">. Sólo cambian los tipos, no el SSR ni la hidratación. Un paquete publicado antes no hay que republicarlo: Meeko lee lo que exporta su.d.meeko.ts, así que su<Nombre>Propssigue siendo suComponentPropsy no tiene<Nombre>Emits. Véanse excepciones aprobadas y la migración. -
bindcon el resto de una unión de props en un componente. El resto llegaba aplanado al hijo (to?: string; href?: string), y un reenvío correcto dabaTS2345oTS2379. Ahora se conservan las variantes, y la exclusión entre ellas llega al padre. -
defineEmitscon un mapa importado no deja sus callbacks en el resto. CondefineEmits<BaseButtonEmits>()el compilador no conoce los eventos, así queonClickseguía enrestybind="rest"lanzabaMEEK113en el SSR cuando el padre usabaon:click, con los tipos correctos. Ahora toda claveonXqueda fuera de la enumeración depropsy del resto en ese caso;props.onXsigue leyéndose. -
Un mensaje útil para
TS2312.interface Props extends Xcon unas props que son una unión da, enmeeko checky en el editor, una propuesta:type Props = X & { … }. Con unas props que son un objeto ya funcionaba, también condefineEmits: lo impide la unión, no los callbacks. -
meeko devsobrevive a una reconstrucción del workspace. Al reconstruir, el reinicio llegaba mientras otro paquete enlazado, comopackages/ui, escribía sudist, fallaba conEl componente externo … necesita una declaración adyacente .d.meeko.tsy se quedaba parado hasta el siguiente cambio de@meekojs/meeko. Ahora vigila también los demás paquetes enlazados que usan Meeko: un reinicio pendiente espera a que se estabilicen, un servidor que falló vuelve cuando cambian y, si se escribieron después de arrancarlo, reintenta hasta 6 veces, con esperas que se doblan. Si no se escribió nada, el fallo es real y se informa como antes. Compilar esos paquetes no reinicia un servidor sano. -
defineExposeen un componente genérico ya no rompe su.d.meeko.ts. La declaración incluía todo lo que devuelve el setup, y los parámetros degenericdentro de un argumento de tipo, como los dedefineEmitsydefineSnippets, salían comoT_1yT_2, que no existen (TS2304). Ahora sólo queda lo que leeComponentExposed. El tipo expuesto sigue siendoanyen la declaración, como en cualquier componente. -
class,bind:classybind="obj"se combinan siempre. Con un spread, el último declassybind:classsustituía al otro, y lo mismo constyle, aunque sin spread se unían y la documentación decía que se combinan. Ahora dan una sola clase en cualquier orden, en SSR, hidratación y actualizaciones, en un elemento y en<component is>con etiqueta. -
Aviso
MEEK133: el mismo atributo como texto y conbind:.id="a" bind:id="b"escribía los dos en el HTML, el navegador se quedaba con el primero y, en ese orden, la hidratación fallaba conHydration mismatch. Ahora valebind:iden cualquier orden, sin ese fallo, ymeeko check,meeko buildy el editor lo avisan.classystyleno cuentan, porque se combinan; en un componente sigue siendoMEEK090. -
Un
aria-currentpropio gana a la ruta en<MeekoLink>.bind:aria-currentescribía el atributo junto al de la ruta, y en un enlace activo el servidor y el cliente no coincidían:Hydration mismatch ... (attribute aria-current), y la página dejaba de hidratar. Unaria-currentescrito salía dos veces y uno enbindse ignoraba. Ahora los tres ganan a la ruta en SSR, hidratación y actualizaciones, también en<component is="'MeekoLink'">y enresolveComponent("MeekoLink"):falsequita el atributo ynulloundefineddejan decidir a la ruta, que sigue comparando elhrefresuelto. Sinaria-currentpropio nada cambia. Unaria-currenty unbind:aria-currenten el mismo enlace sonMEEK105. Véase<MeekoLink>. -
<component is>con un valor de ejecución pasaaria-*ydata-*a un componente. Como en su etiqueta desde1.8. Antes sólo llegaban a un elemento:<component is="form.value" bind:aria-label="…">, con el componente deresolveComponent, los perdía. Si el componente no declara el atributo,meeko checkdaTS2353en él, como en su etiqueta, en lugar deMEEK112. -
<MeekoLink>aceptaparamsenundefineden una ruta estática. Un componente que reenvíabind:params, comoUIButton,UILinkoUILinkCard, dabaTS2379enmeeko checkymeeko buildsi la aplicación tenía una sola ruta y era estática, como una aplicación nueva. Unosparamsreales en una ruta estática siguen siendo un error. -
<select>con contenido rico. parse5 eliminaba sin avisar<button>,<selectedcontent>,<div>,<legend>y<span>dentro de un<select>, una opción con<span>publicaba</span><span>como texto visible y una con<svg>dabaMEEK001. Ahora el compilador lee el<select>como los navegadores actuales y conserva ese contenido. La salida de un<select>sin contenido rico no cambia. Como el navegador cierra el<select>al encontrar<input>u otro<select>, dentro de él esas dos etiquetas son ahoraMEEK008; antes compilaban a un<select>vacío seguido de sus opciones sueltas. -
La hidratación ya no se engancha al clon de
<selectedcontent>. Chromium y WebKit copian ahí la opción elegida, con susdata-meekoy sus marcadores, y la hidratación encontraba antes la copia: con una opción estática enlazada y elegida al cargar, señales y eventos iban al clon y no a la opción. -
El router da a su View Transition el tipo
meeko-route.html:active-view-transition-type(meeko-route)distingue una navegación de una transición propia de un componente. Sin tipos de transición en el navegador, la navegación se anima igual, con el callback solo. -
on:commandrecibe unCommandEvent.on:command="(event: CommandEvent) => …"dabaTS2345, porque los mapas de eventos de TypeScript dicenEvent. Vale parameeko check, el editor y su hover. -
interestforen los datos HTML. El atributo (<a>,<area>,<button>) y los eventosinterestyloseinterestconstan como experimentales, según@mdn/browser-compat-data: aún no están en el estándar. -
Un
<component is="'select'" bind:value>explica qué hacer. Sigue siendoMEEK112, porque<select>no tiene ese atributo, pero el mensaje indica que se marque la opción conbind:selectedo se escriba<select>en la plantilla conmodel. -
Los saltos de línea que formatean un
<snippet>ya no son contenido. Prettier deja<snippet name="code" params="text">en varias líneas, y esos espacios llegaban adonde el hijo pinta el snippet: dentro de una frase,<code>.meeko</code>.se veía como.meeko ., y entre elementos en línea aparecía un hueco. Ahora el compilador quita, en los dos extremos de un<snippet>, el texto que sólo tiene espacios y al menos un salto de línea, en SSR, hidratación y actualizaciones. Un espacio en la misma línea (<snippet name="a"> <b>x</b> </snippet>) sigue siendo contenido, igual que el texto. Un snippet que contaba con ese espacio para separarse de sus vecinos debe escribirlo en una sola línea o separarlos con CSS. Véase<snippet>. -
sitemapXmles público. Una aplicación que sirve el sitemap desde una base de datos (sitemap: false) tenía que escribir a mano el XML y susxhtml:link. Ahora@meekojs/meeko/frameworkexportasitemapXml(origin, urls), la misma función con la quemeeko buildescribesitemap.xml: rutas o URL conlastmod,changefreq,priorityyalternates, con el XML escapado y los mismos errores dehreflangypathname. No usa APIs de Node, así que también funciona en Workers. Véase Sitemap servido desde una base de datos. -
Resolvedores de enlaces:
app.resolveLinky<MeekoLink bind:resolve>. Con rutas localizadas,<MeekoLink to>necesitaba el ID con el prefijo (/en/guide), así que los enlaces de una web multilingüe acababan en<a href={i18n.localePath(…)}>. Ahora un plugin o módulo registraapp.resolveLink(({ to, href, hreflang }) => …)y cada<MeekoLink>(la etiqueta, la rama'MeekoLink'de<component is>y el componente deresolveComponent("MeekoLink")) pasa suhrefpor él en el SSR, el prerender, la hidratación y la navegación del cliente;aria-currentse calcula con elhrefresuelto.hreflang(escrito, conbind:hreflango enbind) llega al resolvedor y sigue en el<a>;bind:resolve="false"deja elhrefdehref(). Sin resolvedores, el HTML no cambia. Un enlace estático sigue sin enlace reactivo dehrefen la hidratación. Tipos nuevosLinkResolveryLinkResolveInputen@meekojs/meeko/frameworky la propresolveenMeekoLinkProps. -
meeko.extendRoutesy el hooksitemap:resolvedpara los módulos. Un módulo no podía añadir rutas que reutilizaran el archivo de una página, así que@meekojs/i18nobligaba a duplicar páginas enapp/pages/[locale]/…para servir/en/…. Ahorameeko.extendRoutes((routes) => [...])transforma el manifiesto descubierto (aplicación, layers y módulos) antes de que lo usen el SSR, el navegador, el prerender, el sitemap, los tipos de rutas y el editor: la función devuelve la lista completa, se aplica en el orden de los módulos y su resultado se valida y ordena como un manifiesto (una colisión de URL es un error con el nombre del módulo). El hooksitemap:resolvedrecibe las URL desitemap.xmlantes de escribirlo; cada URL admitealternates: [{ hreflang, pathname }], que se escriben como<xhtml:link rel="alternate" hreflang>con el espacio de nombresxhtml(unhreflango unpathnameinválidos detienen el build). Tipos públicos nuevos:SitemapAlternateySitemapResolvedUrl.meeko checkcomprueba cada ruta añadida contra sus propiosparamsy laslayoutPropsde la página que comparten una sola vez por archivo (antes, una página con<script server>que heredaba un layout dabaTS2300 Duplicate identifierenpage-contracts.server.ts), yusePage()de su<script server>se tipa con losparamsde la ruta que corresponde a su archivo, como en el editor, sea cual sea el orden de las rutas. Véase El objetomeeko. -
'MeekoLink'y nombres de componente en<component is>. Un botón que es ruta, enlace externo o<button>necesitaba dos ramas conif:when, porque<MeekoLink>es una etiqueta del compilador yis="x ? MeekoLink : 'a'"eraTS2304. Ahora un literal en PascalCase dentro deisnombra<MeekoLink>o un componente importado, autoimportado ('UICard') o declarado en el script, y se combina con etiquetas HTML:is="to !== undefined ? 'MeekoLink' : href !== undefined ? 'a' : 'button'". El compilador resuelve cada nombre como su etiqueta, sin promesas: una regiónif:whenpor nombre, cuya condición es el camino de?:hasta su literal, y otra para las etiquetas, con el SSR, la hidratación y las actualizaciones deif:when. Cada atributo va sólo a las ramas que lo admiten:to,params,searchymatcha'MeekoLink'(con suhreftipado yaria-current, nuncahref), los atributos HTML a las etiquetas que los aceptan y las props a los componentes.meeko checky el editor comprueban cada rama tras su condición, que la estrecha, cada componente con sus props sin tratar como sobrante una prop de otra rama, y danMEEK112en un atributo que no admite ninguna rama. Las etiquetas van en minúscula y los componentes en PascalCase; un nombre desconocido,'MeekoOutlet','MeekoTeleport'o un valor junto a nombres es el error nuevoMEEK132, yref:con varias ramas,MEEK052. El editor completa los nombres dentro deisy muestra el componente en el hover del literal.iscon un valor de componente o con etiquetas HTML no cambia. Véase Nombres de componente enis. -
resolveComponent("MeekoLink"). El registro de componentes incluyeMeekoLink, que carga<MeekoLink>como componente desde@meekojs/meeko/builtins/link, con una variante de props por ruta (to, losparamsde esa ruta,search,matchy los atributos de su<a>salvohref) y el eventoclick; un componente del proyecto llamadoMeekoLinkconserva el nombre. Los componentes de módulos (UI*) ya estaban en el registro. La documentación explica cuándo usar un literal enis(nombre conocido al escribir la plantilla, en el HTML de SSR) y cuándoresolveComponent(nombre en ejecución o carga diferida). Véase Registro de componentes. -
Los tipos que generan los módulos tipan
meeko.config.ts. Las declaraciones demeeko.addTypeTemplate(.meeko/types/modules.d.ts) entran en la comprobación demeeko.config.tsdemeeko checkymeeko build, y en.meeko/tsconfig.config.jsondel editor. Un módulo puede tipar sus opciones con lo que genera:@meekojs/iconrechazaicon: { include: ['lucide:chek'] }conTS2820y la sugerencia"lucide:check". -
bind:viewBoxen SVG conserva su caso. El parser HTML sólo ajustaba el caso de los atributos SVG estáticos, así quebind:viewBoxobind:preserveAspectRatioen un<svg>se escribían en minúsculas (viewbox): el SVG los ignoraba y la hidratación dabaHydration mismatch (attribute viewbox). Ahorabind:en SVG y MathML usa las mismas tablas que los atributos estáticos (viewBox,preserveAspectRatio,gradientUnits,definitionURL…), en SSR, hidratación y actualizaciones. -
Los estilos de
<style scoped>ya no cambian al hidratar. El SSR incrustaba el CSS de los componentes delante de las hojas globales, y el cliente (<style>de Vite enmeeko dev, el CSS de los chunks de la ruta en producción) lo volvía a añadir al final del<head>: una regla global de igual especificidad (.page .cardfrente a.cardcon ámbito) ganaba en el primer pintado y perdía al cargar el JavaScript, y los estilos «saltaban» al recargar. Ahora los<style>en línea van al final del<head>, después deapp/styles/main.css, de las hojas de los módulos y delheaddedocument, en el documento, el streaming, el 404 y el prerender, con la misma CSP (nonce o hashes) y deduplicación. Enmeeko devcada<style>lleva eldata-vite-dev-idde su módulo y el cliente de Vite lo adopta en lugar de duplicarlo; la HMR lo sustituye, así que una regla borrada del<style>deja de aplicarse (antes la copia del SSR la mantenía hasta recargar). La página de error de un fallo del servidor, que no se hidrata, se servía sin el CSS de sus<style>; ahora lo incrusta. Una página que dependía de que la hoja global ganara a igual especificidad (sólo antes de hidratar) debe subir la especificidad de la regla global. Véase PostCSS y Tailwind. -
El formato del editor respeta
.prettierignorey.gitignore. El servidor de lenguaje (editor.formatOnSavey «Formatear documento» de VS Code) formateaba también los.meekoque el proyecto excluye de Prettier, por ejemplo las plantillas en una sola línea dePopover,SelectyThemeTogglede@meekojs/ui, donde los espacios entre etiquetas son texto. Ahora un archivo ignorado no recibe ediciones, como con la CLI de Prettier, y tampoco pasa al formateador anterior. Se leen los archivos de exclusión de la carpeta del.meekoy de las superiores hasta la raíz del repositorio (o la del disco), con los patrones de cada uno relativos a su carpeta; en un workspace no confiable también se respetan, porque sólo se leen datos.formatWithPrettierdevuelveignored: truecon el texto original. Véase Formato con Prettier. -
<div />: los elementos sin contenido se pueden cerrar con/>, como en Vue.<div />,<span class="spinner" if:when="loading.value" />,<li each:of="items.value" … />,<textarea name="nota" />,<td />,<option />o<my-element />dabanMEEK001, porque el parser HTML abre<div/>y mete dentro todo lo que sigue. Ahora cualquier elemento HTML no vacío o personalizado escrito con/>equivale a su forma con etiqueta de cierre: los nodos que siguen son sus hermanos, compila exactamente igual y el SSR escribe HTML válido (<div></div>), con la misma hidratación, actualizaciones y liberación. Los elementos vacíos (<input />), SVG y MathML no cambian.<template>,<script>,<style>,<html>,<head>,<body>y los nombres desconocidos siguen necesitando su etiqueta de cierre (MEEK001, con un mensaje que lo dice), y<td /></td>esMEEK008. El editor,meeko check, el parser de ESLint y Prettier, que conserva la forma escrita, lo entienden; la regla optativameeko/html-self-closingde@meekojs/eslint-pluginunifica el estilo.@meekojs/meeko/parseexportaisSelfClosableElement. Véase Elementos sin contenido. -
devtools: trueenmeeko.config.ts, como en Nuxt.meeko devmonta el panel de Meeko DevTools en la página sinimport()nimountDevtoolsPanel()en la aplicación;{ position: "bottom-left" }elige la esquina y{ panel: false }lo apaga. Por defecto,false. Nunca llega ameeko build,meeko startni a las páginas prerenderizadas. Antes,devtools: truedaba «clave desconocida "devtools"»; un valor no válido detiene la carga con un mensaje que nombra la opción, ymeeko checklo tipa (MeekoDevtoolsConfig). Un módulo conmeta.configKey: "devtools"deja de cargarse: dele otra clave. -
El panel de DevTools con los colores de Meeko, minimizable. El panel dentro de la página y el de la extensión de Chrome usan los tokens del sitio de Meeko (acento azul, superficies neutras, Inter si está disponible) en claro y oscuro: el panel integrado sigue al sistema o al
data-themede la página, y la extensión, al tema de Chrome DevTools. El panel integrado se minimiza a una pastilla con el logo y el número de eventos nuevos (botón accesible oAlt+Mayús+M), se mueve a otra esquina arrastrando su asa o con las flechas, y recuerda ambas cosas enlocalStorage.mountDevtoolsPanelaceptaposition. -
Módulos por su nombre, como en Nuxt.
modules: ['@meekojs/i18n', '@meekojs/ui']registra los módulos sin importarlos y cada uno lee sus opciones de su clave (i18n: { … },ui: { … }, la de sumeta.configKey). Un paquete se resuelve por su subpath./modulesi lo exporta (@meekojs/i18n,@meekojs/meeko/i18n) y, si no, por su raíz. La clave queda tipada sin imports:meeko dev,meeko check,meeko build,meeko routesy el editor de un workspace de confianza escriben.meeko/types/config.d.ts, que importa los tipos de cada módulo demodulesy carga su ampliación deMeekoConfig, y.meeko/tsconfig.config.json, al que apunta eltsconfig.jsondemeeko init. El editor completa dentro demodules: ['…']las dependencias con la palabra clavemeeko-module(nuevos tiposMeekoModuleNamesyMeekoModuleName) y documenta la clave en el hover (defineMeekoConfigrecibeT & MeekoConfig).meeko checkcomprueba siempremeeko.config.tscon esos tipos (antes, sólo cuando la carga fallaba), así quei18n: { defaultLocale: 1 }esTS2322. Una cadena que no resuelve sugiere el módulo instalado más parecido; un paquete sin módulo y dos módulos con la mismaconfigKeydan errores claros; la clave de un módulo instalado que falta enmoduleses el avisoMEEK127. El módulo integrado@meekojs/meeko/i18ndeclaraconfigKey: 'i18n'. -
Obsoletos: el módulo importado y
[módulo, opciones]enmodules. Siguen funcionando en1.xcon el avisoMEEK126enmeeko dev,meeko build,meeko checky el editor, que indica la cadena y la clave que los sustituyen; se retiran en2.0. Véase Actualizar de 1.7 a 1.8. -
meeko checky el editor avisan de unonCleanupque rompe el SSR. Una limpieza registrada en el nivel superior del<script>, en uneffecto en un composable que el setup llama también se ejecuta en el servidor, al liberar la raíz tras el render; si usawindow,document,cancelAnimationFrame,ResizeObserveru otra API que sólo existe en el navegador, fallaba en Node conReferenceError. Ahora es el avisoMEEK128, con la corrección rápida «Mover aonMounted» (o dentro delonMountedexistente). No avisa si la limpieza está protegida contypeof windowoimport.meta.env.SSR, ni dentro deonMountedo de un manejador, y el checker de TypeScript distingue una variable localwindowde la global. Además,MEEK129marca unonCleanuptras unawaitde su funciónasync, ymeeko checkde un proyecto daMEEK130para unonCleanupal evaluar un módulo deapp/,shared/oserver/. Son avisos en1.xy serán errores en2.0; véase onCleanup. -
definePropsse puede desestructurar sin perder la reactividad.const { to, type = 'button', disabled = false, label = '', class: className, ...rest } = defineProps<Props>()dabaMEEK015. Ahora, como en Vue 3.5, el compilador escribe cada lectura de un nombre desestructurado como una lectura de la prop, en el script, la plantilla,computed,effect, manejadores y closures; un parámetro, una variable local, uneach:aso un parámetro de snippet con el mismo nombre no cambia. Los defaults del patrón equivalen al argumento de defaults (la prop es opcional para el padre y nuncaundefineddentro, y un objeto o un array se crea por instancia); con los dos a la vez esMEEK124.class: classNamerenombra y...restes una vista de sólo lectura que sigue al padre, sin las props desestructuradas ni los manejadores dedefineEmits, lista parabind="rest". Las uniones discriminadas se estrechan en el script y en la plantilla (if:when="to !== undefined"da losparamsde esa rama), enmeeko checky en el editor, donde el hover y el completado de cada nombre muestran su tipo y renombrarlo cambia sólo la variable local ({ label: text }). Asignar un nombre desestructurado o borrarlo condeleteesMEEK125, antes de generar código;typeof labelen un tipo también lee la prop; copiarlo en el setup, el avisoMEEK101; un nombre que no es una prop,TS2344. La desestructuración anidada, de array, con clave calculada o conletsigue siendoMEEK015, con un mensaje que explica la alternativa. SSR, hidratación y HMR no cambian. Véase Desestructurar las props. -
El editor muestra los valores por defecto de las props. En el padre, el hover de una etiqueta de componente, el de cada prop (
size?: "sm" | "md" = "md") y el detalle del completado de sus atributos muestran el default, escrito en el argumento dedefinePropso en la desestructuración. El módulo compilado exporta el tipoComponentDefaultscon el texto de cada default, que también llevan las declaraciones.d.meeko.ts. Además, el editor ya trata como opcional para el padre una prop con default en la desestructuración, comomeeko check, y calcula los defaults por variante de una unión. -
Constantes en los valores por defecto de
defineProps. Un default (en el argumento o en la desestructuración) puede leer constantes de nivel superior del script cuyo valor sólo usa literales, imports y otras constantes así (const SIZES = ['sm', 'md'] as const,size: SIZES[1]); se evalúan otra vez con los defaults, por instancia. Lo que depende del setup (una prop, una señal,let, una función del script, el valor de una llamada, oarguments,new.targetysuperde la función del setup) esMEEK123, con la prop, la variable y el motivo; antes eraTS2304: Cannot find name. Las variables que declara el propio default (locales, bloques,catch, parámetros, accessors) no cuentan. Los tipos de los defaults se calculan por variante de una unión de props: un default de una prop que una variante declaranever(typeen la rama de enlace de un botón polimórfico) ya no permite al padre pasarla en esa variante. Véase Constantes en los valores por defecto. -
Un default en el discriminante de una unión ya no permite omitirlo en todas las variantes. Con
type Props = { kind: "a"; a: string } | { kind: "b"; b: string }ykind = "a"(odefineProps<Props>({ kind: "a" })), el padre podía escribir<Child b="hello" />: el componente recibía{ kind: "a", b: "hello" }, que no es ninguna variante, y fallaba en ejecución. Ahora un default literal, o una constante de nivel superior con un literal, hace opcional su prop sólo en las variantes que admiten ese valor, enComponentProps, las declaraciones.d.meeko.ts,meeko checky el editor; ese padre es un error de tipos y debe pasarkind="b". Un default que no es literal (un import,KINDS[0]) sigue siendo opcional en todas las variantes, y es el error nuevoMEEK131sobre la prop si alguna variante que la exige no admite su valor. Véase Constantes en los valores por defecto. -
Valores de los atributos HTML, del estándar. Meeko conoce los valores que admite cada atributo (
typede<button>,hidden,closedbyde<dialog>,aria-pressed…), de los datos HTML que generascripts/html-data.ts. Los tipos públicosAttributeValues<Tag, Name>yAttributeValueOf<Tag, Name>, y los valores deElementAttributes<Tag>, los sugieren al completado sin dejar de aceptar cualquier valor, así que no rompen código de1.x.meeko check,meeko buildy el editor dan el avisoMEEK122cuando el valor es un literal fuera de ellos (<button type="sumbit">,bind:type="'sumbit'"), también en<component is>con etiquetas; unstringcalculado no avisa. En2.0será un error. Véase Valores de los atributos. -
hidden="until-found"conbind:.bind:hiddentratabahiddencomo booleano y escribíahidden=""con cualquier texto, así quebind:hidden="'until-found'"perdía su valor. Ahora un texto no vacío se escribe tal cual, en SSR y en el navegador;truesigue escribiendohidden=""y un valor falso lo quita. La lista de atributos booleanos sale de los datos HTML del estándar:itemscope,headingreset,typemustmatch,alpha,scoped,seamlessy losshadowroot*se escriben vacíos con un valor verdadero. -
Datos HTML generados desde las especificaciones. La lista de atributos, copiada a mano de los datos de MDN del editor, se había quedado atrás: no tenía
<dialog closedby>,commandnicommandfor,<template shadowrootmode>niwritingsuggestions. Ahorapnpm -F @meekojs/meeko html-datagenerasrc/shared/html-data/desde el HTML Living Standard del WHATWG, WAI-ARIA 1.3 (con DPUB-ARIA y Graphics ARIA), Referrer Policy y@mdn/browser-compat-data: etiquetas y elementos vacíos, atributos globales y de cada elemento con sus valores enumerados (closedby:any,closerequestynone;popover:auto,manualyhint; los 22 de<input type>), si son booleanos y si son experimentales u obsoletos, losaria-*con sus valores, los roles y los eventos. Los módulos que necesita el runtime (nombres de etiquetas y de atributos) van separados de los detalles para el compilador y el editor, que no llegan al navegador. Lo que Meeko 1.7 aceptaba y ninguna especificación recoge ya (<body alink>,contextmenu…) se sigue aceptando, marcado como obsoleto;hiddendeja de ser booleano porque admiteuntil-found. Las especificaciones se guardan como instantáneas enscripts/html-data/, así que la generación es reproducible sin red, ytests/html-data.test.tsfalla si los módulos no están al día. Véase Datos HTML de Meeko. -
aria-*ydata-*pasan a los componentes como props.<AppButton aria-label="Cerrar" data-state="open" bind:aria-busy="saving.value" />dabaMEEK032(«los nombres con guiones no son props»); ahora cada atributoaria-*odata-*en minúsculas, estático o conbind:, es una prop con ese nombre en las etiquetas de componente y en<component is>con un componente. El hijo las declara (por ejemplo,defineProps<Props & ElementAttributes<'button'>>()) y una sin declarar esTS2353en el atributo.Aria-Labelydata-meeko*dan unMEEK032propio. Véase Props estáticas. -
propsya no enumera los manejadores dedefineEmits. El objetopropsllevaba en ejecución los callbacks de los eventos (onClick) aunque su tipo no los declarase, así queconst { label, ...rest } = propsconbind="rest"lanzabaMEEK113: bind="…" no admite la clave "onClick". AhoraObject.keys,in, la propagación y el resto los omiten cuando el script escribe los eventos (tipo literal, ointerfaceotypelocales);props.onClickyemitsiguen igual. Véase Eventos de componente. -
<MeekoLink>yhref()sin rutas generadas:paramses opcional. Cuando el proyecto no tiene.meeko/types/routes.d.ts(RouteIdesstring: una librería como@meekojs/uio una app antes demeeko check),<MeekoLink to="/anything">dabaTS2345(Property 'params' is missing) ybind:params="props.params"conRecord<string, string> | undefined,TS2322, y había que escribirparams ?? {};href("/x", { search })fallaba igual. Ahora, sin rutas generadas,paramses opcional y admiteRecord<string, string>oundefined, en<MeekoLink>y enhref(). Con rutas generadas no cambia: una ruta dinámica exige susparamsy una estática los prohíbe.@meekojs/uiquita ese rodeo deUIButtonyUILink. Véase<MeekoLink>. -
<MeekoLink>acepta una unión de rutas con susparams. En un componente conto: RouteId,<MeekoLink bind:to="props.to" bind:params="props.params" bind:search="props.search">dabaTS2412, porque la comprobación sólo admitíaparamscuandotoera una sola ruta y no aceptabasearchopcional. Con una unión,paramspuede ser los de alguna de las rutas, o ninguno; con una sola ruta la comprobación no cambia. La documentación muestra cómo tipar las props para que el padre pase cada ruta con sus parámetros y un botón que es ruta, enlace externo o<button>. Véase Un botón que es ruta, enlace o botón. -
Snippets cuyo nombre sale de un genérico:
cell-<clave>yheader-<clave>. Elnamede<snippet>admite, además de un identificador, segmentos unidos por guiones (cell-email,header-created-at; regla/^[A-Za-z_$][\w$]*(?:-[\w$]+)*$/, sin guion final ni doble); antes eraMEEK061.defineSnippets<T>acepta claves de un tipo mapeado con template literal sobre los parámetros de tipo del componente, como{ [K in keyof Row & string as `cell-${K}`]?: [row: Row, value: Row[K]] }, que antes dabaTS2344. En el padre,meeko check,checkAppy el editor comprueban cada snippet con el tipo que infieren las props:valuetiene el tipo de su columna, un nombre inexistente esTS2353(marcado sobre el nombre) y unosparamsde más,TS2345. El editor completa los nombres (las claves concretas o, si no se conocen, el prefijo comocell-$1) y muestra su firma en el hover. En el propio componente, una intersección con un tipo mapeado se tipa miembro a miembro, así quesnippets.cellconserva su tupla; un nombre calculado (snippets[`cell-${key}`]) funciona en ejecución, pero TypeScript no lo tipa sobre un genérico y se lee de una vista con conversión. SSR, hidratación y actualizaciones no cambian. Véase Snippets dinámicos tipados por un genérico. -
{{ expr }}dice que Meeko interpola con una sola llave. Por costumbre de Vue,{{ props.label }}o{ {props.label} }en el texto de la plantilla dabanMEEK020: ',' expected(las llaves interiores se leen como un objeto literal) y{{ name }}compilaba y pintaba[object Object]. Ahora una interpolación del texto que empieza y termina con llave y no compila, o es un objeto literal abreviado, daMEEK020con «Meeko interpola con una sola llave: {expr}, no {{expr}}» y el enlace a cómo escribir llaves como texto, en el compilador,meeko checky el editor, que marca la interpolación entera y ofrece «Convertir a {expr}», «Mostrar las llaves como texto» ({"{hola}"}) y «Corregir todas las del archivo».{{no es un alias: un objeto literal con sus claves ({{ a: 1 }}) sigue compilando sin aviso. Véase Acciones rápidas. -
El editor completa
bind:<atributo>y ordena los valores de las directivas por lo que declara el script. En un elemento,bindsólo ofrecíabindybind:; ahora ofrece cada atributo de la etiqueta conbind:(bind:type,bind:href,bind:closedby, los globales,aria-*ybind:data-…), con el tipo que espera la expresión (bind:type: 'submit' | 'reset' | 'button') y los obsoletos tachados, desde los datos HTML de Meeko; los valores estáticos (type="…",role="…",aria-pressed="…") salen de los mismos datos.ref:ofrece los nombres deuseElementRefdel script, los que no tienen etiqueta primero. Dentro de un valor van primero las declaraciones que encajan con la directiva y su camino hasta el valor: los manejadores con la firma del evento enon:, las señales escribibles del tipo del control o del modelo enmodel, los booleanos enif:when, las listas eneach:of, los campos del elemento (item.id) eneach:key, lo asignable a la prop en una etiqueta de componente, los valores del atributo enbind:type, los snippets dedefineSnippets(tambiénsnippets['cell-…']) en<render snippet>y las declaraciones del script antes que los globales en{…}.model="",{}ysnippet=""vacíos también se completan. Al escribirbind→bind:→bind:class, cada diagnóstico dice lo que falta ybind:se marca sobre sí mismo, no sobre la etiqueta. Véase Completado de atributos. -
El editor completa las props, eventos y modelos de una etiqueta de componente. En
<AppCard |el servidor de lenguaje sólo ofrecía las directivas, así que el editor completaba con lo que encontrara (sugerencias de palabras o de IA, a vecesitem={}al estilo de JSX, que Meeko no admite). Ahora ofrece lo que declara el hijo, con su tipo y su JSDoc, antes de las directivas, e inserta cada atributo con su valor y el cursor dentro, como la extensión de HTML y Vue: una prop de texto comotitle="…"(y abre sus valores si es una unión literal), una booleana comodisabledobind:disabled="…", una que admite texto y otros tipos de las dos formas, y las demás comobind:item="…"; los eventos dedefineEmitsy las propsonXcomoon:save="…", y los modelos comomodel="…"ymodel:open="…". Trasbind:,on:omodel:sólo se ofrece ese canal y se reemplaza el nombre entero;model:defaultya no aparece yref:no ofrece directivas. Si el nombre ya tiene un=detrás, se inserta sólo el nombre, también en los atributos y eventos de un elemento. Dentro de las comillas vacías debind:,on:,modeloif:whenen una etiqueta de componente no se completaba nada; ahora se completan las expresiones, también al escribir la comilla de apertura en cualquier etiqueta. Los elementos de TypeScript reemplazan exactamente el identificador que completan ({ active:na }). Véase Completado de atributos. -
Una comparación dentro de
{...}en el texto de la plantilla compila.{row.id < active.value.id},{a<b},{n <= 2}, un genérico ({identity<number>(x)}) o una cadena con etiquetas ({"<b>" + x}) dabanMEEK001, porque el parser HTML leía ese<como el inicio de una etiqueta, y Prettier dejaba la plantilla sin formatear sin avisar. Ahora el contenido de cada interpolación del texto es una expresión, con las mismas reglas que ya usaba el compilador (cadenas, template literals, comentarios y regex): compilador, SSR, hidratación, editor, ESLint y Prettier la leen igual. Un<fuera de las llaves sigue siendo HTML, y<dentro de ellas sigue funcionando. -
Una interpolación que combina
<con&ya no se trunca. Las referencias de carácter de una expresión del texto se decodificaban pasando la expresión otra vez por el parser HTML, sin enmascarar sus<:{a<b&&c}compilaba sin error como{a}(el SSR y la hidratación pintaban1en lugar detrue), y{ok && "<b>"}o{ok && `}<b>`}dabanMEEK020. Ahora se decodifican sin interpretar marcado, igual en el compilador,meeko checky el editor. El significado no cambia:<,&y las referencias numéricas se decodifican como en un atributo y en Vue ({x && y}esx && y;{'<'}pinta<), y los diagnósticos tras una referencia (sintaxis, tipos,MEEK057) señalan su posición en la fuente, no la del texto decodificado. Véase{expresión}. -
Un contrato de API o RPC admite composiciones simples de schemas de
shared/.output: v.array(snapshotSchema)dabaoutput debe referenciar un schema importado de shared/y obligaba a exportar unsnapshotListSchema. El generador de clientes no ejecuta el handler y nombra cada schema por su módulo, así que el schema sigue viniendo deshared/. Ahora, una expresión de llamadas, propiedades y literales cuyos nombres son imports deshared/o de un paquete se copia, con esos imports, a.meeko/types/contract-schemas/, de donde sólo se lee su tipo. Otra expresión (una función, una variable local) se rechaza con el motivo, un ejemplo y la referencia a la documentación; los mensajes conservan el prefijo… debe venir de shared/. Los schemas de Socket siguen siendo exports deshared/, porque el navegador los importa para validar. Véase Schemas públicos. -
El runtime de servidor sabe dónde está el proyecto:
ServerRuntimeContext.root. El build mueve el código de servidor a.meeko/output/…, así queimport.meta.urlya no servía para leer archivos del proyecto, yprocess.cwd()sólo es la raíz cuando la CLI lanza el proceso. Ahora la fábrica dedefineServerRuntimereciberoot, la ruta absoluta de la raíz: enmeeko dev, la del proyecto; en las salidas de Node, Bun y Deno (también conmeeko start, connode .meeko/output/node/index.mjsdesde cualquier carpeta y en el prerender), la carpeta tres niveles por encima de la salida, o la nueva variableMEEKO_ROOTsi la salida se despliega fuera del proyecto. En Cloudflare Workers esundefined. Nueva opciónrootdecreateServerApp. Véase Runtime de servidor. -
meeko checkymeeko buildya no escriben declaraciones fuera de.meeko/. Un componente de un módulo enlazado fuera de la app (packages/ui/dist/app/components/Button.meekoen un monorepo) recibía una declaración generada en<app>/packages/ui/dist/app/components/, porque su ruta relativa escapaba de.meeko/types; con más niveles podía llegar a escribirse junto al.d.meeko.tspublicado. Ahora sólo los componentes del proyecto reciben declaración generada; los de un módulo externo usan la suya, como ya exigía el registro de componentes. Las carpetas creadas antes pueden borrarse. -
El SSR y el prerender incrustan el CSS de los componentes ya procesado. El
<style>en línea de cada componente llevaba el CSS antes de PostCSS (un@applyo un@referencede Tailwind, un@utilities), que sólo se aplicaba al cargar el JavaScript. Ahora el exportcssde un componente compilado por el plugin de Vite es el CSS procesado (módulo?meeko-style=…&inline&lang.css), enmeeko dev, en el build de Node, en el prerender, enstaticy en Cloudflare, y la HMR del cliente reaplica ese mismo CSS. En el bundle de cliente de producción, donde nada lo lee,csssigue siendo la cadena del compilador: no añade una segunda pasada de PostCSS por componente ni una copia del CSS procesado a los chunks de página. Un host que no procesa CSS, como Vitest sincss: true, resuelve ese módulo como una cadena vacía: entoncescsses la cadena del compilador, como antes de 1.8, y no queda vacío. Nueva opciónstyleModuledecompile(). Las media queries de rango (@media (width < 40rem), elmax-sm:de Tailwind) llegan intactas al<style>en línea: sólo se escapa la secuencia</style, que cerraría el elemento, en lugar de todo<. CSP, deduplicación y orden no cambian. -
El editor completa los autoimports del proyecto sin escribir un import. Al completar un export de las carpetas de autoimports (
app/utils,app/composables,shared/…,imports.appDirs…) en el<script>o la plantilla de un.meeko, también enapp/app.meeko, el servidor de lenguaje ofrecía el auto-import de TypeScript, que añadíaimport { formatPrice } from "../utils/price.js"; si el archivo no estaba en el programa de TypeScript (un proyecto sintsconfig.json, o con unincludeque no lo cubre, donde sólo lo importa quien lo usa) no lo ofrecía, y un nombre que coincide con un global del DOM, comonavigation, sólo aparecía como el global. Ahora se ofrece siempre que el build lo autoimporta, sin import, con su archivo (app/utils/price.ts) como detalle, y sustituye al global. Un componente bajoserver/(una plantilla de email), que sólo se ejecuta en el servidor, completa además los autoimports de ámbitoserver, como los resuelven su código virtual y el grafo SSR. Los nombres autoimportados de@meekojs/meekosiguen importándose desde su entrada pública. Véase Autoimports y TypeScript. -
El editor escribe los imports relativos sin
.js. El completado con auto-import, «Add import» y la corrección de un autoimport ambiguo escribían./settings/context.js; ahora escriben./settings/context, que aceptan Vite y la resoluciónBundlerdeltsconfig.jsonde Meeko. Un proyecto conmoduleResolution(omodule)node16onodenextconserva la extensión. -
export defaultno se autoimporta.export default function formatPrice()se registraba como el export nombradoformatPrice, y su uso sin import fallaba con un import que no existe (TS2614en el editor y enmeeko check). Ahora sólo se autoimportan los exports nombrados, como dice la documentación: useexport function formatPrice(). -
classybind:class(ostyleybind:style) en el mismo elemento escriben un solo atributo. El SSR escribíaclass="card" class="ring-4"y la hidratación fallaba; ahora SSR, hidratación y actualizaciones escribenclass="card ring-4", con las declaraciones dinámicas destyletras las estáticas. También en<component is>con etiqueta. -
Una clave de
createContextque provee un plugin ya no se pierde enmeeko dev. La app de servidor demeeko devcargaba una sola vez lo que su entrada importa estáticamente (server/runtime.tsy los plugins de servidor y universales), y las páginas en cada petición. Al editar el módulo de la clave (app/utils/modal.ts, importado porapp/plugins/modal.ts), Vite lo evaluaba otra vez para las páginas, pero el plugin seguía publicando la clave anterior:injectfallaba conMissing Meeko context: modalhasta reiniciarmeeko dev, y una edición del propio plugin no se aplicaba en el servidor. Ahora una edición que alcanza esos módulos sin pasar por un módulo con HMR propio (un componente) crea la app de nuevo, como el navegador recarga la página; lo que la app guarda en memoria (sesiones deMemorySessionStore, cachés) empieza vacío, como al añadir una página. Las ediciones de páginas, componentes y de lo que sólo ellos importan siguen siendo actualizaciones en caliente, sin crear la app. -
El runtime de un módulo de Meeko instalado en
node_modulesse evalúa una sola vez. Un módulo publicado llegaba a Vite por dos caminos: el nombre del paquete de sus autoimports (@meekojs/ui/runtime) y las rutas de los archivos que registra (addPlugin,addComponentsDir…), que importan sus vecinos en relativo. Enmeeko devel optimizador de dependencias empaquetaba aparte el primero para el navegador (node_modules/.vite/deps), y el build de servidor lo dejaba externo mientras empaquetaba el plugin: el mismo archivo se ejecutaba dos veces, cada copia con su clave decreateContext, y la hidratación (en dev) o el SSR (trasmeeko build) fallaban conMissing Meeko context. Ahora los paquetes instalados que contienen archivos de los módulos se compilan como código fuente, igual que un paquete enlazado: se añaden assr.noExternaly aoptimizeDeps.exclude. Una dependencia CommonJS de uno de esos paquetes debe ir enoptimizeDeps.include("mi-modulo > dependencia-cjs").Symbol.for(...)ya no hace falta como rodeo. Véase createContext. -
Editar una página ya no rompe en
meeko devla clave decreateContextque provee un plugin. Al editar una página, un layout o un archivo deapp/utils,app/composablesoapp/plugins, al añadir o quitar un componente o al cambiar la hoja global,meeko devrecargaba el navegador e invalidaba todos los módulos del servidor. La app de servidor conservaba sus plugins, pero las páginas volvían a evaluar el módulo de la clave, que ya no era la que el plugin había publicado: cada página respondía 500 conMissing Meeko contexthasta reiniciarmeeko dev. Con@meekojs/uibastaba guardar una página para que todas fallaran enuseModalHost(@meekojs/ui/modal.manager). Ahora esa invalidación conserva lo que la app importa al crearse (server/runtime.ts, los plugins de servidor y universales y sus importaciones); las páginas y lo demás se evalúan de nuevo. Además, un script de un módulo de Meeko que el navegador cargó (runtime.js, un composable) recarga la página, comoapp/utilsdel proyecto: al reconstruir un paquete enlazado, la actualización en caliente de un componente que llegaba antes de la recarga importaba una segunda copia de la clave y la hidratación fallaba con el mismo error. Los componentes de los módulos y sus hojas de estilos siguen con HMR. -
La CLI trabaja siempre desde la raíz del proyecto.
meeko dev,build,start,preview,check <directorio>,routes,prerenderycheck-appcambian el directorio de trabajo del proceso a la raíz indicada antes de cargar.env, la configuración, PostCSS y Vite. Corrige que@tailwindcss/postcss, que detecta las fuentes desdeprocess.cwd(), recorriera la carpeta de lanzamiento (con el cwd en la raíz de un disco, el disco entero) en lugar del proyecto. La ruta relativa del argumento se resuelve contra el cwd original, y el supervisor dedevrelanza su hijo con la ruta absoluta y con las rutas de--env-filey--env-file-if-existsresueltas, escritas con=o separadas por un espacio (node --env-file .env.local).checkyroutessiguen imprimiendo rutas absolutas, como en 1.7. Las APIs programáticas no cambian el cwd del proceso: llámelas con el cwd en la raíz. Véase PostCSS y Tailwind. -
Migración de controles a UI (1.8).
<MeekoPopover>,<MeekoSelect>y<MeekoThemeToggle>quedan obsoletos con avisoMEEK121y etiqueta tachada en el editor; funcionan en 1.x y se retiran en 2.0. Sus reemplazos sonUIPopover,UISelectyUIThemeTogglede@meekojs/ui0.2. Nueva API pública estableuseThemeToggle()y tiposThemeToggleStateyThemeToggleSystemen@meekojs/meeko/framework. El sistema de temas permanece en core. Véase Actualizar de 1.7 a 1.8. -
<MeekoPopover>emite cada apertura y cada cierre aunque el navegador fusione sus eventostoggle. Si el menú se cerraba y se reabría (con la flecha,show(), el modelo opopovertarget) antes del eventotoggledel cierre, el navegador fusionaba los dos en uno, el menú reabierto no recibía el foco yon:toggleno emitía nada. Ahoraon:toggleemitefalseytrueen orden y el foco entra en el menú. Cambio visible: unhintabierto y cerrado muy rápido (un paso rápido del puntero) ahora emitetoggletrueyfalse, donde antes no emitía nada.UIPopoverde@meekojs/ui0.2 se comporta igual. -
CSP de ejemplo con
img-srcydata:. Un documento sin icono propio lleva<link rel="icon" href="data:,">para que el navegador no pida/favicon.ico; con una políticadefault-src 'self'sindata:enimg-src, como los ejemplos de la documentación, el navegador registraba una violación de CSP en cada página. Los ejemplos de CSP con hashes incluyen ahoraimg-src 'self' data:y explican la alternativa de declarar un icono propio. El HTML no cambia. -
Diagnóstico y observabilidad.
meeko doctor [directorio] [--json]inspecciona versiones, resolución de paquetes, LSP disponible y artefactos generados sin ejecutar la configuración ni leer.env. DevTools incorpora una línea de actividad de navegación y datos, con fases, duración y origen, sin registrar URLs resueltas, claves de caché ni payloads. Véanse Doctor y DevTools. -
Compilador y cliente separados por responsabilidades. El análisis de expresiones y el scoping de CSS tienen módulos propios; el navegador separa validación de payloads y transporte deferred/stream del commit transaccional. Se conservan sourcemaps, SSR, hidratación, rollback, CSRF y View Transitions.
-
Calidad del workspace. La CI prepara los paquetes antes de lint, verifica todos los paquetes públicos y aplicaciones, y prueba consumidores de tarballs fuera del workspace. Las declaraciones de componentes UI se reubican con imports portables. Nuevos benchmarks reproducibles de compilación, SSR, hidratación, bundle y LSP, con presupuestos y comparación por entorno; véanse Pruebas y Rendimiento.
-
View Transitions nativas en navegación cliente.
viewTransitions: trueenmeeko.config.tsactiva la API del navegador alrededor del commit de DOM y head, después de cargar módulos y datos. Admite historial, nombres compartidos mediante CSS y excepciones condata-meeko-view-transition="false"oapp.navigate(to, { viewTransition: false }). La hidratación inicial, movimiento reducido y navegadores sin soporte conservan la navegación habitual. Las transiciones omitidas no bloquean la navegación ni permiten commits de rutas abortadas. Véase View Transitions nativas. -
IP del cliente tras Cloudflare Tunnel:
MEEKO_TRUST_PROXY=cloudflare. Tras Cloudflare →cloudflared→ Traefik sintrustedIPs,X-Forwarded-Forsólo traía la IP decloudflared, así que conMEEKO_TRUST_PROXY=1todos los visitantes compartían el contador derateLimit. El valorcloudflarehace quecontext.clientAddress,clientAddress(request)yrateLimitleanCF-Connecting-IP(oTrue-Client-IPsi falta) sólo cuando la conexión y cada salto deX-Forwarded-Forvienen de las redes de la nuevaMEEKO_TRUSTED_PROXIES(CIDR o IP; por defecto, loopback, RFC 1918 yfc00::/7). Una conexión de fuera, un salto público (alguien que llega directamente a Traefik con la cabecera inventada) o un valor que no es una única IP devuelven la IP de la conexión o la de ese salto. UnMEEKO_TRUSTED_PROXIESno válido detiene el arranque. En Workers no cambia nada. Véase Cloudflare Tunnel y Traefik.
Seguridad#
- Toda mutación comprueba
Originpor defecto, también sin sesión. Hasta1.7, una peticiónPOST,PUT,PATCHoDELETEdedefineApiRoute, o una llamada dedefineRpc, sin cookie de sesión sólo comprobabaOriginsi la ruta declarabaorigin: "required", así que otra web podía ejecutar una ruta anónima que aceptara un formulario o JSON enviado comotext/plain, sin preflight de CORS. Ahora responde403 INVALID_ORIGINsi suOriginfalta, esnullo no es el de la aplicación (publicOriginoMEEKO_PUBLIC_ORIGIN) ni uno deSessionManager.allowedOrigins, la misma regla que con sesión;Sec-Fetch-SiteyRefererno sustituyen aOrigin.origin: "required"equivale al valor por defecto.origin: "none"es la salida por ruta para webhooks y APIs públicas (insegura: úsela con una firma o un token): desactiva la comprobación en las peticiones sin sesión, también en un formulario nativo (un webhookapplication/x-www-form-urlencodedcomo los de Twilio, que hasta1.7recibía403), mientras que una con sesión sigue exigiendoOriginy el token CSRF.createLocalCallsenvía ahora el origen de la aplicación comoOrigin, como los clientesapi.*yrpc.*del SSR, en lugar de reenviar el de la petición original, así que una mutación local desde una página pedida conGETfunciona; con sesión sigue necesitando el token CSRF. Los navegadores ya envíanOriginy los clientesapi.*yrpc.*del SSR envían el de la petición, así que las aplicaciones web no cambian; un webhook, un script o una app nativa sinOriginrecibe403. Es un cambio incompatible aprobado como corrección de seguridad; véase Actualizar de 1.7 a 1.8.
[1.7.1] - 2026-10-29#
Correcciones#
meeko devdice cuándo no publica valores en vivo. ConMEEKO_EDITOR_BRIDGE=0(también en el.env),meeko devno escribía el descriptor del puente y no avisaba, y la extensión de VS Code dejaba «Meeko live» desconectado sin explicar por qué. Ahora escribe tras la URL, en la salida de errores,Valores en vivo del editor desactivados (MEEKO_EDITOR_BRIDGE=0), o(meeko dev escucha en <dirección>, fuera de loopback)cuando escucha en una dirección concreta fuera de loopback, y lo anota en su registro,.meeko/dev/server.json, coneditorBridge: falsey el motivo eneditorBridgeReason(también si el puente falla al arrancar). La extensión 1.7.1 lee ese registro y muestra «Meeko live: desactivado en meeko dev» con el motivo. Nuevo tipoDevelopmentServerRecorden el contrato del puente. Véase Valores en vivo.- El servidor de lenguaje ya no abre ventanas de consola en Windows. Al editar un
.meeko, el editor abría una ventana de consola cada pocos segundos: la comprobación de los contratos de páginas y la del proyecto (framework/page-contracts-check.tsy las dos llamadas deframework/check-project.ts) lanzabantypescript/bin/tsc, el lanzador en JavaScript, connode, y ese lanzador abretsc.exesinwindowsHide. Ahora las cuatro llamadas al compilador de TypeScript, también la demeeko check, pasan por un único helper que en Windows ejecutatsc.exedirectamente, oculto, y en los demás sistemas sigue usando el lanzador con Node. Un test falla si otro archivo vuelve a lanzarbin/tsc. modelen un checkbox aceptaboolean | undefined. El campo de una clave opcional con valor por defecto, comoconsent: v.optional(v.boolean(), false), tiene el tipo de la entrada del schema,boolean | undefined, y<input type="checkbox" model="form.fields.consent">(omodel="props.field.value"con unFormField<boolean | undefined>) dabaTS2322enmeeko check,meeko buildy el editor. Ahora la señal de un checkbox puede serSignal<boolean>oSignal<boolean | undefined>:undefinedse pinta desmarcado, en el SSR y en el navegador, ychangeescribe siempre un booleano. El campo deuseFormsigue teniendo el tipo de la entrada (FormField<boolean | undefined>): un componente de campo para checkbox debe declarar ese tipo, porque tiparlo con la salida rompería los schemas cuya entrada admiteundefinedhasta que el usuario elige. Véase Un checkbox opcional con valor por defecto.
Cambios#
meeko devmuestralocalhosten vez de la IP.meeko dev,meeko start,meeko previewy las salidas de Node, Bun y Deno escribenhttp://localhost:4173cuando escuchan en loopback (127.0.0.1,::1) o en todas las interfaces (0.0.0.0,::); hasta ahora escribíanhttp://127.0.0.1:4173. Sólo cambia el texto: la dirección de escucha sigue siendo127.0.0.1por defecto,serveNode(...).urlyserveDeno(...).urlno cambian y, sinMEEKO_PUBLIC_ORIGIN, Socket sigue tomando la dirección de escucha como origen y acepta los otros nombres de loopback, así que una página abierta enlocalhostconecta igual. ConMEEKO_HOST=0.0.0.0(o::, oserver.host) se añade una líneaRed: http://<IP>:4173por cada dirección de las interfaces de red, al estilo de Vite; la salida de Deno sólo las lista con--allow-sys=networkInterfacesy nunca pide el permiso. Un host concreto se muestra tal cual. La primera línea sigue siendo la URL local, sola: un script que buscabahttp://127.0.0.1:<puerto>en la salida debe buscarhttp://localhost:<puerto>o leer esa primera línea.meeko devconMEEKO_HOST=::toma ahora[::1]como origen de Socket, en lugar del[::]con el que rechazaba las páginas de loopback. Elserver.mjsque creameeko inittambién escribehttp://localhost:4173(y sigue escuchando sólo en127.0.0.1), igual que su README. Véase URL que se muestra.
Documentación#
- El campo
returnde un formulario nativo, con la/sin codificar. Se comprobó quereturn=/contacto(como lo envíacurl -d),return=%2Fcontacto(como lo envía el navegador), el mismo campo enmultipart/form-datay una URL absoluta del mismo origen vuelven todos a/contacto, y que//,\, los esquemas y los segmentos de punto siguen volviendo a/; un test lo fija. Ellocation: /que se veía al probarlo concurldesde Git Bash en Windows no venía de Meeko: MSYS convierte el argumentoreturn=/contactoenreturn=C:/Program Files/Git/contacto, un esquemac:, que se rechaza como debe. Formularios sin JavaScript lo explica, conreturn=%2FcontactooMSYS_NO_PATHCONV=1como alternativas.
[1.7.0] - 2026-10-29#
Los cambios de comportamiento que se notan al actualizar desde 1.6, con lo que hay que revisar en cada uno, y cómo adoptar lo nuevo se recogen en Actualizar de 1.6 a 1.7.
Añadido#
- CSP y cabeceras en los adaptadores con servidor.
server.contentSecurityPolicyyserver.headers, que hasta ahora sólo tenía el adaptadorstatic, existen también conadapter: "node","bun","deno"y"cloudflare"(tipoServerResponseConfig). En producción, la aplicación completa la política de cada respuesta: una página prerenderizada lleva los hashes de su índice (scriptHashesystyleHashes, que ahora también llegan enPrerenderedPage) y se sigue sirviendo desde su archivo; una página SSR,"stream", de error o 404 lleva un nonce nuevo por respuesta enscript-src; una página SSR concache, los hashes del código en línea que escribe Meeko; y el JSON de API, RPC y navegación (todo/api/, también un endpoint concontentType: "response"),default-src 'none'; frame-ancestors 'none'. Los hashes sólo cubren lo que Meeko escribe fuera de la página (el<head>, el payload, la entrada ydocument.bodyEnd): un<script>o un<style>dentro de#appo de un teleport, que puede llegar conhtml="…"o con contenido de usuario, queda bloqueado como con el nonce. El nonce y los hashes van ascript-src-elemystyle-src-elemsi la política las tiene. Meeko nunca añade un nonce ni un hash a una directiva con'unsafe-inline'(por ejemplo, elstyle-srcque necesitan los atributosstylede Shiki), salvo que tenga'strict-dynamic', con el que el navegador ya ignora'unsafe-inline'. La página que muestra el resultado de un formulario nativo lleva el nonce de su respuesta.server.headersadmite, además de las cabeceras globales, patrones de ruta ("/admin/*": { "X-Robots-Tag": "noindex" }, tipoServerHeaders), y nunca sustituye una cabecera que la respuesta ya trae. Los Workers lo aplican igual, con Web Crypto; sincontentSecurityPolicy, el build de Cloudflare deja fuera del Worker el analizador HTML de los hashes (parse5), unos 240 KB. El manifiesto de la aplicación lleva los valores enAppManifest.contentSecurityPolicyyAppManifest.headers. Véase CSP y cabeceras en los adaptadores con servidor. - Caché de páginas SSR.
definePage({ cache: { etag, maxAge, staleWhileRevalidate, memory } })(tiposPageCache,PageCacheContextyPageMemoryCache).etag(ctx)se calcula antes del loader con losparamsysearchvalidados; Meeko lo combina con el tema, el build (AppManifest.buildId, nuevo en cadameeko build) y el tipo de respuesta en unETagdébil y responde304sin loader ni render, para el documento y para el payload de navegación (application/meeko+jsony elndjsonde"stream"), conVary: Accept, Cookie.memoryguarda el HTML y el payload en una LRU por ruta y proceso (1000 entradas y 32 MiB por defecto) que se invalida al cambiar elETag. El middleware de ruta, isomórfico y de servidor, se ejecuta siempre antes del304. Nunca se cachea una petición con la cookie de sesión o conAuthorization, ni una respuesta que pone una cookie (en desarrollo, con un aviso);"stream"sólo usa elETag; la caché sólo se aplica en producción. En una ruta con schemasearch, la clave dememoryusa elsearchvalidado, así que un parámetro no declarado (?utm_source=…) no llena la LRU. Véase Caché de páginas SSR. - Formularios sin JavaScript contra la API:
form.defineApiRoute({ body, response, form: true, handler })acepta, además de JSON, un<form method="post">nativo (application/x-www-form-urlencodedymultipart/form-data), validado con el mismo schema debody. Un nombre repetido o terminado en[]es una lista; los campos llegan como texto y, si el schema rechaza el de algunos, se validan una vez más con su lectura tipada (""→undefined,"on"/"true"→true,"false"→false, un número decimal →number), así quev.number()yv.boolean()sirven sin conversiones propias; un schema asíncrono se ejecuta una sola vez, sin ese segundo intento. Un envío correcto responde303 See Otheraform.redirect(result, context), al camporeturno a la página deReferer; un error (de validación, unApiError,401,429…) vuelve con303a esa página. La ruta nunca sale del sitio://,\, un esquema o segmentos de punto se sustituyen por/, con las comprobaciones dePOST /_meeko/theme. El resultado viaja en una cookie de un solo uso (__Host-meeko-form,HttpOnly, 60 s), que la página lee y borra mientras se renderiza (también si está prerenderizada): el código, el estado, losissuesy sólo los valores de los campos que listaform.echo(por defecto ninguno; nunca archivos ni nombres comopasswordotoken). Todo envío nativo exige unOriginpermitido y, con sesión, el token CSRF en el campo_csrf; esos dos fallos responden403sin redirección.rateLimitse cuenta antes de leer el cuerpo.form.maxBytescambia el límite de 1 MiB,form.files: trueadmite archivos, que el schema recibe comoFile(sin él,400 FILES_NOT_ALLOWED), yform.echolista los campos cuyo valor vuelve a la página tras un error. Un endpoint sinformsigue respondiendo415a un formulario. Nuevo tipoApiFormOptionsen@meekojs/meeko/server. Véase Formularios sin JavaScript. useForm({ action })pinta el resultado del envío nativo. Conaction: "/api/feedback", el SSR de la página a la que vuelve el navegador muestraerrors,erroryfailurede un error (con los valores deform.echoen sus campos), osubmittedtras un envío correcto, y la hidratación conserva lo mismo; con JavaScript,form.submitsigue enviando JSON con la misma definición.form.csrfTokenda el token de la sesión para el campo oculto_csrf.debounceMsyenabledenuseApiyuseRpcQuery.{ debounceMs: 200 }espera a que la entrada se quede quieta y hace una sola petición por ráfaga, cancelando la que estaba en curso; durante la esperapendingya valetrueydataconserva el resultado anterior (salvo conkeepPrevious: false).{ enabled: () => q.value.length >= 2 }(también un booleano o una señal) no pide nada mientras valefalse, cancela la petición en curso, dejapendingenfalsey conservadata. En el SSR, un recurso conawaitse pide enseguida, sin espera; uno desactivado no puede esperarse. Véase Búsqueda mientras se escribe.rateLimiten SQL, sin Redis.SqlSessionDrivergana el método opcionalrateLimit(key, windowMs, now), que cuenta una petición con una sola sentencia atómica y devuelve la fila{ count, windowEndsAt }(tipoSqlRateLimitRow). Si el driver lo implementa,SqlSessionStore.rateLimitdelega en él y los límites derateLimitde API y RPC se comparten entre todas las instancias, con la misma semántica que Redis y la memoria: ventana fija, reinicio al terminar y429conRetry-After(los segundos que faltan, como mínimo 1); si la base de datos falla, la petición se rechaza con500.keyson los 32 bytes del SHA-256 de la clave del contador, que ya resume la ruta y el sujeto, así que la tabla nunca guarda una IP en claro. framework.md documenta la tablaUNLOGGEDde referencia para PostgreSQL (key byteacomo clave primaria,window_ends_atycount) y suINSERT … ON CONFLICT DO UPDATE … RETURNING. SinrateLimiten el driver todo sigue igual: contadores en la memoria de cada instancia y un aviso en producción, que ahora indica cómo compartirlos.D1SessionDrivercuentarateLimiten D1. Con la opciónrateLimitTable, el driver de Cloudflare D1 implementarateLimitsobre esa tabla, cuyo esquema devuelveD1SessionDriver.rateLimitSchema(table?)(por defecto,meeko_rate_limits), ydeleteExpired()borra también los contadores cuya ventana terminó. Es opcional porque necesita una migración: sin la opción, el driver no cambia.clientAddress(request, env?)en@meekojs/meeko/server. Da la IP del cliente donde no hayRequestContext, como endefineServerMiddlewareo en un handler deaddServerHandler, con la misma política quecontext.clientAddressyrateLimit: la IP de la conexión (el socket en Node, Bun y Deno;CF-Connecting-IPen Workers) o, conMEEKO_TRUST_PROXY, la entrada deX-Forwarded-Forque añadió el proxy de confianza más externo. Sinenv, aplica el entorno de la aplicación que atiende la petición, también en la petición nueva que un middleware pasa anext(); así ya no hace falta leerX-Forwarded-Fora mano, que cualquier cliente puede falsificar. Véase IP del cliente tras un proxy.- Rutas dinámicas con SSR en el sitemap:
sitemapEntries.definePage({ sitemapEntries })declara las URL de la ruta ensitemap.xml: una lista de{ params, lastmod?, changefreq?, priority? }(tipoSitemapEntry) o una función, síncrona o asíncrona, quemeeko buildevalúa una vez después de prerenderizar, en el servidor de Node intermedio, conSitemapEntriesContext(env, elprocess.envdel build, yprerendering: true). Hasta ahora el sitemap sólo conocía las páginas prerenderizadas y las rutas sin parámetros, así que una ruta/docs/[slug]con SSR no aparecía. Cada<url>puede llevar<lastmod>(unDateo un texto W3C Datetime),<changefreq>y<priority>, validados en el build. En una rutarender: "prerender",sitemapEntriessólo completa sus páginas prerenderizadas con esos campos, y una entrada sin página detiene el build;sitemap.excludeynoindexsiguen ganando. Una páginarender: "client"no lo admite (error de tipos y de descubrimiento). Vale para todos los adaptadores, Node ystaticincluidos. Véase Rutas dinámicas en el sitemap. context.servicestipado sin conversiones.meeko check,meeko devy el editor generan.meeko/types/services.d.ts, que infiere el tipo deservicesdel export por defecto deserver/runtime.ts(el objeto compartido, o lo que devuelve la función por petición) y amplía la nueva interfazGeneratedServicesde@meekojs/meeko/server.RequestContext.servicesyservicesdeServerRouteMiddlewareContextpasan a serAppServicescuando no se indica otro tipo, así quecontext.services.dbcompila sinas unknown as MisServiciosenusePage().context, API, RPC, Socket y el middleware de ruta de servidor. Un tipo escrito (RequestContext<MisServicios>) oanysiguen ganando; sin runtime en TypeScript o sinservices, el tipo sigue siendounknown.RequestContextcompara por su tipo de servicios, así que el contexto de un test con servicios falsos (createRequestContext(request, { db: falso }, session)) se sigue aceptando en una ruta sin tipo escrito. Un runtime que usadefineServerRuntimeautoimportado también se infiere en el editor: la declaración lee una copia con los autoimports escritos (.meeko/types/server-runtime.ts, con@ts-nocheck). Nuevos tipos:AppServices,GeneratedServicesyServerRuntimeServices. Véase Tipos de los servicios.
Cambios#
- Con
server.contentSecurityPolicy, una página prerenderizada se sirve desde su archivo aunque el host paseDocumentOptions.nonce; sin ella, como antes, un nonce del host la renderiza con SSR. - Toda respuesta de
createServerAppconSet-Cookiey unCache-Controlpublico cons-maxage(también si lo añadeserver.headers) sale comoprivate, no-storey sinETag, para que ninguna caché compartida guarde la cookie. - Una conversión directa de
context.servicesa un tipo incompatible es un error de tipos. Conserver/runtime.tsen TypeScript,context.services as Otrocompara ahora con el tipo inferido en lugar de conunknown.as unknown as Otrosigue compilando; lo recomendable es quitar la conversión. useSocketClient(yuseSocket,useSocketChannel) no lanza en SSR. Sinawait, en el render del servidor devuelve un cliente inerte que se queda en"connecting", cuyoonno registra nada y cuyoemitrechaza, como prometía su JSDoc; hasta ahora lanzabauseSocketClient() is only available in the browsery el render fallaba. El navegador abre la conexión al montar, como antes. La comprobación dewindowsigue funcionando y ya no hace falta.- Avisos de configuración. Una clave de
meeko.config.tsque ni Meeko ni un módulo (meta.configKey) leen da un aviso una vez por configuración, con la clave más parecida (¿Quisiste decir "sitemap"?); antes se ignoraba en silencio. Es un aviso y no un error porque esa configuración cargaba en1.6, y los tipos dedefineMeekoConfigno la rechazan, porque las claves de los módulos que no aumentanMeekoConfigson válidas. Una aplicación de producción con canales de Socket y sinsocket.pubsubavisa una vez de que usaLocalSocketPubSub, como ya hacía el contador en memoria derateLimit;new LocalSocketPubSub()explícito lo quita. - Mensajes en español. La validación de
meeko.config.tsy del tema (server.port debe ser un entero entre 0 y 65535,campo desconocido),meeko initymeeko init-framework(El destino no está vacío), la contraseña vacía o demasiado larga demeeko auth hashpor tubería y el build que falta enmeeko prerender. El servidor de lenguaje reconoce el nuevoNo se puede cargar <archivo>de la configuración. meeko <comando> --help(ymeeko help <comando>) imprime la ayuda de ese comando;meeko --helplista los cuatro transportes demeeko lsp.meeko routesprepara.meeko/comomeeko check(entradas, contratos de páginas,services.d.tsytsconfig.*.json), así quetsc -p .meeko/tsconfig.server.jsonfunciona después.buildProjectgenera esos archivos en un proyecto limpio en lugar de fallar conTsconfig not found. Un segundomeeko devdel mismo proyecto avisa al arrancar de que el primero sigue en marcha (.meeko/dev/server.json), sin sustituir ni borrar su registro.- Nuevos autoimports:
useNavigation,createRootydefer.useNavigation(de@meekojs/meeko/framework) ycreateRoot(de@meekojs/meeko) se autoimportan comouseRouteyeffect;defer, en el código de servidor:<script server>(en el nivel superior y dentro de las opciones de ruta), los sidecars*.route.ts,server/y los*.server.ts. Antes los tres dabanTS2304/TS2552sin un import. Un export propio con uno de esos nombres tiene prioridad, como con los demás autoimports añadidos después de1.0. Condeferautoimportado, unexport const x = defer(...)llega a la plantilla comoDeferred<T>con su tipo exacto:x.value.valuedeja de compilar y los elementos de una lista enx.valueya no sonunknown.redirectynotFoundsiguen sin autoimportarse enapp/middleware/*.server.ts, porque sus tipos serían globales en todo el proyectoserver, también en las API, donde fallarían conMEEK073. - Las props son de sólo lectura en los tipos. El script ve
defineProps<T>()comoReadonly<T>, así queprops.x = …es el errorTS2540enmeeko check,meeko buildy el editor; en ejecución ya lanzaba unTypeError. Leerlas, pasarlas a una función o expandirlas no cambia. - Asignar
computed(...).valuelanzaTypeError. Hasta ahora, en JavaScript sin tipos, la escritura sustituía el valor hasta el siguiente recálculo; ahora lanza siempre, como una escritura en las props. TypeScript ya la rechazaba (ReadonlySignal). PageInputError.namees"PageInputError", como el deClientErroryApiError; antes era"Error".instanceofycodeno cambian.useHeadrechaza en los tipos los manejadores de eventos. Unonclick,onloadu otro manejador de los eventos del DOM enhtmlAttrsobodyAttrses un error de tipos; en ejecución ya lanzaba unTypeError. UnRecord<string, string>sigue valiendo, y cualquier otroon*sigue fallando en ejecución.- Aviso
MEEK119. Un atributoeach:*oif:*que no lee ninguna directiva del nodo (each:byjunto aeach:of,if:elsejunto aif:when) se escribía en silencio como atributo HTML literal; ahorameeko check,meeko buildy el editor lo avisan. El HTML generado no cambia.
Seguridad#
- Hashes de CSP sólo del código que escribe Meeko. Desde
1.3,scriptHashesystyleHashesdel índice de prerender y la CSP del adaptadorstaticse calculaban sobre el documento entero, así que un<script>o un<style>que llegara dentro de la página (contenido de un CMS leído en el build,html="…"sin sanear) quedaba autorizado. Ahora sólo cuentan los de fuera de#appy de los teleports; un.htmlpropio depublic/se sigue leyendo entero.inlineScriptHasheseinlineStyleHashessiguen leyendo todo el documento que reciben.
Correcciones#
contentSecurityPolicy()respetascript-src-elem. Los hashes de scripts iban siempre ascript-src, que el navegador no consulta para los elementos<script>si la política tienescript-src-elem; ahora van a esa directiva, como los de estilos astyle-src-elemdesde1.4.meeko checkaceptaexport default definePage(...). En<script server>sin código de nivel superior y en un sidecar*.route.ts, la forma documentada que el build y el runtime ya leían (route ?? default) hacía fallar la comprobación de contratos conhas no exported member 'route'. Ahorameeko checky el editor comparan su loader con las props de la página, como conexport const route.MEEK073en las funciones que Meeko llama al calcular el head.usePage(),redirect()onotFound()dentro deexport const head = () => ...o de la función deuseHead(() => ...)yuseSeoMeta(() => ...)en<script server>fallaban en cada petición con500, porque esas funciones se ejecutan cuando el código de nivel superior ya terminó; ahora son el errorMEEK073enmeeko check,meeko buildy el editor. Un helper al que el código de nivel superior llama mientras se ejecuta (tambiénasync) sigue pudiendo usarlas.- Una ruta dinámica
render: "prerender"sinprerenderEntriesdetiene el descubrimiento. Hasta ahora sólo fallabameeko build;meeko checkymeeko devdan el mismo error (Dynamic prerender route /posts/[id] requires prerenderEntries) con el archivo de las opciones. Unas opciones con un spread que puede llevarprerenderEntriesse dejan al build, que las lee al ejecutarlas. handleProcessErrorsse instala una vez por aplicación. Junto aserveNode/serveDenoconhandleSignals, una segunda llamada añadía otros listeners: cada fallo se informaba dos veces y dos cierres competían. Ahora no añade nada y devuelve su propia función de retirada; los listeners se retiran con la última. Si sus opciones (onError,close,shutdownTimeout) son distintas de las de la primera, se ignoran con un aviso.405conAllow: POSTen un RPC.GET(u otro método) a/_meeko/rpc/...encreateServerDispatcherydispatchRpcrespondía405sinAllow, aunque su JSDoc lo prometía y la API ya lo ponía.- La CLI no da por buena una ruta que no existe.
meeko check-app ./no-existeimprimíaOKy salía con0; ahora, comoroutes,prerender,check,dev,build,startypreview, falla conNo existe el directorio …. Un componente que falta daNo existe el archivo …en lugar delENOENTde Node. meeko initeinit-frameworkaceptan los mismos destinos. Una carpeta con sólo.gitse rechazaba en los dos, einit-frameworkno aceptababun.locknibun.lockb.meeko types,compileycheck <componente>usan las mayúsculas del archivo en disco. En Windows y macOS,meeko types src/app.meekocon el archivoApp.meekoescribíaapp.d.meeko.ts.meeko check componente.meekocomprueba lo mismo que el del proyecto. Un componente comprobado solo daMEEK044(un<script server>fuera de una página),MEEK100(genericen una página o un layout) y los errores de tipos de su<script server>, también de las opciones de ruta (redirect(url, 404), unaslayoutPropsque no son las del layout); antes sólo los dabameeko check .. En un proyecto que ya tiene.meeko/types, regenera antes las declaraciones, así que ve un layout o una página creados después del últimomeeko check ..signal(props.x ?? 0)no daMEEK101. Cualquier expresión de una prop como valor inicial designal(...)(props.x ?? 0,props.items.length + 1,[...props.items]) cuenta como el valor inicial explícito que ya erasignal(props.x).
[1.6.0] - 2026-10-28#
Los cambios de comportamiento que se notan al actualizar desde 1.5, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.5 a 1.6.
Añadido#
- Middleware de ruta con nombre.
export default defineRouteMiddleware(fn, { global: false })enapp/middleware/auth.ts(o10-auth.ts: el prefijo numérico no cuenta) declara el middleware"auth", que sólo se ejecuta en las rutas que lo piden condefineGroup({ middleware: ["auth"] })en un+group.tsodefinePage({ middleware: ["auth", "admin"] }). Se acumula de los grupos exteriores a la página, sin repetir, ymiddleware: falsequita los heredados. Se ejecuta después de los globales, en el orden declarado, con el mismo contrato en SSR, en la navegación del navegador, enrender: "client", en el prerender y en el adaptadorstatic. Meeko lee{ global }ymiddlewaresin ejecutar los archivos: un nombre que no existe (con la lista de los disponibles), uno global o un valor que no es literal detienenmeeko dev,meeko buildymeeko checkcon el archivo.middlewarese tipa con la uniónRouteMiddlewareName(GeneratedRouteMiddlewareen.meeko/types/routes.d.ts), con completado, hover y Ctrl+clic en el editor; en una aplicación sin middleware con nombre la unión está vacía, así que cualquier nombre es un error de tipos. En el navegador, cada middleware con nombre es un chunk que se carga la primera vez que se navega a una de sus rutas. La ruta guarda la lista resuelta enRouteRecord.middleware; un host propio la carga con la nueva opciónloadNamedMiddlewaredemountClientAppycreateFrameworkHandler, ydiscoverRouteMiddlewarede@meekojs/meeko/buildsepara los globales. Un middleware sin opciones sigue siendo global, ymiddlewareen un+group.tsdeja de ser un error. Como siempre, el middleware de ruta se empaqueta para el navegador y no sustituye a la autorización del loader, la API o el RPC. - Middleware de ruta de servidor. Un archivo
app/middleware/*.server.tsconexport default defineServerRouteMiddleware(fn)(de@meekojs/meeko/framework, autoimportado en los*.server.tsy enserver/) es middleware de ruta que sólo se ejecuta en el servidor, con el contexto deusePage()(url,params,search,request,signal,context) ysession,services,platformyrequestIda mano. Expresa «este grupo exige sesión» sin repetirlo en cada página:auth.server.tscon{ global: false }es"auth"ydefineGroup({ middleware: ["auth"] })lo pide; sin opciones es global. Corta la ruta conredirect(),notFound(), un error o unResponse. Se ejecuta después del middleware isomórfico (globales y luego con nombre) y antes del<script server>o elload: en SSR y, al navegar, dentro de la petición de datos de la página, cuya redirección sigue el navegador; enrender: "stream", antes del primer byte. Nunca llega al bundle del navegador y puede importarserver/y#server/…sinMEEK108. Una páginarender: "client"o"prerender"que pide uno con nombre detiene el descubrimiento, y los globales no se ejecutan en ellas (el adaptadorstaticlo avisa).RouteMiddlewareNameincluye sus nombres (elmiddlewarede una páginarender: "client"no los admite), el hover del editor los marca como «sólo servidor», yauth.tsyauth.server.tscon nombre son un nombre duplicado. Nuevos: los tiposServerRouteMiddlewareyServerRouteMiddlewareContext,RouteRecord.serverMiddleware(la cadena completa de la ruta: los globales de servidor y después los con nombre), la opciónloadServerMiddleware(file)decreateFrameworkHandler(una ruta que lista alguno y no la tiene falla en lugar de saltárselo),PageRoute.middlewareyserverGlobaldeRouteMiddlewareDiscovery. El middleware de ruta no protegeserver/apini el RPC, que siguen necesitando sus propias guardas.
Cambios#
app/middleware/*.server.tsdeja de ignorarse. Hasta1.5el descubrimiento de middleware de ruta los excluía; desde1.6son middleware de ruta de servidor. Un*.server.tscuyo export por defecto no es una llamada adefineServerRouteMiddleware(...)(un helper sin export por defecto, o conexport default function parseCookies…) detienemeeko dev,meeko buildymeeko checkcon el archivo y la indicación de mover los helpers aserver/utils, en lugar de convertirse en middleware global de servidor sin aviso (véase la excepción aprobada).- Un spread en las opciones de ruta que puede llevar
middlewareexige declararlo después.definePage({ ...protegida, render: "ssr" })con unprotegidaimportado (o una constante del archivo que tienemiddleware) detiene el descubrimiento: Meeko no lee el spread y la ruta se serviría sin ese middleware. Declaremiddlewaredespués del último spread (middleware: []si no añade ninguno). El spread de una constante del mismo archivo sinmiddlewaresigue valiendo. Además, una página cuyomiddlewarenombra uno que su manifiesto no lista (un build antiguo) responde500en lugar de servirse sin él, como ya pasaba conlayout. - El navegador ejecuta el middleware de ruta global en el mismo orden que el servidor. Hasta
1.5, la entrada del cliente lo ordenaba conlocaleComparey el servidor por código de carácter, lo que sólo difería con nombres que mezclan mayúsculas,-o_; ahora ambos usan el orden del servidor.
Correcciones#
- Una página renderizada para otra URL ya hidrata. El
404.htmldel adaptadorstatic, que el hosting sirve en cualquier URL inexistente, y una página prerenderizada servida con una query string (/?utm=mail) fallaban al hidratar conHydration mismatchsi algo pintabauseRoute(): el navegador hidrataba con su propia URL y no con la del render. Le pasaba a<MeekoThemeToggle>, cuyo campo ocultoreturnllevauseRoute().url, y podía pasarle a<MeekoAwait reserve>(su clave depende depath) y alaria-currentde<MeekoLink>. El payload lleva ahora la URL del render (campo opcionalurl); el navegador hidrata con ella y, justo después,useRoute()pasa a la del navegador, así que el formulario sin JavaScript vuelve a la página en la que está el visitante. POST /_meeko/themeno redirige fuera del origen con segmentos de punto. Unreturncomo/.//evil.exampleo/a/..//evil.examplese normalizaba a//evil.example, unaLocationrelativa al protocolo. Ahora vuelve a/. La comprobación deOriginya impedía usarlo desde otro sitio.
[1.5.0] - 2026-10-28#
Los cambios de comportamiento que se notan al actualizar desde 1.4, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.4 a 1.5.
Añadido#
- Layouts con nombre. Cada
app/layouts/<nombre>.meekoes un layout con nombre (app/layouts/admin/panel.meekoes"admin/panel") que la página elige condefinePage({ layout: "dashboard" })en<script server>o en el sidecar;layout: falsedeja sóloapp/app.meeko, y sinlayoutse usadefault, como hasta ahora.meeko buildlo lee sin ejecutar el archivo, comorender, y un nombre que no existe detienemeeko dev,meeko buildymeeko check. El orden esapp/app.meeko→ layout con nombre →+layout.meekode carpetas y grupos → página. Al navegar, el mismo layout se conserva con su estado y otro lo reemplaza, también conrender: "client","stream", prerender y el adaptadorstatic. Los layers aportan los suyos y los módulos los añaden conmeeko.addLayout(name, file); el proyecto gana.discoverLayoutsen@meekojs/meeko/build, y el campo opcionallayout({ name, file, props? }) enRouteRecordyErrorPageRecord. - Opciones por carpeta:
+group.ts.export default defineGroup({ layout: "dashboard", layoutProps: { showAlert: false } })en cualquier carpeta deapp/pages(autoimportado) da su layout y sus props a las páginas de la carpeta y sus subcarpetas: en cascada del exterior al interior, la página gana ylayoutPropsse combina por clave. Se lee sin ejecutar el archivo, así que sus valores son literales.meeko checkavisa conMEEK115de un+group.tsfuera deapp/pages.middlewareno se admite: las rutas no declaran middleware por nombre. - Props del layout desde la página.
definePage({ layoutProps: { title: "Conexiones" } })olayoutProps: ({ data }) => ({ title: data.user.name }), tipadas contra lasPropsdel layout elegido y presentes en el HTML del SSR desde el primer byte (viajan en el payload, campo opcionallayoutProps). Una prop conundefinedtoma el valor por defecto del layout, y una que JSON no representa falla con la página, el layout y la prop.useLayout()(autoimportado) las lee y las cambia de forma reactiva desde el<script>de la página conlayout.set({ showAlert: true }), sin volver a montar el layout; en una página se tipa con su layout. Lo que el setup escribe durante el SSR llega al HTML con un segundo render, sin mismatch de hidratación, y al navegar el layout vuelve a las props de la página nueva..meeko/types/routes.d.tsdeclaraGeneratedLayoutsyGeneratedPageLayouts;@meekojs/meeko/frameworkexportauseLayout,defineGroupy los tiposLayoutName,LayoutProps(autoimportado),LayoutHandle,LayoutPropsInput,PageLayout,PageLayoutOptions,GroupDefinitionyNamedLayoutRecord.meeko checkcompara laslayoutPropsde un+group.tso de una página que hereda su layout con las de ese layout (MEEK_PAGE_LAYOUT_PROPS). El editor completalayout:y las claves delayoutProps, y el hover del nombre del layout muestra su archivo y sus páginas. <MeekoOutlet />con props propias. Un layout que usa<MeekoOutlet />puede declarar sólo sus props (defineProps<{ title?: string }>()): Meeko añadeoutleta suComponentProps, así que ya no hace falta declararlo.meeko devsigue los cambios de layout. Editar una página, un sidecar o un+group.tsde forma que cambie el layout (o elrender) de una ruta vuelve a crear la aplicación con el manifiesto nuevo, sin reiniciar.- Datos que llegan después del primer pintado, sin saltos:
<MeekoAwait>. Una etiqueta propia de Meeko, como<MeekoLink>, sin import, que envuelve la zona de unuseApio unuseRpcQuerysinawait:<MeekoAwait for="items" reserve>.fores una expresión (comoeach:of; tambiénbind:for) con un recurso o una lista (for="[items, stats]"). Pinta el snippetpendingen la primera carga,error(conerroryretry, que vuelve a pedir los recursos que fallaron) si falla, y su contenido cuando todos tienen datos; sin snippeterror, un<p role="alert">con un botón «Reintentar» (textos enbind:messages). Durante una recarga el contenido sigue montado y reciberefreshing(<snippet name="default" params="refreshing">). Dentro del contenido,meeko checky el editor leenitems.data.valuecomo cargado, sin!. El contenedor es un<div>condata-meeko-await="pending|error|ready",aria-busy="true"mientras carga o recarga ydata-meeko-refreshing, sin estilos propios; si el foco estaba en un nodo que desaparece al cambiar de estado (el botón «Reintentar»), pasa a la zona en lugar de perderse. Sinfores el nuevo errorMEEK116. TiposMeekoAwaitProps(autoimportado),MeekoAwaitSnippets,AwaitResourceyAwaitMessagesen@meekojs/meeko/framework. El editor completa la etiqueta, sus props y sus snippets, la explica en el hover y lleva aMeekoAwaitProps; la gramática de VS Code la colorea como integrada y suforcomo TypeScript. Véase<MeekoAwait>. - Reserva de altura con memoria:
<MeekoAwait reserve>. Al mostrar datos, la zona mide su altura con unResizeObserver(sin bloquear) y la guarda ensessionStoragey en memoria, por ruta, petición e instancia; mientras vuelve a esperar esos datos, una regla de una hoja de estilos construida (adoptedStyleSheets) le da esamin-block-size, sin atributosstyle, así que vale con una CSP estricta. En una carga completa, un script en línea de<head>(con el nonce, o con su hash en un sitio estático; siempre el mismo texto) adopta las reglas antes del primer pintado y la hidratación no cambia nada; en una navegación o un montaje posterior, el componente las adopta antes de insertar la zona.reserve="320px"fija además un mínimo, también en la primera visita, con un<style>de<head>;bind:reservepuede cambiar en el navegador, y el script, las reglas y la medición lo siguen. ConsessionStoragebloqueado, la memoria vive mientras la página está abierta. En Chrome, una card con 8 filas que llegan a los 900 ms pasa de CLS 0,246 a 0 en la segunda visita, por navegación o por recarga. Véase Evitar saltos de altura (CLS). refreshingykeepPreviousenuseApiyuseRpcQuery. Como desde1.1,refresh()y un cambio de la entrada conservandatahasta que llegan los datos nuevos; ahorarefreshingestruedurante esa recarga (la primera carga espendingsinrefreshing), para un indicador que no oculta los datos. El nuevo último argumento{ keepPrevious }(tipoDataResourceOptions) valetruepor defecto, el comportamiento de siempre; confalse, un cambio de la entrada vacíadatayerrory vuelve a ser una primera carga.refresh()y un recurso esperado conawaitconservan siempre los datos. Véase Datos anteriores durante una recarga.- Aviso
MEEK117.meeko check,meeko buildy el editor avisan de una página o un layout que pinta en uneach:oflos datos de unuseApio unuseRpcQuerysinawaitsin ocuparse de su carga: la lista sale vacía en el HTML del servidor y crece al llegar los datos. Desaparece conawait,<MeekoAwait>, una lectura dependingorefreshing, unif:whensobre los datos o al pasar el recurso entero a otro componente. Es una pista, no un error. <MeekoTeleport>: contenido en otro lugar del documento. Una etiqueta propia, sin import:<MeekoTeleport to="#toolbar">…</MeekoTeleport>pinta su contenido dentro del elemento con eseidyto="body", al final de<body>. El contenido sigue siendo del componente que lo declara (señales, eventos, snippets,provide/inject, manejadores de errores y<style scoped>) y se libera con él, también al cambiar de página, sin dejar nodos en el destino. El HTML de SSR,"stream"y"prerender"(también con el adaptadorstatic) ya lo trae en su destino, se renderice éste antes o después, y la hidratación no cambia el DOM.disabledobind:disabledlo pintan en su sitio; cambiarlo mueve los mismos nodos. Funciona dentro deif:whenyeach:of.tosólo admite"body"y"#id", que se busca congetElementById; otro valor estático daMEEK114, ybind:tose tipa comoMeekoTeleportTarget. Un#idque la página no renderiza falla en desarrollo y, en producción, se pinta en su sitio con un aviso.@meekojs/meeko/frameworkexporta los tiposMeekoTeleportPropsyMeekoTeleportTarget; un componente del proyecto llamadoMeekoTeleporttiene prioridad, conMEEK106. El editor completa sus atributos y, ento="…",bodyy losiddel componente, deapp/app.meekoy de los layouts. Véase<MeekoTeleport>, que explica también cuándo bastan<dialog>opopover.- Popover integrado:
<MeekoPopover>. Una etiqueta propia, sin import, sobre la API nativapopovery CSS anchor positioning: Meeko pinta un<button>conpopovertarget,aria-expanded,aria-controlsyaria-haspopup(el snippettriggeres su contenido) y el contenido en un elemento conpopover, oculto desde el HTML del servidor.modees"auto"(se cierra al pulsar fuera),"manual"o"hint"(un tooltip que se abre con el puntero o el foco, con retardo y sin robar el foco);placementadmitetop,bottom,leftyrightcon-start/-end,offsetsepara el contenido yposition-try-fallbacks: flip-block, flip-inlinelo da la vuelta si no cabe. Sin anchor positioning, un cálculo mínimo en JavaScript lo coloca al abrirse y conresize/scroll, también dado la vuelta y dentro del viewport.Esccierra y devuelve el foco; conrole="menu", flechas,Inicio/Finy búsqueda por letras entre losmenuitem, y elegir uno cierra el menú;role="dialog"lleva el foco dentro yrole="tooltip"describe el disparador.modeles la señal de apertura yon:toggleemite{ open }. El snippetanchoracepta un disparador propio, que recibe sus atributos entrigger(listo parabind="trigger"). Los IDs salen deuseIdy los estilos van por CSSOM, en la capa de cascadameeko: una CSP sin'unsafe-inline'basta. Véase Popover. - Select integrado:
<MeekoSelect>. Un select accesible sobre<MeekoPopover>, sin estilos visuales: unrole="combobox"conaria-activedescendant(el foco se queda en el disparador) y unrole="listbox", condata-active,data-selectedydata-disableden las opciones ydata-openen la raíz.modeles la opción o, conmultiple, un array;meeko checky el editor infieren su tipo, el debyy el de los parámetros de los snippets (option,value,group,empty,loading) del tipo deoptions.bycompara objetos,groupByagrupa con encabezados,disabledBydeshabilita ymaxlimita la selección múltiple.searchableconvierte el disparador en un<input>que filtra sin distinguir acentos;filteres una búsqueda propia, también asíncrona, con estado de carga y cancelación. El teclado sigue el patrón de select de ARIA (flechas,Inicio/Fin,RePág/AvPág,Intro,Espacio,Escy búsqueda por letras), y la opción activa se desplaza hasta verse. Connameenvía<input type="hidden">con el<form>;requiredydisabledfuncionan como en un control nativo, ybind:field="form.field('x')"deuseFormda el valor, elid,aria-invalidyaria-describedby. La lista mide al menos lo que el disparador y tiene una altura máxima con scroll. Véase Select. - Etiquetas integradas sin coste en el bundle.
<MeekoThemeToggle>,<MeekoAwait>,<MeekoPopover>y<MeekoSelect>son módulos propios (@meekojs/meeko/builtins/theme-toggle,/await,/popovery/select, contrato entre el compilador y el runtime de la misma versión, como/internal) que el compilador importa sólo en los componentes que usan la etiqueta; el resto de la aplicación no carga su código. Sus tiposMeekoPopoverPropsyMeekoSelectProps(conPopoverPlacement,PopoverTrigger,SelectModel,SelectFilter…) se exportan desde@meekojs/meeko/frameworky se autoimportan enapp/. El editor completa ambas etiquetas, sus props y sus snippets y las explica en el hover, y la gramática de VS Code las colorea como etiquetas integradas. Un componente del proyecto con uno de esos nombres tiene prioridad, conMEEK106. - Etiquetas HTML en
<component is>, con contenido.isacepta, además de un componente, una etiqueta HTML: un literal ('a'), una expresión cuyo tipo es una unión de etiquetas (props.href ? 'a' : 'button', una propas: "section" | "article") o una unión de etiquetas y componentes. Con una etiqueta es ese elemento, con sus atributos, eventos,ref:,htmlybind="obj", y con su contenido (hijos,if:when,each:of,<render>y componentes); con un componente, el contenido son sus snippets. Meeko deduce la unión de etiquetas del tipo deis:event.currentTargetde unon:y eluseElementRefde unref:son la unión de sus elementos (HTMLAnchorElement | HTMLButtonElement). Si todos los resultados deisson literales, el SSR escribe el elemento sin marcadores; si alguno puede ser un componente, el elemento va en el rango del componente. Al cambiar la etiqueta en el navegador, un elemento nuevo sustituye al anterior y los hijos se mueven a él con sus nodos, su estado, el valor de sus controles y el foco; entre una etiqueta y un componente, el rango se monta de nuevo. Las etiquetas admitidas son el nuevo tipo públicoTagName: noscript,style,iframe,object,embed,template,textareani las de metadatos; un valor no admitido es un error de compilación (si es un literal) o de tipos (MEEK112), y en ejecución unTypeErrorantes de escribir HTML, en SSR y en el navegador. Un<button>que pasa a ser<a>al recibirhrefse escribe con una sola plantilla. Véase Etiquetas HTML en<component is>. - Atributos según la etiqueta. En un
<component is>con etiquetas, un atributo que no es global sólo se escribe mientras la etiqueta del momento lo admite según los datos HTML de Meeko (los de MDN que usa el editor), sin ternarios:bind:href,targetyrelsólo en<a>,bind:typeydisabledsólo en<button>. Los globales (class,id,style,title,hidden,tabindex,role,aria-*,data-*…) y los eventos se escriben siempre. Un atributo que no admite ninguna de las etiquetas posibles es el nuevo errorMEEK112demeeko checky del editor. En los elementos normales nada cambia. Véase Atributos según la etiqueta. - Atributos en bloque:
bind="obj". En un elemento, en<component is>y en una etiqueta de componente, escribe las claves de un objeto como atributos o como props, de forma reactiva: añade las nuevas y quita las que el objeto deja de tener. Unbind:xexplícito o un atributo estático gana sobre la misma clave;classystylese combinan con los explícitos (las declaraciones explícitas destyleganan). Las clavesundefined,nullofalseno se escriben. Las claveson*se rechazan en los tipos y en ejecución, porque los manejadores no se crean desde datos, y también lasdata-meeko*. Los nombres se comparan sin distinguir mayúsculas, como en HTML:IDjunto abind:idno escribe un segundoid, yData-Meekotambién se rechaza. El objeto se comprueba contra los atributos de la etiqueta o contra las props del componente (MEEK113); el nuevo tipo públicoElementAttributes<Tag>lo tipa. En un elemento personalizado,bindsigue siendo un atributo HTML. Véase Atributos en bloque conbind. - Enlace activo por sección:
matchen<MeekoLink>yuseRoute().isActive().match="exact"(por defecto) mantiene el comportamiento de antes, ymatch="prefix"marca también las rutas que están por debajo: en su propia ruta el enlace llevaaria-current="page"y en una ruta por debajo,aria-current="true", la sección actual. Compara por segmentos completos, sobre las rutas codificadas comohref()y sin query ni fragmento:/dashboardcubre/dashboard/stats, no/dashboards, y/dashno cubre/dashboard.to="/"sólo coincide exacta, también conprefix, para no quedar activo en todas las páginas; en una ruta dinámica, la sección es la de susparams.matchadmitebind:matchcon una expresión"exact" | "prefix", quemeeko checky el editor comprueban; otro valor estático esMEEK105. El valor sale en el HTML del servidor, la hidratación no lo cambia y la navegación lo actualiza, también en un layout conservado.MeekoLinkPropsganamatch, y el editor lo completa con sus dos valores y lo explica en el hover. Para los enlaces y componentes propios,RouteState(lo que devuelveuseRoute()) ganaisActive(to, { params, prefix }), con la misma regla:toes unRouteIdyparamslos de esa ruta, con los tipos dehref(); devuelve"page","true"ofalse, que sirve tal cual parabind:aria-currenty como condición, y es reactivo. Véase<MeekoOutlet />y<MeekoLink>y Ruta actual. useId(): IDs estables entre SSR e hidratación. Desde@meekojs/meekoy autoimportado: devuelve un ID paraid,foroaria-describedbyque sale de la posición de la instancia en el árbol (componente, clave de la fila deeach:,if:y snippet) y del orden de la llamada, así que el servidor y el navegador dan el mismo sin enviarlo, y nunca se repite entre instancias, también en filas o regiones que aparecen después en el navegador.document.idPrefixenmeeko.config.tscambia su prefijo (mpor defecto). Véase IDs estables.useForm:trySubmit,commit,onSuccess,focusInvalidyfield.form.trySubmit(event)envía comosubmity resuelve con unFormSubmissiondiscriminado ({ ok: true, result }o{ ok: false, reason: "invalid" | "error" | "skipped" });submitno cambia.form.commit(values?)toma los valores como nuevos iniciales dedirtyy dereset()sin borrarsubmitted, yonSuccess(result, form)lo hace tras un envío correcto. ConfocusInvalid: true, un envío con errores en los campos enfoca el primer control inválido una vez pintados los mensajes.form.field(name)devuelve unFormFieldtipado con la señal, el mensaje,invalid,describedBye IDs deuseId, para un componente de campo propio (<AppField bind:field="form.field('email')" />). Véase Formularios.ApiErrorconissues.new ApiError("EMAIL_TAKEN", 409, { issues: [{ path: "email", message: "…" }] })envía mensajes de campo con la forma de los de un400(como mucho 20, sólo con un4xx); elmessagesigue siendo interno. Llegan enClientError.issuesyuseFormlos lleva a su campo. Sólo viajan los de unApiErrorde Meeko: otro error con su forma (unApiErrorde un SDK, unClientErrorde otro servicio que se deja escapar) no envía susissues. Tipos nuevosApiErrorOptionsyApiErrorIssue. Véase Errores de un campo.ClientErroreisClientErrorautoimportados en los componentes y en los módulos deapp/, comouseForm; no enserver/,*.server.tsni los archivos de ruta. Un export propio con el mismo nombre tiene prioridad.- Aliases de ruta:
~/,@/,#shared/y#server/. Sin configuración,~/es la raíz del proyecto,@/esapp/(al revés que en Nuxt, a propósito),#shared/esshared/y#server/esserver/. Funcionan enmeeko devymeeko build(navegador y SSR, con todos los adaptadores), en lospathsde.meeko/tsconfig.app.json,serveryshared(así quemeeko checky el editor resuelven y tipan), en el servidor de lenguaje (completado de carpetas y archivos tras el alias e «Ir a la definición») y en los módulos de@utilitiesy lo que importan. Cada alias tiene el ámbito de su carpeta, como los autoimports:#server/sólo existe en el código de servidor y en un componente, un.tsdeapp/oshared/daMEEK108(también conimport type) enmeeko check, el editor y el build, y@/enshared/da el nuevo errorMEEK118enmeeko check,meeko devy el build. Los aliases se buscan en el proyecto y después en cada layer: gana el primer archivo que existe, también desde un archivo de un layer. Un componente se puede importar con un alias (import * as Card from "@/components/Card.meeko").aliasenmeeko.config.ts({ "ui/": "app/components/ui/", "#server/": false }) añade aliases, cambia los de Meeko o los quita; cada uno toma el ámbito de la carpeta a la que apunta, y una clave o una carpeta no válidas son un error de la configuración (MEEK_CONFIGen el editor). Un patrón de lospathsdeltsconfig.jsondel proyecto con el prefijo de un alias de Meeko y otra carpeta, unresolve.aliasde Vite que cubre ese prefijo (del proyecto o de un módulo) y una entrada de"imports"depackage.jsonque empieza por#shared/o#server/ganan y desactivan ese alias de Meeko en todas partes, también en las comprobaciones de ámbito (MEEK108,MEEK118), con un aviso una vez por proceso. Los schemas de los contratos de API, RPC y Socket también se importan con un alias (#shared/schemas/autho uno propio que apunte ashared/): el generador de contratos lo resuelve y los clientes generados nombran el archivo deshared/por su ruta. Véase Aliases de ruta.
Cambios#
<component is>admite contenido. Hasta1.4dabaMEEK031; ahora su contenido son los snippets del componente, como en su etiqueta, o los hijos de la etiqueta HTML.MEEK031queda para una etiqueta vacía (img,input…) con contenido. En un<component is>cuya expresión puede dar una etiqueta, los atributos con guiones, que antes dabanMEEK032, son del elemento. Un<component is>cuyos resultados son nombres en PascalCase o imports de un.meeko(is="Card",is="open ? Dialog : Sheet") compila igual que antes; una expresión comoprops.contentadmite además etiquetas y lleva algo más de código.bind="..."es una directiva. Sin nombre,binden un elemento estándar o en una etiqueta de componente ya no es un atributo literal ni la prop estáticabind: esbind="obj". Para la prop o el atributo literal, usebind:bind="'texto'"(véase la excepción aprobada).aliases una clave deMeekoConfig. Un módulo conmeta.configKey: "alias"hace fallar la carga de la configuración y pide otra clave, comothemedesde1.4(véase la excepción aprobada).- Los
tsconfiggenerados copian lospathsdel proyecto..meeko/tsconfig.app.json,serverysharedincluyen ahora lospathsdeltsconfig.jsondel proyecto, que hasta1.4sólo usaban el editor y la comprobación de los componentes, así quemeeko checkresuelve con ellos también los.ts. MEEK108ve elserver/de los layers enmeeko check. Un import de valores de un componente que llega alserver/de un layer, que ya fallaba en el build, es ahoraMEEK108también enmeeko checky en el editor.
Correcciones#
- Una señal con
undefinedya no rompe el SSR.const id = signal<string | undefined>(undefined)hacía responder500a toda la página con «TypeError: Non-serializable value at $.id», sin decir de qué componente. Ahora el estado lleva esas señales aparte y el navegador las hidrata comoundefined, sin diferencias. Un valor que JSON sigue sin representar (una fecha, unMap, una instancia) falla con el componente, la señal y una pista:app/pages/agenda.meeko: the signal "when" cannot be sent to the browser with the SSR state (…). useFormrevalida los campos que dependen del que cambia. Una confirmación (v.forward(v.partialCheck(…), ["confirm"])) seguía mostrando «Las contraseñas no coinciden» después de corregir la contraseña, porque sólo se validaba el campo editado. Ahora cada cambio vuelve a validar también los demás campos con un error del schema (y el mensaje del formulario que vino del schema); los mensajes del servidor de otros campos se conservan.
[1.4.1] - 2026-10-28#
Correcciones#
- La hoja global ya no se queda sin reglas tras editar un archivo que importa. En
meeko dev, el snapshot.meeko/styles/main.csstransformabaapp/styles/main.cssen cuanto el watcher avisaba de un cambio. Cuando el aviso llega al truncarse el archivo, antes de escribirse su contenido (el watcher descarta después el cambio de la escritura), transformaba un archivo vacío, y la petición del navegador reutilizaba ese resultado de la caché de Vite. La página se quedaba sin las reglas del archivo hasta la siguiente edición. Ahora el snapshot espera al contenido del archivo cambiado con elread()de Vite antes de transformar la hoja.
[1.4.0] - 2026-10-28#
Los cambios de comportamiento que se notan al actualizar desde 1.3, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.3 a 1.4.
Añadido#
- Snippets condicionales:
if:whenen<snippet>.<snippet name="footer" if:when="snippets.actions">…</snippet>pasa el snippet al hijo sólo mientras la condición es verdadera; con la condición falsa, para el hijo no existe:snippets.footeresundefined, suif:when="snippets.footer"es falso y un<render>no pinta nada. Permite reenviar un snippet opcional sin que el hijo pinte un contenedor vacío. La condición se evalúa en el ámbito del padre, igual en SSR y al hidratar, y es reactiva: el hijo sigue montado (con su DOM, su estado y su foco) y sólo se actualizan los bindings que leen ese snippet, porque el objetosnippetsde un hijo con props reactivas sigue al padre; un hijo sin props reactivas (con unawaitde datos en el nivel superior, o compilado antes de1.2) se vuelve a montar, conservando sus señales editables por nombre. Sólo vale para un snippet que el hijo declara opcional (x?:): en uno obligatorio es el nuevo error de tiposMEEK110demeeko checky del editor, marcado enif:when. La condición estrecha los tipos dentro del contenido del snippet, comoif:whenen un elemento.MEEK061sigue rechazando los demás atributos y señala además unif:whenvacío o sin valor. El editor completaname,paramseif:whenen<snippet>, y el plugin de Prettier formatea la condición como una expresión. Véase docs/api.md. - Tema claro/oscuro sin parpadeo:
themeyuseTheme(). Contheme: true(o{ themes, default, system, darkThemes, attribute, colorScheme, cookie, storageKey, clientHints }) enmeeko.config.ts, Meeko guarda la elección en una cookie (meeko-theme) y enlocalStorage, y el servidor escribe el tema en<html>(data-theme="dark"o una clase) desde el primer byte. En modo"system", el servidor lo resuelve con el client hintSec-CH-Prefers-Color-Scheme(responde conAccept-CHyCritical-CH, así que Chromium lo envía ya en la primera visita) y, si no lo recibe, un script en línea y bloqueante de<head>aplica el tema antes del primer pintado; el mismo script resuelve las páginas prerenderizadas y el adaptadorstatic. El script lleva el nonce en SSR y su hash entra en la CSP de las páginas prerenderizadas y destatic;color-schemese aplica por CSSOM, que una CSP sin'unsafe-inline'permite.useTheme(), autoimportado, devuelvetheme(la elección, editable y válida paramodel),resolved(el tema efectivo),themesyset(theme): en SSR lee la cookie, así que los iconos salen bien en el HTML del servidor; la hidratación pinta lo mismo que el servidor y recibe los valores del navegador al terminar; un cambio actualiza<html>, la cookie ylocalStorage, sigue aprefers-color-schemeen"system"y llega a las otras pestañas. La cookie se valida contra la lista de temas, los documentos llevanVary: Cookie, Sec-CH-Prefers-Color-Schemeymeeko checktipa los temas como la unión literal dethemes(GeneratedTheme), que el editor declara a partir dethemeleído sin ejecutar la configuración. Conattribute: "class", el runtime sólo cambia la clase del tema en<html>(conclassList) y conserva las demás, también las que añaden otros scripts. El script va en<head>justo después de lasmetay antes de las hojas de estilo enlazadas, así que no espera a ninguna. Sintheme, nada cambia. Véase Tema claro/oscuro sin parpadeo. - Selector de tema integrado:
<MeekoThemeToggle>. Conthemeactivo, una etiqueta propia de Meeko, como<MeekoLink>, sin import:mode="cycle"(por defecto), un botón que pasa a la elección siguiente, omode="select", un grupo de radios accesible con el teclado;bind:themesrestringe y ordena la lista,bind:labelsnombra cada elección («Claro», «Oscuro», «Sistema» por defecto) ybind:messagestraduce los textos accesibles (tipoThemeToggleMessages);classeidvan al<form>raíz. Un snippet por elección (<snippet name="dark"><IconMoon /></snippet>) es su icono;meeko checky el editor tipan los snippets,themesylabelscon los temas de la configuración (tipoThemeToggleSnippets; el editor leethemedemeeko.config.*sin ejecutarlo, también antes del primermeeko checky aunque el tsconfig no incluya.meeko/types/routes.d.ts), y el editor completa la etiqueta, sus props y sus snippets y la explica en el hover. El componente pinta todos los iconos, cada uno en<span data-meeko-theme-icon>, y una regla mínima que Meeko añade a<head>(sólodisplay: none) muestra el de la elección, así que el icono sale bien desde el primer frame, también en"system"sin cookie ni client hint, y hidratar no cambia ningún nodo. Elaria-labeldel botón dice la elección y la siguiente («Tema: oscuro. Cambiar a sistema») o, mientras la página no la conoce, «Cambiar tema». Enmode="select", mientras no se conoce la elección (sin la cookie o en una página prerenderizada) ningún radio llevaaria-checked, ni siquiera el dedefault; el estado visual lo da el CSS condata-theme-choice, y al hidratar cada radio recibe suaria-checkeduna sola vez. Sin JavaScript es un formulario que envíaPOST /_meeko/theme: el servidor comprueba que el tema sea uno dethemeso"system"y queOriginsea el del propio sitio, escribe la cookie y responde303sólo a una ruta del mismo origen (nunca una redirección abierta); con JavaScript, el envío se intercepta y el cambio es inmediato. En el adaptadorstaticel formulario necesita JavaScript. Usarla sinthemees el nuevo errorMEEK111(enmeeko checky el editor, cuandomeeko.config.*lo dice sin ejecutarse; si no, al renderizarse); un componente del proyecto con ese nombre tiene prioridad, conMEEK106. La gramática de VS Code la colorea como etiqueta integrada. Véase<MeekoThemeToggle>. data-theme-choiceen<html>. Contheme,<html>lleva además la elección,"system"incluido: la escribe el script de<head>antes del primer pintado, el servidor cuando la conoce por la cookie y el runtime con cada cambio, para el CSS propio (un icono de «sistema» que no salta). Una cookie que escribió el servidor pasa alocalStoragey a las otras pestañas en la siguiente carga.theme.attributeno puede ser"data-theme-choice".- CSP con hashes de estilos.
inlineStyleHashes(html)de@meekojs/meeko/buildcalcula el SHA-256 de cada<style>en línea,prerender/manifest.jsonlos guarda enpages[ruta].styleHashesycontentSecurityPolicy(policy, scriptHashes, styleHashes)los añade astyle-src-elem,style-srco una copia dedefault-src, salvo que la directiva ya admita'unsafe-inline'(el hash la anularía para los atributosstyle). El adaptadorstaticlos aplica conserver.contentSecurityPolicy, así que el estilo del tema (y el de los componentes) funciona con una política sin'unsafe-inline'; en SSR lleva el nonce. Véase CSP con hashes. - Autoimports en los módulos de
@utilities. En un proyecto Meeko (una carpeta conmeeko.config.*o unpackage.jsonque depende de@meekojs/meeko),meekoUtilities()aplica al módulo de@utilities, y a los módulos locales en TypeScript que importa, los mismos autoimports de valores que el plugin de Vite aplica al resto del proyecto, con el mismo registro y la misma transformación: los deapp/composables,app/utils,app/stores,app/typeseimports.appDirs, los deshared/*eimports.sharedDirsy los de los layers y los módulos. Así, unapp/styles/utilities/color.tsque usa sin import una constante deapp/themedeclarada enimports.appDirsgenera la hoja en lugar de fallar conTHEME_COLORS is not defined, como ya aceptaban el editor ymeeko check. No llegan los exports deserver/yserverDirs, los.client.tsy.server.ts, los componentes.meeko, los clientes generados ni las API del framework (signal,useState…); usar uno da un error que nombra el autoimport, su origen y el motivo, no unReferenceError.defineUtilitiessigue importándose de@meekojs/meeko/postcss. Los archivos de autoimports que se usan son dependencias de la hoja, y añadir o quitar un export en una carpeta de autoimports regenera el módulo. Nueva opciónmeekoUtilities({ autoImports }),truepor defecto;falseempaqueta los módulos como en1.3. Para leer el registro, el plugin ejecutameeko.config.*y lossetupde sus módulos, comomeeko dev, una vez por hoja; si eso falla, la hoja se genera sin autoimports y con un aviso, en lugar de fallar. Fuera de un proyecto Meeko, conautoImports: falseo sin rolldown (Vite 8), la configuración no se ejecuta y los módulos se cargan como en1.3. Véase docs/framework.md. defineSnippetsacepta un mapa de snippets. El valor de cada clave deTpuede ser, además de una tupla de parámetros, unSnippet<Args>, que cuenta comoArgs. Así un componente reutiliza los snippets de otro:defineSnippets<Omit<AppCardSnippets, "footer"> & { actions?: [close: () => void] }>(), o conimport type { ComponentSnippets as CardSnippets } from "./Card.meeko", que antes era el errorTS2344porqueComponentSnippetses el mapa deSnippet<Args>y no la declaración de tuplas. Cada snippet conserva sus parámetros y su opcionalidad conOmit,Picky&, también en un componente genérico, ymeeko check, el editor y la comprobación de las etiquetas del padre ven las tuplas resultantes (MEEK110incluido). Las declaraciones con tuplas tienen el mismo tipo que antes. Véase docs/api.md.- Tipos con el nombre de cada componente. Cada componente autoimportado (de
app/components, de los layers y de las carpetas de componentes de los módulos, con su prefijo) aporta sin import<Nombre>Props,<Nombre>Snippets,<Nombre>Modelsy<Nombre>Exposed, con el nombre de su etiqueta:app/components/App/Card.meekodaAppCardProps,AppCardSnippets,AppCardModelsyAppCardExposed. Son elComponentProps,ComponentSnippets,ComponentModelsyComponentExposedde su módulo, con sus parámetros de tipo ynullcuando el componente no declara el canal, y tienen el ámbito del componente: los.meekoy los.tsdeapp/, noshared/. Llegan conimport typedesde el.meekoy se declaran en.meeko/types/autoimports/; un autoimport del proyecto con el mismo nombre (por ejemplo, deapp/types) tiene prioridad. El editor los completa donde se escribe un tipo, y el hover y la navegación a definición llevan al componente y a la macro que declara cada uno; lo mismo hacen ahora con unComponentSnippetsimportado de un.meeko. Los nombres genéricos no cambian. Véase Tipos de cada componente. MeekoLinkProps,MeekoOutletProps,MeekoThemeTogglePropsyRouteIden@meekojs/meeko/framework: los tipos de las props de<MeekoLink>(to,paramsysearch, con las rutas de la aplicación y los parámetros de cada una), de<MeekoOutlet />(ninguna) y de<MeekoThemeToggle>(mode,themes,labels,messages,classeid, con los temas de la configuración), y el ID de cualquier ruta de la aplicación (el tipo detoy dehref). Sólo son tipos, documentados con JSDoc. Se autoimportan: los de las etiquetas donde se usan los componentes (app/y los.meeko), con el ámbito deAppCardProps, yRouteIden todo el proyecto, comohref; un tipo del proyecto con el mismo nombre tiene prioridad, y un componente del proyecto que sustituye la etiqueta (app/components/MeekoLink.meeko, conMEEK106) aporta su propioMeekoLinkProps. Así un componente recibe una ruta sin import, condefineProps<{ href: MeekoLinkProps["to"] }>()oRouteId, ymeeko checkcomprueba que el padre le pasa una ruta que existe. El editor los usa en el hover de esas etiquetas y en «Ir a definición», y los completa sin añadir un import.- Hover de
<MeekoLink>,<MeekoOutlet />y<MeekoThemeToggle>en el editor. Muestran la misma ficha que un componente del proyecto en lugar deany: sus props con los tipos reales (tocon la ruta,bind:paramscon los parámetros de esa ruta,bind:search), su contenido (default), lo que renderizan, su documentación y un enlace a la guía. La de<MeekoThemeToggle>lista sus props y un snippet por elección de la configuración. «Ir a definición» abreMeekoLinkProps,MeekoOutletPropsoMeekoThemeToggleProps; el hover y el completado deto,bind:paramsybind:searchusan esos tipos,on:clicky el resto de atributos tienen los de un<a>y el completado ofrece los atributos de<a>sinhref. El servidor de lenguaje las describe con una tabla de etiquetas propias (props, snippets, elemento que renderizan, documentación y dónde valen), a la que se suma cada etiqueta nueva. Con un@meekojs/meekoanterior sin esos tipos, la ficha usa los de la tabla. - Ficha de componente con color. El hover de una etiqueta de componente, del identificador que lo importa y de sus tipos (
AppCardProps…) muestra la firma de la etiqueta en un bloque TypeScript (<AppCard, una línea por prop con su tipo declarado, comovariant?: CardVariant,on:save(id: number),model:open?: booleany los snippetsheader?()ydefault()como contenido), un enlace que abre el.meekoy el JSDoc de cada prop, evento, modelo y snippet que lo tenga. Se omiten las secciones vacías en lugar de escribir «ninguno». El hover de<snippet name>también muestra el JSDoc del snippet.
Cambios#
meta.configKeyno puede ser una clave propia deMeekoConfig. Desde esta versiónthemees una opción de Meeko, y un módulo conconfigKey: "theme", como el del ejemplo de módulos de la guía de1.3, leería como sus opciones las del tema de Meeko y su aumentación deMeekoConfig.themechocaría con el tipo de Meeko. Ahora un módulo cuyometa.configKeyeslayers,extends,modules,imports,server,css,document,dev,site,sitemap,robotsothemehace fallar la carga de la configuración con un mensaje que pide otra clave, y unas opciones dethemecon campos que no son del tema (initial) fallan conunknown fieldy un mensaje que apunta a ese conflicto. Es una excepción aprobada en stability.md; la migración (otra clave, comoacmeTheme) está en la guía de actualización.
Correcciones#
- Completado del
<script>sin ruido. El completado, el hover y «Ir a definición» ya no ofrecen los tipos auxiliares del código virtual del editor (__MeekoDeclaredProps,__MeekoEmit,__MeekoChannelsOf,__MeekoAnyProp…), y el hover de un evento muestraHTMLAnchorElementen lugar de__MeekoElementOf<"a", false>. De@meekojs/meekosólo se sugieren las entradas públicas deexports: con el paquete enlazado (link:de pnpm o una junction), TypeScript lo resuelve fuera denode_modulesy sugería sus archivos internos con rutas relativas (MeekoAdapterNamedesde../../../../Clients/meeko/packages/meeko/dist/…); ahora un nombre que exporta una entrada se importa desde ella (@meekojs/meeko/framework) y uno que ninguna exporta no se sugiere. Los exports de los paquetes van después de los nombres locales y de los autoimports del proyecto. - Autoimport de un nombre de un módulo ya autoimportado. Completar
Signal(oMeekoModule) en un componente que usasignal()(ouseRoute()) escribía, type Signalal principio del<script>, porque TypeScript lo añadía al import que el editor genera para el autoimport y que no existe en el.meeko. Ahora añadeimport { type Signal } from "@meekojs/meeko";. - Comprobación del proyecto en el editor desactualizada (
MEEK_PROJECT_CHECK). Un.meekovacío recién creado (por ejemplo, en una carpeta nuevaapp/components/Layout) rompía la comprobación de contratos y suMEEK007aparecía en cada archivo abierto que la lanzaba; al guardar el componente con su contenido el error seguía, porque guardar un componente fuera deapp/pagesyapp/layoutsno volvía a comprobar el proyecto y un fallo anterior no se borraba con la siguiente comprobación correcta. Ahora la comprobación se repite, con una espera de 450 ms, al guardar, crear, borrar o renombrar cualquier.meeko, módulo, configuración o carpeta de la aplicación (el servidor observa también la creación y el borrado de carpetas bajoapp/,server/yshared/); usa el texto sin guardar de los componentes abiertos; un componente que no compila conserva su última declaración y su error sale en ese archivo, un.meekovacío se trata como «en edición», y cada comprobación sustituye todos los diagnósticos de la anterior, también los de un fallo. meeko devcon un Meeko enlazado ya no se reinicia al leer el build. En Windows, con la actualización de la fecha de último acceso activa (lo habitual), la primera lectura de cada archivo en una hora llega al observador como un cambio, así que cargar undist/de hace más de una hora o servir uno de sus módulos al navegador reiniciaba el servidor supervisado. Ahora sólo cuentan las escrituras, creaciones y borrados dedist/ypackage.json.MEEKO_LINK_WATCH=0avisa al reconstruir el Meeko enlazado. Sin supervisor,meeko devconserva el código de servidor de Meeko que cargó mientras Vite sirve al navegador eldist/nuevo, así que el HTML salía del build anterior y la hidratación usaba el nuevo, sin ningún aviso. Ahora escribe en la terminal, una vez por reconstrucción, que hay que reiniciarmeeko dev.- Dependencias de
@utilitiescon constantes en línea. Un módulo local cuyo export rolldown escribía en línea (una constante de texto o número) dejaba de figurar en los mensajesdependencyde la hoja, así que editarlo no regeneraba el CSS enmeeko dev. Ahora la hoja depende de todos los módulos locales que lee el bundle. <snippet>en el completado de una etiqueta de componente. Escribir<snjusto dentro de<AppCard>o<MeekoThemeToggle>ofrece<snippet name="…">y, al insertarlo, los nombres de los snippets del hijo. No se ofrece dentro de<MeekoLink>ni de un elemento, donde<snippet>esMEEK061.- Completado del
<script>de un componente sin nombres de Node ni del módulo compilado. Con@types/nodeen el tsconfig del proyecto, el completado del<script>y de la plantilla, que también se ejecutan en el navegador, ofrecía__dirname,__filename,require,processoBuffery autoimports de los módulos de Node (userInfodeosy denode:os). Además ofrecíarenderToString,renderWithState,hydrate,hydrateScopeycss, que no son APIs de Meeko sino los exports del propio componente compilado para su padre y el runtime. Ahora quedan fuera; los globales que el DOM también declara (setTimeout,fetch,URL…) siguen, y<script server>conserva los de Node. - Servidor de lenguaje al apagarse.
shutdowncancela lo pendiente y espera a que termine la comprobación de contratos en curso; lo que llega después se descarta, y escribir en el log, publicar diagnósticos o reenviarconsole.*con la conexión ya cerrada ya no lanza un error sin capturar (Connection is disposed).
[1.3.0] - 2026-10-27#
Los cambios de comportamiento que se notan al actualizar desde 1.2, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.2 a 1.3.
Añadido#
- Arrays expandidos como los objetos en el plugin de Prettier. Nueva opción
meekoPreserveArrayBreaksde@meekojs/meeko/prettier, activa por defecto: un array, un patrón de desestructuración o una tupla de TypeScript con un salto de línea entre[y su primer elemento se imprime con un elemento por línea aunque quepa enprintWidth, la regla deobjectWrap: "preserve"para los objetos; sin ese salto decide Prettier, así que para colapsarlo basta con unir[y el primer elemento. El plugin sustituye para ello el printerestreede Prettier (exportado enprinters), así que se aplica a todo el JavaScript y TypeScript que formatee Prettier con el plugin en la configuración, no sólo al script y a las expresiones de la plantilla de los.meeko. Ese printer delega en tiempo de ejecución en el printerestreedel Prettier que lo ejecuta, aunque sea otra versión 3.x que la que instala Meeko, así que confalsela salida es la de ese Prettier. Es un cambio de la salida del formato, que docs/stability.md permite en un minor (véase docs/editors.md). - HTML de confianza para
html="...":TrustedHtmlytrustHtml. La raíz@meekojs/meekoexporta el tipoTrustedHtml, unstringcon una marca que sólo existe en los tipos, ytrustHtml(html), que la pone y en ejecución devuelve el mismostring, sin coste.trustHtmlno sanea: sólo declara que el HTML es propio o ya está saneado (por ejemplo,trustHtml(DOMPurify.sanitize(input))). La marca llega intacta a la plantilla desde<script server>,loadyServerData<T>, porque el payload es JSON. No es un autoimport. Véase docs/api.md. - Orden de las clases en el plugin de Prettier. Nueva opción
meekoSortClassesde@meekojs/meeko/prettier("off"por defecto,"stylesheet"o"alphabetical"; en la CLI,--meeko-sort-classescon--plugin @meekojs/meeko/prettier) para el atributo estáticoclassde la plantilla. Con"stylesheet", comoprettier-plugin-tailwindcss, cada clase toma la posición de su primera aparición enapp/styles/main.css, procesada con la API de Vite como enmeeko dev(elpostcss.configdel proyecto,@importy@utilities), y después en el<style>del componente; las clases desconocidas van primero en su orden. No depende de quemeeko devesté en marcha: la hoja procesada se cachea por proceso mientras no cambie ningún archivo de su grafo. Si la hoja no existe o falla, no se ordena y se avisa una vez en la salida de errores. En un workspace no confiable de VS Code no se aplica, porque procesar la hoja ejecuta código del proyecto. Véase docs/editors.md. - Aviso
MEEK107.meeko check,meeko buildy el editor avisan cuando el valor dehtml="..."no es asignable aTrustedHtml | null | undefined: unstring,string | undefinedoany. No avisan los tipos literales de texto y sus uniones (html="'<br>'"), niTrustedHtml. Es un aviso: no hace fallar la comprobación ni cambia el SSR, la hidratación ni el código generado. - Hoja global procesada para el editor:
.meeko/styles/main.css.meeko devescribeapp/styles/main.csstal como la sirve en desarrollo (con los@importresueltos, las@utilitiesexpandidas y el resto de PostCSS aplicado, sin minificar) en.meeko/styles/main.css, en cuanto el servidor escucha y cada vez que cambian la hoja, un archivo que importa o un módulo de@utilitiesy su grafo. La sustituye de forma atómica y sólo si cambia el contenido, Vite no la vigila (no recarga la página) y, si la hoja no compila, conserva la última versión buena. Sinapp/styles/main.cssno hay archivo. Sirve para que extensiones como HTML CSS Support completen las clases generadas con"css.styleSheets": [".meeko/styles/main.css"];meeko buildno la escribe (véase docs/framework.md). - Completado de clases CSS en el editor. El servidor de lenguaje completa en
class="…"y en los literales de cadena debind:classlas clases del<style>del componente (también sin guardar), de los<style>sinscopedde los demás componentes (de uno conscoped, sólo las de:global(…)) y de la hoja global: la quemeeko devescribe ya procesada en.meeko/styles/main.cssy, siempre,app/styles/main.csscon sus@importlocales, leída sin ejecutar PostCSS. Cada clase lleva su origen y su regla; el hover las muestra e ir a definición lleva al selector en el componente, en otro componente o en la hoja, o a la regla de la hoja procesada si sólo existe allí. Lee las reglas anidadas,@media,@layer,:is(),:where(),:not(),:global()y@utilityde Tailwind 4, guarda lo leído por archivo y funciona en workspaces sin confianza. El servidor observa además**/*.css(véase docs/editors.md). - Cada API en su sitio: error
MEEK108y avisoMEEK109.meeko check,meeko build(también el plugin de Vite enmeeko dev) y el editor señalan, con un mensaje que dice dónde se usa, lo que no existe donde está escrito. En el<script>y la plantilla de un componente, que también se ejecutan en el navegador, esMEEK108un autoimport de servidor (defineApiRoute,defineRpc,defineServerMiddleware,defineSocketChannel,defineServerRuntime,ApiErroro uno deserver/utils), un import de valores de@meekojs/meeko/servero/cloudflarey un import deserver/o de un módulo*.server.*;usePage,redirectynotFoundsin import danMEEK073, como ya ocurría al importarlos. En<script server>esMEEK108una macro del componente (defineProps,defineEmits,defineExpose,defineModel,defineSnippets,useElementRef) yonMounted. Todo eso ya fallaba: el autoimport no se resolvía en el bundle del navegador,/servery/cloudflarelanzan unTypeErroral cargarse allí, un import deserver/deteníameeko buildy las macros no existen fuera del<script>; ahora el error llega antes y en su línea. EsMEEK109, un aviso, lo que hoy compila y se carga:definePageen el componente, que no configura la ruta, y un import de valores de@meekojs/meeko/node,/bun,/deno,/passwordo/build, cuyas funciones sólo funcionan en el servidor. Losimport typesiguen admitidos y un componente dentro deserver/puede usarlo todo. El build ymeeko devsólo lo aplican al bundle del navegador, así que un componente fuera deserver/que sólo importa código de servidor (una plantilla de correo deapp/emails/que renderiza una ruta de API) sigue compilando. Véase docs/framework.md. - Fallos globales del navegador.
mountClientAppescuchaerroryunhandledrejectionenwindowmientras la aplicación está montada: las excepciones no capturadas y las promesas rechazadas sin manejar del código que no pasa por Meeko (unsetTimeout, unfetch().then()sincatch, una librería) llegan aonErrory aapp.onErrorde los plugins con las fuentes nuevas"uncaught"y"rejection"; sin ellos, al primerprovideErrorHandleractivo. Un fallo entregado llama apreventDefault(), así que el navegador no lo repite en la consola; sin observador, el navegador lo muestra como siempre. Un error que ya recibió un observador de Meeko no se informa otra vez, el"Script error."opaco de un script de otro origen se deja a la consola y los listeners se retiran condispose()o si la hidratación falla.ErrorSourcegana los dos miembros, como permite la política de estabilidad para las uniones que Meeko entrega: unswitchexhaustivo necesita rama por defecto (véase docs/framework.md). - Fallos fuera de una petición en el servidor:
processErrors.meeko dev, las salidas demeeko build(Node, Bun, Deno y Workers) yserveNodeyserveDenoconhandleSignalsatienden las promesas rechazadas sin manejar y las excepciones no capturadas del proceso. Por defecto, un rechazo llega alonErrordeserver/runtime.tsy el servidor sigue atendiendo (hasta ahora, Node, Bun y Deno terminaban el proceso, tambiénmeeko dev); una excepción llega aonErrory el servidor se cierra de forma ordenada (deja de aceptar conexiones, espera las respuestas abiertas, las tareas dewaitUntily el cierre de la aplicación) y sale con código 1.processErrorsenserver/runtime.tscambia la política (unhandledRejection: "report" | "shutdown" | "off",uncaughtException: "shutdown" | "off") o la desactiva confalse.onErrorrecibe esos fallos con un tercer argumento opcional,{ kind, inRequest }(tipoServerErrorInfo), y elrequestIdde la petición cuando el runtime conserva su contexto asíncrono (Node; Deno y Workers para los rechazos; Bun para las excepciones); sinonError, Meeko escribe el error completo en la consola. Si la aplicación registró sus propios listeners deprocess, Meeko sólo informa. En Workers sólo se informan los rechazos.handleProcessErrors(app, { close })aplica la misma política a un host propio. Nuevos tiposProcessErrorPolicy,ProcessErrorOptionsyServerErrorInfo(véase docs/framework.md). isClientError(error, code?)en@meekojs/meeko/framework: comprueba y estrecha unClientError, opcionalmente por código (isClientError(error, "UNAUTHENTICATED")), en los observadores que recibenunknown. Reconoce también elClientErrorde otra copia de Meeko en la página, dondeinstanceoffalla.- Logs del servidor en la consola del navegador. En
meeko dev, lo que el código de servidor escribe conconsole.log,info,warn,errorodebugmientras atiende una petición aparece también en la consola del navegador, agrupado por petición (método, ruta yrequestId) y marcado con la insigniaserver, comofeatures.devLogsde Nuxt; la terminal lo sigue mostrando igual. Cada log pertenece a su petición, también con peticiones simultáneas, y llega una sola vez: en el documento HTML, en los chunks derender: "stream", en el payload de una navegación cliente o, para API, RPC y el trabajo posterior a la respuesta, por el WebSocket de HMR. Los objetos se despliegan como en el servidor (Map,Set,bigint, ciclos) y losErrormuestran la traza del servidor y sucause, con límites de profundidad y tamaño que nunca rompen la respuesta ni el log. Sólo llega en la respuesta al navegador del desarrollador (peticiones desde loopback, a un host de loopback y sin cabeceras de proxy, así que tampoco a los visitantes de un túnel como ngrok o cloudflared), los scripts que inyecta llevan el nonce del documento y no existe enmeeko buildni enmeeko start. Se desactiva con la nueva opcióndev: { serverLogs: false }demeeko.config.ts(véase docs/framework.md). - Adaptador
static.meeko build --adapter static(oserver: { adapter: "static" }) escribe en.meeko/output/static/un sitio sin servidor para cualquier hosting estático: los assets ypublic/, cada página prerenderizada como<ruta>/index.htmlcon su payload<ruta>/index.meeko.json, y404.htmldesdeapp/error.meeko. El cliente de este build navega leyendo esos archivos, sin la negociación deAcceptque un hosting estático no hace (ClientAppOptions.serverDataacepta"static"). El build falla, antes de compilar, con la lista de lo que necesita un servidor: páginas que no sonrender: "prerender", API, RPC, Socket, middleware de servidor y handlers de servidor de los módulos.meeko previewsirve la salida como un hosting. Nuevo tipoStaticHeaderFile(véase docs/framework.md). sitemap.xmlyrobots.txt.sitemap: true | { exclude }yrobots: true | { allow, disallow }enmeeko.config.tslos generan en el build, con las páginas prerenderizadas (salvo lasnoindex) y las rutas sin parámetros, para todos los adaptadores; en Node, Bun, Deno y Workers se sirven desde el manifiesto de assets. Nuevos tiposSitemapConfigyRobotsConfig(véase docs/framework.md).siteenmeeko.config.ts: el origen público de las URL absolutas que escribe el build (MEEKO_PUBLIC_ORIGINsin él). No cambia las comprobaciones deOrigin.- Canónica y Open Graph por página:
document.seo. Añade<link rel="canonical">,og:url,og:titleyog:descriptiona cada documento a partir del origen, la ruta, el título final y la descripción; una página que declara la etiqueta la sustituye, las páginas de error no tienen URL canónica y la navegación cliente las actualiza. Nuevo tipoDocumentSeo(véase docs/framework.md). - CSP con hashes.
prerender/manifest.jsonguarda enscriptHashesel SHA-256 de los scripts en línea de cada página prerenderizada;inlineScriptHashes(html)de@meekojs/meeko/buildlos calcula para cualquier documento y señala el JavaScript que un hash no puede permitir (on*,javascript:), ycontentSecurityPolicy(policy, hashes)de@meekojs/meeko/serverconstruye la cabecera. El adaptadorstaticlo aplica conserver.contentSecurityPolicyy escribe la cabecera, conserver.headers, en_headers(Netlify, Cloudflare Pages) o_meeko/headers.conf(nginx) segúnserver.headerFiles. Nuevos tiposContentSecurityPolicyeInlineScriptReport(véase docs/framework.md). - Carpetas de autoimports de
app/yshared/:imports.appDirseimports.sharedDirs. Junto aimports.serverDirs,meeko.config.tsadmite carpetas de autoimports dentro deapp/(appDirs: ["app/services"], con el ámbito deapp/: navegador y SSR, noshared/) y dentro deshared/(sharedDirs: ["shared/validators"], con el ámbito deshared/). Se leen como las carpetas por defecto: de forma recursiva, en el proyecto y en cada layer, sin*.test.ts,*.spec.tsni*.d.ts, con los sufijos.client.ts,.server.tsy.shared.ts,import typepara los tipos, prioridad sobre los autoimports del framework y el mismo error de colisión; sólo aportan exports, no componentes.meeko check,meeko build,meeko dev(que recarga al crear, editar o borrar sus archivos), las declaraciones de.meeko/types/autoimports/y el editor (también el lector estático de un workspace no confiable) las ven, yMEEK108trata un*.server.tsdeappDirscomo código de servidor. Una ruta fuera de su carpeta (appDirs: ["shared/x"],.., una ruta absoluta) es un error demeeko.config.tsque nombra la entrada,imports.appDirs[0] must be a relative path inside app/: shared/x, que el editor marca en ella (MEEK_CONFIG); una entrada no válida deserverDirs, que ya se rechazaba al leer los autoimports, da ahora el mismo error, y unimportsque no es un objeto también es un error. Véase Carpetas de autoimports.
Cambios#
- El plugin de Prettier normaliza el atributo estático
class. En la plantilla de los.meeko, el valor de cadaclass="…"estático de un elemento pierde los espacios del principio y del final, sus espacios, tabuladores y saltos de línea quedan en uno y sus clases repetidas desaparecen (se conserva la primera), como ya hacía Prettier con el HTML suelto. Siempre activo y sin opción: no cambia las clases del elemento, porque los atributos estáticos de Meeko no interpolan. No tocabind:class, elclassde una etiqueta de componente (una prop estática) ni un valor con referencias de carácter. Es un cambio de la salida del formato, que docs/stability.md permite en un minor:prettier --checkpuede marcar archivos que antes pasaban. - Declaraciones de
@meekojs/meeko/postcsstipadas con csstype. El objeto de declaraciones derule()yatRule()(UtilityDeclarations) usa los tipos de csstype: el editor autocompleta las propiedades estándar y con prefijo de proveedor, en camelCase y en kebab-case, y las palabras clave de cada valor, y una propiedad mal escrita (backgroundColr) es un error de tipos. Las longitudes y los tiempos son cadenas con unidad salvo el0(padding: 4, que el plugin escribía sin unidad y el navegador descartaba, es ahora un error); los números siguen valiendo enopacity,zIndex,flexGrow,lineHeight… Siguen compilando las propiedades--*, los arrays de respaldo (tambiénas const),undefined,!importanty las claves dinámicas deObject.fromEntries. Sin la firma de índice[property: string], leer las declaraciones con una clavestring(decls[key]) es el errorTS7053connoImplicitAny: useObject.entries(decls)o una clavekeyof UtilityDeclarations.atRule()acepta además los descriptores de@font-face,@property,@page,@counter-style,@font-palette-valuesy@view-transitioncon el nuevo tipoUtilityAtRuleDeclarations.csstypepasa a ser una dependencia del paquete, sólo de tipos; el CSS generado no cambia. Lo que csstype no conozca se escribe concss`…`. Es una excepción aprobada de SemVer, sólo de tipos, registrada en docs/stability.md. definePageresuelve las llamadas inferidas con una sola firma. Una firma nueva, la primera, cuya opción es la unión dePageDefinitionyClientPageDefinition, atiende las llamadas sin argumentos de tipo. Con unrendertodavía incompleto, el editor completaba la opción contra la primera de las cuatro sobrecargas (render: "client"con y sinloady las de servidor) y sólo ofrecía"client"; ahora ofrece"ssr","stream","prerender"y"client", en<script server>y en los sidecars. La inferencia no cambia: el contexto deloadsegún el modo, los datos dehead, elloadobligatorio en el resultado (que leemeeko check) y el error de tipos derender: "client"conprerenderEntries, cuyo mensaje sí cambia: tras «No overload matches this call», señalarender,prerenderEntriesyloadcomo incompatibles. Las cuatro sobrecargas de 1.2 siguen detrás para las llamadas que escriben los argumentos de tipo hastaD(definePage<P, S, D, C>(…), la forma de tipar el contextoC), en las que TypeScript no infiere nada: el resultado sigue siendo el tipo del modo derender, conloadobligatorio si se declara. El hover muestra la firma nueva, con un parámetro de tipo más,T.- El autocompletado del
<script>y de la plantilla no ofrece APIs de servidor. Quedan fuera los exports de@meekojs/meeko/server,/node,/bun,/deno,/password,/buildy/cloudflare, los módulos deserver/y*.server.*, ydefinePage,usePage,redirectynotFound.<script server>las sigue ofreciendo. - JSDoc de las opciones de ruta.
renderdocumenta su valor por defecto ("ssr") y cada modo;params,search,prerenderEntries,loadyheaddePageDefinitionyClientPageDefinitiondicen cuándo se ejecutan y qué valida el servidor;PageBodyHeadincluye las etiquetas deuseHeaddesde1.2, yPageRequest.contextavisa de que lanza sin contexto de petición.
Correcciones#
- El
404.htmlde un sitio estático hidrata y navega en el cliente. Un documento con el payload de error deapp/error.meekoya no lanzaMeeko route hydration mismatchcuando su URL coincide con una ruta, como/docs/xcon/docs/[slug]sin esa entrada prerenderizada: hidrata la página de error y sus enlaces navegan en el cliente en lugar de recargar.
Deprecado#
html="..."con unstringsin marcar. Sigue funcionando igual durante 1.x, con el avisoMEEK107; en2.0será un error demeeko check. Reemplazo: marcar el HTML contrustHtml(…)donde se produce, después de sanearlo si viene de usuarios, o usar{expr}, que escapa. Véase docs/stability.md.definePagey los imports de valores de@meekojs/meeko/node,/bun,/deno,/passwordy/builden el<script>o la plantilla de un componente. Siguen compilando durante 1.x, con el avisoMEEK109; en2.0serán un error. Reemplazo: las opciones de ruta en<script server>o en un sidecar*.route.ts, y el código de servidor en<script server>o enserver/, conimport typepara los tipos. Véase docs/stability.md.
Documentación#
- Nueva sección Errores en la guía del framework: qué captura cada nivel (componente, aplicación, página, API y RPC, runtime del servidor y proceso), con su observador y su política. Las pruebas confirman que un fallo del render SSR o de una página
render: "stream"y un diferido rechazado de un stream (en el documento y en la navegación) llegan a+error.meekoo al cliente como{ code, status }, y alonErrordel runtime y de los plugins con elrequestIdde la respuesta.
[1.2.0] - 2026-10-27#
Los cambios de comportamiento que se notan al actualizar desde 1.1, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.1 a 1.2.
Añadido#
- Head del documento:
document,useHeadyuseSeoMeta.documentenmeeko.config.tsfijalangy el<head>común de todos los documentos (SSR,"stream","prerender", errores y el shell de"client").useHead(input)añade título,titleTemplate,meta,link,script,styley atributos de<html>y<body>desde páginas, layouts,app.meeko, componentes, composables y el nivel superior de<script server>; con un getter o una señal, el navegador actualiza el documento.useSeoMetaes el atajo tipado para descripción, Open Graph, Twitter ycanonical. Las fuentes se combinan con una prioridad y una deduplicación documentadas, el servidor las escribe entre<!--meeko:head-->y<!--/meeko:head-->con el nonce CSP, la hidratación las adopta sin duplicar y la navegación restaura lo que sustituía una página. Nuevos tiposHeadInput,HeadMeta,HeadLink,HeadScript,HeadStyle,SeoMetaInput,DocumentConfigyDocumentHead. - Módulos.
defineMeekoModulecrea módulos que configuran el proyecto desde código, como los de Nuxt: componentes, autoimports, plugins, middleware y handlers de servidor, layers, entradas de<head>(meeko.addHead, antes deldocumentde la configuración), archivos públicos, plugins y configuración de Vite, y hooks del build.modulesacepta un módulo, un paquete, una ruta o[módulo, opciones]; las opciones se combinan condefaultsy la clavemeta.configKeydemeeko.config.ts, ymeta.compatibilitycomprueba la versión de Meeko. Elsetupse ejecuta sólo en Node y nunca llega al navegador. - Plugins de aplicación.
definePluginenapp/plugins(o añadido por un módulo) se ejecuta en orden antes de renderizar en el servidor y antes de hidratar en el navegador, conprovidepara todos los componentes yonErrorcomo observador de la aplicación. Los sufijos.servery.clientlimitan un plugin a un lado. layers. Nuevo nombre deextends(que sigue funcionando como alias) para las carpetas con la estructura de la aplicación; sus páginas se muestran dentro del shell del proyecto y los layouts propios se anidan dentro.- Props reactivas. Cuando el padre cambia una prop, el hijo sigue montado y sólo se actualizan los bindings que la leen: se conservan el DOM, el foco, el cursor y la composición de un
<input>, el estado, los snippets y los hijos anidados.propses una vista de sólo lectura (escribir en ella lanzaTypeError) y los defaults se aplican en cada lectura. Se siguen montando de nuevo los cambios de<component is>, de modelos o de presencia de snippets, y los componentes con unawaitde datos en el nivel superior. El SSR y la hidratación no cambian (véase docs/api.md). - Aviso
MEEK101: props leídas una sola vez.meeko check,meeko buildy el editor avisan cuando el setup desestructura o expandeprops, o leeprops.xen su nivel superior o directamente enonMounted: con las props reactivas, ese valor no sigue a la prop. Es el primer diagnóstico de tipo aviso:CompileDiagnosticganaseverity: "warning"yCompileResultganawarnings, y un aviso no hace fallar la comprobación. No avisan las lecturas en funciones,computedoeffect, las llamadas a una prop función,signal(props.x)ni los componentes con unawaitde datos en el nivel superior.untrackse autoimporta, como el resto de la reactividad, para declarar una lectura única intencionada:untrack(() => props.x). .envdel proyecto.meeko dev,build,start,previewycheckcargan el.envde la raíz enprocess.env, como Vite y Nuxt en desarrollo:MEEKO_PORT,MEEKO_PUBLIC_ORIGIN,MEEKO_LINK_WATCHo las variables de la propia aplicación ya no hay que exportarlas en la terminal. El entorno real tiene prioridad, y la salida demeeko buildejecutada directamente no lee.env.<MeekoOutlet />y<MeekoLink>. Dos etiquetas propias que el compilador reconoce sin import.<MeekoOutlet />pinta, enapp.meekoy en los layouts, el layout o la página de dentro sin declarardefineProps<{ outlet }>()(sustituye a<component is="props.outlet">).<MeekoLink to="/users/[id]" bind:params="{ id }">renderiza un<a>con elhrefdehref(): sólo rutas existentes,paramsobligatorios en las dinámicas,aria-current="page"en la ruta activa y el resto de atributos (class,aria-*,data-*,on:click) en el<a>. Los errores se marcan ento; nuevos códigosMEEK104yMEEK105. El starter demeeko init-frameworklas usa.definePagey las definiciones de servidor sin import.definePage,defineRouteMiddlewareydefinePluginse autoimportan, y enserver/y<script server>tambiéndefineApiRoute,defineRpc,defineServerMiddleware,defineSocketChannel,defineServerRuntimeyApiError. El descubrimiento de contratos ya leía la llamada por su nombre, así que los contratos no cambian; las de servidor nunca llegan al código del navegador. La página demeeko init-frameworkdeja de importardefinePage.app/typesse autoimporta. Los tipos que sólo usa la aplicación (por ejemplo, las variantes de un botón) se usan sin import en componentes, páginas y módulos deapp/, en cliente y SSR, conimport type. Como el resto deapp/, no llegan ashared/; los tipos comunes con el servidor siguen enshared/types.- Navegación en el editor. Las cadenas de
href,useApi,useApiMutation,useRpcQueryyuseRpcMutationenlazan a su página o handler; la búsqueda de símbolos del workspace encuentra componentes, páginas, endpoints y procedimientos; y unas pistas en línea muestran el tipo del elemento deeach:asy de los parámetros de<snippet params>. - Acciones rápidas nuevas en el editor. Para
MEEK101, convertir la copia de una prop encomputed(con.valueen cada uso, también en la plantilla) o declarar la lectura única conuntrack; paraMEEK022, añadireach:ascon el singular de la lista, aunque el componente no compile; y, en una etiqueta de componente que no existe, crearlo enapp/componentscon una prop tipada por cadabind:. - HTML y CSS en el editor. El servidor de lenguaje completa las etiquetas, los atributos HTML y sus valores en la plantilla, con la documentación de MDN en el hover, y los eventos del DOM tras
on:en un elemento; renombra juntas la etiqueta de apertura y la de cierre y respondemeeko/tagCompletepara cerrarlas. En<style>, completado, hover, diagnósticos de CSS y colores. Enclass="…"completa las clases de<style>e ir a definición lleva a sus selectores; en<style scoped>marca como sin uso las clases que no nombran ni la plantilla ni el script (MEEK_UNUSED_CLASS); un prefijo antes de una interpolación (`badge--${tone}`) cubre las clases que empiezan por él. TypeScript sólo responde a/,<y las comillas donde significan algo, como en una ruta dehref("…"). - Completado de directivas en el editor. En el nombre de un atributo de la plantilla, el servidor de lenguaje ofrece
if:when,each:of,each:as,each:key,each:index,bind:,on:yref:en cualquier etiqueta, también en las de componente,htmlen los elementos,modelen los controles de formulario ymodel:en los componentes, sin repetir las que la etiqueta ya tiene. idempotencyKeyenapi.*yrpc.*. Una llamada puede enviar su propia cabeceraIdempotency-Key; un valor que no es UUID rechaza conTypeErrorsin enviar nada.onSuccessyonErrorenuseRpcMutationyuseApiMutation. Avisan de cada intento, también de los reintentos deretrySafe, coninput,idempotencyKeyytrigger. Nuevos tiposMutationAttemptyMutationFailure;MutationOptionsgana parámetros de tipo opcionales.- Duración propia de cada sesión.
context.session.create(data, { ttlMs })fija la duración de esa sesión (un entero positivo de milisegundos; uno no válido rechaza conRangeError), por ejemplo 30 días para los propietarios y 12 horas para el personal. Determina elMax-Agede la cookie y la caducidad en el store, se guarda en el campo opcionalSessionRecord.ttlMsyrotate()la conserva. Sin la opción, y en los registros anteriores, se usaSessionManager.ttlMs, como hasta ahora.SqlSessionStore(y con élD1SessionDriver) deduce la duración deexpiresAt - createdAt, sin migrar el esquema. Nuevo tipoSessionCreateOptions(véase docs/framework.md). origin: "required"endefineApiRouteydefineRpc. Exige unOriginpermitido (el de la petición opublicOrigin, o uno deSessionManager.allowedOrigins) aunque no haya sesión: en los métodosPOST,PUT,PATCHyDELETEde una ruta de API y en toda llamada RPC. Si falta o no está permitido, responde403 INVALID_ORIGINantes de validar la entrada. Protege los inicios de sesión frente al login CSRF sin comprobaciones a mano. Los clientes de servidor del SSR siguen funcionando (véase docs/framework.md).- Mensajes de validación en las respuestas
400.INVALID_PARAMS,INVALID_QUERY,INVALID_BODYeINVALID_INPUTañadenissues: [{ path, message }]con los mensajes del schema (como mucho 20;pathson las claves unidas con., o""). No viajan valores, expectativas ni trazas, y los fallos deresponse/outputsiguen siendo500sin detalle. En el cliente,ClientError.issues(tipoValidationIssue, de sólo lectura,undefinedsi no hay) llega desdeapi.*,rpc.*,useApi,useRpcQueryyuseRpcMutation(véase docs/framework.md). localsendefineApiRouteydefineRpc.locals(context)se ejecuta tras Origin, CSRF,auth,authorizey la validación de la entrada, y su valor llega al handler comolocals, con el tipo inferido; unApiErrorque lance es la respuesta. Sinlocals, el handler recibeundefined.ApiRouteDefinitionyRpcDefinitionganan el parámetro de tipo opcionalL(véase docs/framework.md).- Eventos de plantilla tipados por elemento. En un elemento HTML o SVG, el manejador de
on:eventorecibe el tipo deHTMLElementEventMap(oSVGElementEventMap) para ese nombre, no sólo para los ocho eventos que se tipaban hasta ahora, yevent.currentTargettiene el tipo del elemento de la etiqueta, el mismo que daref:(HTMLInputElement,HTMLSelectElement,HTMLElementen un elemento personalizado…).on:input="(event) => query.value = event.currentTarget.value", el patrón de la documentación, ya pasameeko checky el editor sin conversiones. El tipo nuevo es más preciso que el anterior, así que un manejador que era válido lo sigue siendo; sólo una conversión explícita a otro elemento (event.currentTarget as HTMLSelectElementen un<input>) puede dar ahora un error de tipos. Las etiquetas de componente no cambian. Es una excepción aprobada de SemVer, sólo de tipos y sin cambio en ejecución, registrada en docs/stability.md. modelconbind:typeen<input>. El control se trata siempre como uno de texto (value, eninput, con una señalstring) aunque cambie el tipo, por ejemplo en un campo de contraseña que muestra su texto. Un tipo sin valor de texto (checkbox,radio,file,submit,reset,button,image) no se admite: el modelo no toca ni la señal ni el control, la hidratación lanza unTypeErrorsi el control ya lo tiene y, si lo toma después, el error llega al observador de errores más cercano.MEEK065ya no rechazabind:type, pero síbind:typejunto atype="..."en el mismo<input>;bind:multiplesigue rechazado (véase docs/api.md).useRoute()en@meekojs/meeko/framework, también autoimportado: devuelve la ruta confirmada que renderiza el componente, con una señal de sólo lectura por valor (tipoRouteState):url(URL relativa al origen, con el fragmento en el navegador),path,id(nullen la página404deapp/error.meeko),paramsysearch, con la misma forma que reciben las páginas. En SSR son los de la petición, también en layouts,app/app.meeko, rutas"prerender"y layouts de rutas"client"; el navegador hidrata con los mismos valores y los actualiza sólo al confirmar una navegación, también por el historial y al cambiar sólo el fragmento, y el render previo de la navegación ya ve la ruta de destino. Fuera de una aplicación lanza un error. Un export propio llamadouseRouteen las carpetas de autoimports tiene prioridad sobre el del framework, así que no se vuelve ambiguo. Véase Ruta actual.statusendefineApiRoute(200,201,202o204): responde, por ejemplo,201 Createdsin perder la validación conresponseni el tipo del cliente generado. Con204no hay cuerpo yapi.*resuelveundefined. El generador exige un literal ygenerateOpenApidocumenta el estado. Nuevo tipoApiSuccessStatusy nuevo códigoINVALID_RESPONSE_STATUSpara unstatusmal declarado.- Broadcast tipado desde el servidor. En el código de servidor (API, RPC,
<script server>, middleware y handlers de Socket), el autoimportsocketofrecesocket.<canal>.broadcast(sala, evento, payload), tipado por el contrato del canal y con las mismas comprobaciones quehub.broadcast. Fuera de una petición,createServerSocketRegistry(socketContracts, { hub: () => app.socket?.hub }). NuevoscreateServerSocketRegistry,ServerSocketRegistry,ServerSocketRegistryOptionsySocketBroadcaster. - Desestructuración en el
<script>.const { a, b: c = 1, ...rest } = useX()yconst [x, , ...ys] = pair()en el nivel superior. Cada nombre es un enlace del setup para la plantilla y los tipos; las señales desestructuradas no entran en la captura de estado de SSR y HMR, porque pertenecen al composable que las devolvió.definePropsyuseElementRefsiguen necesitando un nombre simple (MEEK015, con un mensaje específico). if:whenestrecha tipos enmeeko checky en el editor, comov-if:<p if:when="user.value">{user.value.name}</p>ya no necesita!. Alcanza a atributos, props, eventos, contenido, listas y snippets anidados, sin cambiar el código de ejecución.- Utilidades CSS generadas con TypeScript:
@meekojs/meeko/postcss. Nuevo subpath con el plugin de PostCSSmeekoUtilities(). La at-rule@utilities "./utilities/color.ts" layer(utilities);(en la hoja global, en un archivo importado o en un<style>de un.meeko) se sustituye por las reglas que devuelve la función exportada por un módulo.ts,.mts,.jso.mjs, relativo a esa hoja.defineUtilitiestipa la función, que puede serasyncy recibe los helpersrule,atRule(para@media,@supports…) ycss, con anidamiento, declaraciones como objeto (camelCase a kebab-case,--*sin cambios,undefinedomite) ylayer(x)para envolver el resultado en@layer x. Con Vite 8 el módulo se empaqueta con rolldown, el bundler que Vite ya incluye, así que editarlo, o editar un módulo local que importa, actualiza el CSS enmeeko devsin reiniciar. Los errores (módulo inexistente, export que no es una función, una excepción, un selector o un valor no válidos) se muestran en la línea del@utilities. Va antes de@tailwindcss/postcss, y avisa si Tailwind se ejecuta primero; nunca forma parte del runtime (véase docs/framework.md). - Segmentos dinámicos invocables en
api.*.server/api/menu/categories/[id]/archive.post.tsse llamaapi.menu.categories({ id }).archive.post(), y los segmentos se encadenan:api.orgs({ org }).users({ id }).get(). La llamada exige el parámetro de su segmento y los métodos que siguen ya no recibenparams;query,bodyysignalno cambian y la URL se construye igual. Un catch-all recibe la ruta unida por/. El editor completa la llamada y lleva al handler.useApino cambia. useForm(), también autoimportado: una señal por campo paramodel/model:value, validación al enviar con cualquier Standard Schema, losissuesde un400en cada campo (errors, con rutas como"items.0.name") y el resto enerror, másfailure,pending,submitted,dirtyyreset().form.submit(event)hacepreventDefault, ignora un segundo envío en curso y no envía en SSR. Nuevo tipoForm.useStorage(clave, inicial, opciones?), también autoimportado: una señal guardada en JSON enlocalStorage(osessionStorage), con un schema opcional para validar lo leído. El SSR y la hidratación usaninicialy el valor guardado llega justo después de hidratar, antes deonMounted; otras pestañas se sincronizan por el eventostorage; un almacenamiento bloqueado nunca lanza. Nuevos tiposStorageOptionsyStorageArea.- Mutaciones seguras ante reintentos.
useRpcMutation(proc, { retrySafe: { key } })y el nuevouseApiMutation(ruta, método, opciones?)guardan la intención y la envían con la cabeceraIdempotency-Key; sin respuesta (red,5xx,408,429) la conservan enuncertainy la reintentan con la misma clave al volveronline, al montarse o conretry();discard()la olvida. En el servidor,context.idempotencyKeyexpone la clave validada (Meeko no deduplica). NuevoscreateUseApiMutation,ApiMutation,ApiMutationInput,ApiMutationContract,UseApiMutation,MutationOptions,RetrySafeOptions,RetrySafeMutationyUncertainMutation. meeko devse reinicia al reconstruir un Meeko enlazado (link:de pnpm,npm linko un workspace): vigiladist/ypackage.jsondel paquete y, cuando el build termina, cierra el servidor anterior y arranca otro en el mismo puerto.MEEKO_LINK_WATCH=0lo desactiva.- Puerto libre en
meeko dev. SinPORTniserver.port, usa el primer puerto libre desde4173(hasta4192), avisa del cambio y deriva de él el HMR y el puente con el editor. Un puerto fijado que está ocupado termina con un mensaje que lo nombra. - Sentencias en el setup. El nivel superior del
<script>admite cualquier sentencia de expresión (count.value = props.initial,void Child) yif, bucles,try,switch, bloques y sentencias etiquetadas, como la continuación de navegador.useElementRef,useApi,useRpcQueryyuseRpcMutationdentro de ellas danMEEK054. css.scopeAttributeenmeeko.config.ts(yscopeAttributeencompile()): con"matched",<style scoped>ponedata-meeko-s-<hash>sólo en los elementos que algún selector con ámbito puede seleccionar, con los mismos estilos y menos HTML (en el sitio, 2.650 → 2.216 atributos). En 1.x el valor por defecto sigue siendo"all"; será"matched"en 2.0.- Sesiones sin configuración. Sin
server/runtime.ts, o si no devuelvesessions, Meeko crea las sesiones: en memoria en desarrollo y, en producción (Node, Bun y Deno), en Redis conMEEKO_REDIS_URL. Importa el paqueteredisdel proyecto, conecta un cliente y un suscriptor de revocaciones antes de servir, usa el prefijoMEEKO_REDIS_PREFIXomeeko:<name>:prod:y cierra ambas conexiones al detenerse; sin conexión, las peticiones fallan en el acto.MEEKO_SESSION_TTL(12h,7d…) yMEEKO_SESSION_SAMESITE(LaxoStrict) ajustan esas sesiones. Sin store persistente, la producción sigue sin arrancar, y unassessionsexplícitas siempre ganan.ServerRuntime.sessionspasa a ser opcional yServerAppOptionsganaappNameyloadRedis, que pasa la entrada generada. El starter demeeko init-frameworkya no creaserver/runtime.tsy añade.env.example(véase docs/framework.md). - Contraseñas:
@meekojs/meeko/passwordymeeko auth hash.hashPassword,verifyPasswordyneedsRehashusan scrypt (N = 2^17, r = 8, p = 1) en el formato autodescriptivoscrypt$<N>$<r>$<p>$<sal>$<hash>, comparan en tiempo constante, limitan a dos los cálculos simultáneos y aceptan los hashes con ese formato escritos antes a mano. La subruta importanode:crypto(Node, Bun y Deno), así que/serversigue siendo portable.meeko auth hashpide la contraseña sin eco (o la lee de stdin) e imprime sólo el hash (véase docs/framework.md). rateLimitendefineApiRouteydefineRpc.{ limit, window, key? }limita las peticiones por ventana fija y responde429 RATE_LIMITEDconRetry-After, tras Origin y CSRF y antes deauth, la entrada y el handler. Sinkey, cuenta por IP de cliente (IPv6 por /64) o, si no se conoce, en un contador común de la ruta. Los contadores viven en el store de sesiones: memoria o Redis, con un script atómico bajo el mismo prefijo y la clave resumida con SHA-256; si el store falla, la petición se rechaza. Nuevo método opcionalSessionStore.rateLimit(ySessionManager.rateLimit), implementado porMemorySessionStoreyRedisSessionStore; nuevos tiposRateLimitOptionsyRateLimitHit.RequestContext.clientAddressexpone la IP: la de la conexión (los adaptadores de Node, Bun y Deno la entregan; en Workers,CF-Connecting-IP) o, conMEEKO_TRUST_PROXY(número de proxies otrue), la deX-Forwarded-For, que sin esa variable nunca se lee.BunServerLikeganarequestIPopcional (véase docs/framework.md).- Datos de sesión tipados:
MeekoSession. Una aumentación de módulo (declare module "@meekojs/meeko/server" { interface MeekoSession { user: string } }) tipacontext.session.current.dataycontext.session.create(data)en el<script server>de las páginas (usePage().context), API, RPC, Socket,authorizeylocals, sinas. Sin declaración, los datos siguen siendounknown.RequestContext.sessionpasa a serSessionHandle<SessionData>ySessionHandletomaSessionDatacomo tipo por defecto; nuevos tiposMeekoSessionySessionData.
Cambios#
- Un marcador de hidratación por elemento. Cada elemento con bindings lleva un único
data-meeko="N"en lugar de un atributo por binding (data-meeko-a-N,data-meeko-e-N,data-meeko-m-Nydata-meeko-r-N): el HTML pesa menos y el runtime busca el elemento una sola vez para todos sus atributos, eventos, modelos y referencias. Los atributos son internos; sólo cambia algo para quien los leía desde CSS o pruebas. <MeekoLink>estático conserva suhrefen el HTML. Con untosin segmentos dinámicos nibind:params/bind:search, el SSR escribe elhrefdirectamente y sóloaria-currentqueda enlazado, así que el enlace funciona sin JavaScript y lo siguen los rastreadores.- Resolución de módulos
Bundler.meeko check, el editor y lostsconfigque genera Meeko usan"module": "ESNext"y"moduleResolution": "Bundler"en lugar deNodeNext: un import relativo ya no necesita.js(import { format } from "../utils/format"), como en Vite y Nuxt. Los imports con.jssiguen funcionando ymeeko init-frameworkgenera eltsconfig.jsonnuevo; un proyecto existente puede cambiar esas dos líneas de sutsconfig.json. - Lo que el setup copia de una prop conserva el valor inicial. Con las props reactivas,
const { label } = propsoprops.x.toUpperCase()en el setup ya no se recalculan: hasta1.1el hijo se reconstruía con cada cambio. Use{props.x},computedo una función enuseApi/useRpcQuery. Es una excepción aprobada de SemVer, registrada en docs/stability.md. - Estado de las mutaciones. Cada envío vacía
erroral empezar; el resultado escribedataoerror,pendingyuncertainen una sola actualización, ydiscard()vacíaerror. - Props opcionales y
undefined. Una prop opcional aceptaundefineddesdebind:también conexactOptionalPropertyTypes(bind:error="cond ? 'x' : undefined"), y el hijo recibe su default dedefineProps. Las props obligatorias y las desconocidas siguen siendo errores. defineSocketChannelacepta la forma abreviada de propiedades (serverToClient: { kitchenChanged }) con schemas importados deshared/.- Diagnósticos de contratos en español. Los del descubrimiento y la generación de contratos de API, RPC y Socket (
meeko routes,meeko check,meeko dev). socket.<canal>en el servidor. Leerlo ya no lanza un error; lo lanzanon,emit,stateyclose, porque en el servidor no hay conexión.- El manifiesto rechaza segmentos ambiguos del cliente de API: un segmento estático con el mismo nombre que el segmento dinámico de su nivel (
users/byId/junto ausers/[id]) o dos parámetros que sólo cambian en la inicial. - Servidor de lenguaje y extensión de VS Code: los cambios en paquetes enlazados dentro de
node_moduleso en proyectos bajo un.meeko/ya no disparan comprobaciones de contratos en ellos, y los valores en vivo ya no se conectan ameeko devde esos proyectos. - Repositorio como workspace de pnpm. El paquete vive ahora en
packages/meeko, junto a la extensión de VS Code (packages/vscode), la de Chrome DevTools (packages/devtools) y el sitio (apps/site). Nada cambia para quien instala@meekojs/meekodesde npm; un proyecto que enlaza un checkout local conlink:debe apuntar a<checkout>/packages/meeko. - Prefijo
MEEKO_en las variables de entorno. Todas las variables que lee Meeko empiezan porMEEKO_:MEEKO_HOST,MEEKO_PORTyMEEKO_PUBLIC_ORIGINsustituyen aHOST,PORTyPUBLIC_ORIGINenmeeko dev,startypreview, en las salidas de Node, Bun y Deno y en el Worker (varsdewrangler.jsonc).PORTsigue valiendo sin aviso, porque la inyectan plataformas como Coolify, Railway o Cloud Run. El mensaje de puerto ocupado proponeMEEKO_PORT=<otro puerto>. El prefijo queda reservado: las variables propias de la aplicación deben usar otro nombre.
Seguridad#
- Login CSRF bloqueado por defecto en
session.create(). En una peticiónPOST,PUT,PATCHoDELETE,context.session.create()sólo crea la sesión si suOriginya se comprobó (origin: "required"o una mutación autenticada que superó Origin y CSRF) o está permitido en ese momento; si no, rechaza con403 INVALID_ORIGINsin tocar el store. Así, otra web no puede iniciar sesión en el navegador de la víctima aunque la ruta no declareorigin: "required". Los navegadores envíanOriginen esas peticiones, así que las aplicaciones web no cambian. Migración: un script, una prueba o una app nativa que inicia sesión debe enviar elOriginde la aplicación, o la ruta declararorigin: "none"(nuevo, documentado como inseguro).GETsigue sin comprobarse, para los callbacks de OAuth y los enlaces mágicos. Una aplicación detrás de un proxy que termina TLS y sinMEEKO_PUBLIC_ORIGINve su inicio de sesión rechazado con403; esa aplicación ya fallaba en1.1en las mutaciones autenticadas, y se corrige igual, fijandoMEEKO_PUBLIC_ORIGIN. Es una excepción aprobada de SemVer por seguridad, registrada en docs/stability.md (véase docs/framework.md). rateLimitconserva la IP del cliente tras un middleware que sustituye la petición. Con un middleware de servidor habitual comoreturn next(new Request(request, { headers })), la petición nueva perdía la IP de la conexión: todos los clientes compartían el contador común de la ruta y uno solo podía bloquear el inicio de sesión de los demás.next()copia ahora la IP a la petición que recibe, igual que las peticiones que creancreateLocalCallsy el handshake de Socket (también el decreateNodeSocketUpgradeHandler).
Correcciones#
meeko.config.tsreescrito dos veces seguidas. La configuración se importaba con la fecha de modificación del archivo en la URL, así que dos escrituras dentro del mismo instante (un guardado rápido del editor) volvían a cargar la versión anterior desde la caché de módulos. Ahora la clave es un hash del contenido.- Errores del prerender legibles. Si una página lanzaba durante el prerender de
meeko buildomeeko prerender, la salida sólo decía[meeko] request error <id> (Error)y que la ruta respondió500, sin mensaje ni pila. Ahora el build falla conPrerender <url> (<archivo de la ruta>) failed:seguido del error con su pila y la cadena decause(«Caused by: …»), y el error original queda encause.prerenderRouteshace lo mismo cuando suhandlerrechaza. La política de las peticiones en producción no cambia: el observador por defecto sigue registrando sólo el nombre del error y el ID de la petición, salvo en desarrollo. titleTemplatededocument.headtipado como cadena.document.headdemeeko.config.tsymeeko.addHeadde los módulos son ahora del nuevo tipoDocumentHead, en el quetitleTemplatesólo es una cadena: una función, que el build no puede fijar como JSON y la ejecución ya rechazaba, es un error de TypeScript en la línea detitleTemplate, en el editor y enmeeko check. Si la configuración no carga,meeko checkymeeko buildmuestran primero los errores de tipos demeeko.config.tscon su línea y columna, ymeeko.addHeadvalida su entrada al llamarse, con el nombre del módulo en el error.useHeadsigue admitiendo la función.- El título del sitio no pasa por la plantilla. Con
title: "Mi Sitio"ytitleTemplate: "%s - Mi Sitio"endocument.head, una página sin título mostraba «Mi Sitio - Mi Sitio». Eltitlededocument.head(y el demeeko.addHead) es ahora el título del sitio: se muestra tal cual en las páginas sin título propio, y la plantilla sólo se aplica a los títulos deapp.meeko, los layouts, las páginas,useHeady elheadde la ruta. Un título ganador vacío también muestra el del sitio, en lugar de « - Mi Sitio». Igual en SSR, al hidratar y al navegar (véase Head del documento). - Errores de
meeko.config.tsen su archivo. En el editor, un error de validación de la configuración aparecía en la línea 1 de cada componente abierto. Ahora se marca enmeeko.config.*, en la propiedad que nombra (por ejemplo,titleTemplatedentro dedocument.head), con el códigoMEEK_CONFIG, y desaparece cuando la configuración vuelve a cargar. - Declaraciones del proyecto en
meeko checky en el editor. La comprobación de cada.meekoy el servidor de lenguaje no incluían los.d.tsdeapp/,server/yshared/, así que una aumentación de módulo (comoMeekoSession) no llegaba al<script server>de una página ni a los.tsque importa, y daban errores falsos que los proyectos del servidor no daban. Ahora los incluyen. - PostCSS en los
<style>de componentes. El CSS de un componente era un módulo virtual cuyo id empezaba por un byte nulo, y los plugins de PostCSS que resuelven archivos fallaban con él: Tailwind con@referenceo@apply, o un@importrelativo, dejaban la página en error 500 («The argument 'path' must be … without null bytes»). Ahora el módulo esComponente.meeko?meeko-style=…&lang.css, junto al archivo del componente, como en Vue. - Servidor de lenguaje más rápido. Los primeros diagnósticos de un
.meekotardaban más de 20 s en un proyecto con dependencias enlazadas, y cada completado en el script, unos 6 s. La resolución de módulos recorríanode_modulesen cada import sin caché, y los autoimports volvían a leer elpackage.jsonde cada paquete y a calcular cada ruta de import. Ahora el proyecto comparte una caché de resoluciones (con la depackage.json) y la de rutas de import de tsserver, y devuelve el completado incompleto cuando TypeScript no resolvió todos los autoimports, como VS Code con TypeScript. En diegootayza y ordemi-meeko, los primeros diagnósticos bajan a unos 3 s y el completado, a menos de 1 s (0,1 s al repetirlo). - Imports que aparecen después. Crear el archivo de un import que no resolvía (o instalar su paquete) no quitaba el error «Cannot find module» hasta editar el archivo que lo importa: el servicio de lenguaje conservaba el programa con las resoluciones anteriores. Los cambios de archivos del proyecto, y ahora también de
package.jsony de los lockfiles, invalidan esas resoluciones. - Ventanas de consola en Windows. El servidor de lenguaje comprobaba los contratos de página con
tscen un proceso sinwindowsHide; como VS Code no tiene consola, Windows abría una ventana cada vez. Ese proceso y eltscdemeeko checkymeeko buildse lanzan ahora ocultos, y una prueba exigewindowsHideen todo proceso hijo. - Estilos globales duplicados en desarrollo.
meeko devcargabaapp/styles/main.cssdos veces: como<link>en el documento del servidor y como<style>que inyectaba el cliente de Vite. El<link>lleva ahora el id del módulo CSS (data-vite-dev-id), de modo que Vite lo adopta: no añade la copia y aplica el HMR sobre el propio enlace, sin que una regla borrada siga aplicándose desde la otra hoja. - Uniones en los clientes de API generados.
api.x.post({ body })acepta unbodytipado como la unión completa del schema (por ejemplo, unv.variantcon varias formas), y lo mismoparamsyquery; antes sólo compilaba un literal de una de las variantes. Una forma que no pertenece a la unión sigue siendo un error, y las reglas de argumentos opcionales no cambian.rpc.x(input),useApi,useRpcQueryyuseRpcMutation().mutateya aceptaban uniones y se comprueban ahora en las pruebas. <form>con un componente autocerrado. Un<Componente />dentro de un<form>hacía que</form>se compilara como texto y que el resto de la plantilla quedara dentro del formulario. El compilador enmascara ahora<form>mientras lee la plantilla, para que su cierre funcione como el de cualquier otro elemento.- Listas anidadas. Con tres o más
each:anidados, la hidratación (ymeeko check) leía en una lista interior el elemento de la lista que la contiene: cada lista nombraba su señal de elemento igual. Ahora cada lista usa nombres propios. - Orden de actualización de las regiones. Un
if:when="v.value"que protege{v.value!.n}(o un atributo, una prop o las filas de uneach:) lanzaba unTypeErroral volversenull, porque el contenido se actualizaba antes que la condición que debía ocultarlo. Los observadores se ejecutan ahora en orden de creación, y cada región reserva su lugar antes de hidratar su contenido, de modo que se actualiza (y libera ese contenido) antes que él. exportde valores en el<script>(export const,export function,export default) generaban código inválido; ahora danMEEK018.definePropsfuera de su propia declaración daMEEK016en vez de fallar en ejecución. Los autoimports ya no toman las etiquetas debreak/continuecomo referencias.aria-currenten un<MeekoLink>estático con caracteres no ASCII. Conto="/categorías", elhrefdel HTML era el literal sin codificar y nunca coincidía conuseRoute().path, que es elpathnamecodificado (/categor%C3%ADas), así que el enlace no recibíaaria-current="page". Elhrefestático se codifica ahora por segmentos como enhref(), y la comparación usa la misma codificación en ambos lados.- Componentes propios llamados
MeekoLinkoMeekoOutlet. El compilador interceptaba siempre esas etiquetas, así que un proyecto de1.1con un componente con ese nombre (autoimportado deapp/componentso de un módulo, o importado o declarado en el<script>) cambiaba de comportamiento o fallaba conMEEK104oMEEK105. Ahora gana el componente del proyecto, ymeeko check,meeko buildy el editor avisan con el nuevo avisoMEEK106para renombrarlo y adoptar la etiqueta propia. El completado del editor trata esa etiqueta como un componente. - Hoja de estilos de un componente que ya no existe en
meeko dev. Una pestaña abierta antes de reiniciarmeeko devpedía la hoja de estilos de un componente con un hash que el servidor no conocía; Vite leía entonces el.meekocomo CSS y PostCSS fallaba. Ahora se sirve una hoja vacía con un aviso que pide recargar la página, sin romperla. Además, el servidor de desarrollo sólo conserva las dos últimas hojas de cada componente, en lugar de todas las que generaba cada edición. - Teclas especiales en
meeko auth hash. El aviso de la contraseña sólo descartaba el carácterESC, así que una flecha (ESC [ A) añadía[Aa la contraseña y el hash no correspondía a lo que se creía escribir. Ahora descarta las secuencias de escape completas (flechas, Inicio, Supr, teclas de función, Alt+tecla, los marcadores del pegado entre corchetes y las secuencias OSC), también partidas entre lecturas, y cualquier otro carácter de control salvo Retroceso e Intro. X-Forwarded-Forcon puerto enrateLimit. ConMEEKO_TRUST_PROXY, una entrada con puerto, como la que escribe Azure Application Gateway (203.0.113.7:5678o[2001:db8::1]:5678), no se reconocía como IP y la petición caía en el contador común de la ruta. Ahora cuenta por su IP; una entrada que sigue sin ser una IP cuenta con la IP de la conexión, nunca con el contador común.MEEKO_TRUST_PROXYno válida en el entorno del adaptador.createServerAppya la validaba al arrancar, peroserveNode,createBunServeryserveDenosirven las peticiones con su propia plataforma (por defecto,process.env,Bun.envoDeno.env), y un valor no válido sólo ahí aparecía como500en cada ruta conrateLimity en cada página que llamaba a la API en local. Esos adaptadores la validan ahora al arrancar.createLocalCallsyrateLimit. Las llamadas locales contaban en un contador propio (método y ruta concreta), distinto del que usa el despachador HTTP, así que no compartían el límite. Ahora usan el mismo contador de la ruta y la IP delcontextque reciben. Un host propio que crea sus peticiones sin un adaptador de Meeko no tiene IP de conexión: está documentado en Límite de peticiones.
Deprecado#
- Carpetas de layer en
modules. Una carpeta conapp/pageslistada enmodulessigue funcionando como layer en toda la serie1.x, con un aviso; muévala alayers. Dejará de funcionar en2.0. byIdenapi.*.api.users.byId.get({ params: { id } })sigue funcionando durante 1.x y se retira en 2.0. Reemplazo:api.users({ id }).get().- Variables
HOSTyPUBLIC_ORIGIN. Siguen funcionando durante 1.x cuando falta la variable nueva, con un aviso por proceso, y dejarán de leerse en 2.0. Reemplazos:MEEKO_HOSTyMEEKO_PUBLIC_ORIGIN.PORTno está obsoleta: es el respaldo permanente deMEEKO_PORT.
Documentación#
- Guía de autenticación. docs/framework.md muestra la aplicación mínima:
.envconMEEKO_REDIS_URLyMEEKO_PUBLIC_ORIGIN, el tipo de la sesión, un inicio de sesión de diez líneas, el cierre y una página protegida. - Valibot, validador por defecto de la documentación. Meeko sigue aceptando cualquier Standard Schema (Valibot, Zod, ArkType…) sin cambios de API; la documentación lo explica en API y RPC y recomienda Valibot por ser pequeño y modular, y sus ejemplos y los de la referencia usan
import * as v from "valibot". - Todas las propiedades de los tipos públicos tienen descripción en el hover del editor, también las de
DevtoolsSnapshot,DevtoolsAssignmentSite,SocketUpgrade,PluginErrorInfooMeekoServerConfig; una prueba lo exige ahora para las interfaces y los tipos objeto exportados, como ya hacía con las clases.
[1.1.0] - 2026-10-26#
Los cambios de comportamiento que se notan al actualizar desde 1.0, con lo que hay que revisar en cada uno, se recogen en Actualizar de 1.0 a 1.1.
Añadido#
-
<script server>en las páginas. El bloque<script server lang="ts">declara las opciones de ruta (load,head,render,params,search,prerenderEntries) dentro del propio.meeko, sin un sidecar*.route.ts. El componente compilado nunca lo incluye: se ejecuta en el servidor con sus autoimports y sólo llega al navegador en las rutasrender: "client".meeko checklo comprueba con TypeScript en el ámbito del servidor y compara su loader con las props de la página, con los errores en la línea del.meeko; el servidor de lenguaje le da diagnósticos, hover, completado, navegación, renombrado, símbolos y formato, y la gramática de VS Code resalta el atributoserver. Los sidecars siguen funcionando, pero una página no puede declarar ambos.meeko init-frameworkgenera la portada con este bloque. Nuevos diagnósticosMEEK043(servercon valor) yMEEK044(bloque fuera de una página). En esas páginas,RouteRecord.sidecar(dediscoverRoutesy del manifiesto) es el id de módulo<página>.meeko?meeko-route&lang.ts, que sólo carga el plugin de Vite de Meeko, y no una ruta de archivo: una herramienta que leasidecardel disco debe tratar ese caso. -
Código de nivel superior en
<script server>. Las sentencias del bloque se ejecutan en cada petición (SSR y datos de navegación), después del middleware y de validarparamsysearch, conawaitde nivel superior. Cadaexport constoexport letes un dato de la página que el<script>y la plantilla ven por su nombre, tipado y de sólo lectura, sindefinePage({ load })niprops.data; sólo los exports llegan al navegador y se comprueban como JSON enmeeko checky en ejecución.export const headfija el<head>.usePage()devuelve{ url, params, search, request, context, signal }, tipado por los schemas deroute, yredirect(url, status?)ynotFound()responden con una redirección o con la página de error404; las tres se exportan desde@meekojs/meeko/frameworky se usan sin import en el bloque. Un bloque tiene código de nivel superior si exporta un nombre distinto derouteo usa esas funciones fuera de cualquier función; si no, se comporta como en1.0. Conrender: "stream", un export condeferllega después de la página; con"prerender", el bloque se ejecuta en el build. El editor tipa los exports en la página y lleva de su uso alexport. Nuevos tiposPageRequest,PageRequestOf,PageBodyHead,ServerData,JsonShapeySerializableExport, y nuevos diagnósticosMEEK070aMEEK076. -
Página de error de la aplicación
app/error.meeko, junto aapp/app.meeko, con props{ status, message }:- Una petición de documento (
accept: text/html) a una URL sin ruta responde404con esa página dentro deapp.meekoy el layout raíz, hidratable, en lugar del texto planoNot Found. Un enlace a una URL sin ruta sigue cargándose como documento, porque esa URL puede pertenecer a middleware de servidor, a un archivo o a una redirección. - Es el respaldo cuando falla una página sin
+error.meekomás cercano. - No puede coexistir con
app/pages/+error.meeko. Sinapp/error.meeko, el comportamiento de1.0no cambia./api,/_meekoy las peticiones que no son de página conservan sus404. - El payload de navegación de ese
404llevaerror: truey no llevaroute(véase docs/stability.md). Nuevos tipos y campos opcionales:ErrorPageRecord,errorPageenClientAppOptions,FrameworkHandlerOptionsyAppManifest, ydiscoverErrorPageen@meekojs/meeko/build.
- Una petición de documento (
-
href(id, options)sin el manifiesto. Usa el de la aplicación en ejecución: en el servidor, el de la petición en curso (SSR, loaders,head, prerender, API, RPC y middleware); en el navegador, el demountClientApp. Fuera de una aplicación lanza un error que indica pasar el manifiesto.href(routes, id, options)no cambia, y la acción rápida «quitarparams» del editor reconoce ambas formas. -
PageHead.description.headyexport const headpueden devolver la descripción de la página, que va en<meta name="description">en SSR y en el prerender y se actualiza al navegar. El payload llevahead.description. -
Etiquetas de componente con nombre en mayúscula.
<AppButton bind:label="x" />equivale a<component is="AppButton">, con un import o un autoimport deapp/components. Se cierran solas o con</AppButton>, como<component is="..." />, y su contenido son snippets del hijo (véasedefineSnippets);MEEK049, que rechazaba ese contenido, ya no se emite. Un</AppButton>sin apertura daMEEK008y una etiqueta de componente fuera de las secciones,MEEK002. El editor completa los nombres, lleva al.meekoy la gramática de VS Code los resalta. -
Props estáticas en componentes.
<AppButton variant="primary" disabled />pasa"primary"como literal de texto ytruepara un atributo sin valor, comprobados con tipos contra las props del hijo. La misma prop como texto estático y conbind:/on:esMEEK090.is,ref,htmly los nombres con guiones o con:que no son directivas siguen dandoMEEK032. -
<script lang="ts">opcional. Un componente o una página con sólo<template>, o con<script server>y<template>, compila con setup vacío y propsRecord<string, never>.MEEK005deja de emitirse. Las acciones rápidas de imports crean el<script>si falta, y Prettier no lo añade. -
Componentes genéricos.
<script lang="ts" generic="T extends { id: string }">declara parámetros de tipo que se usan endefineProps,defineEmits,defineModel,defineSnippets,defineExposey el resto del script. Cada etiqueta del padre infiere los parámetros de sus props (también de los manejadoreson:con parámetros anotados) y comprueba con ellos los manejadores, los modelos, los snippets y el valor de suref:. El módulo compilado exportaComponentProps<T>,ComponentModels<T>,ComponentSnippets<T>yComponentExposed<T>, y las declaraciones.d.meeko.tsconservan los parámetros. Límites: los parámetros sólo se infieren de las props, unaref:dentro deeach:ofusa la restricción, y un componente con snippets obligatorios no puede usarse con<component is>. El hover de la etiqueta muestra los parámetros resueltos (T = User), la gramática de VS Code resalta el valor degenericcomo TypeScript y Prettier lo conserva. Nuevo diagnósticoMEEK100: lista no válida, modificadores, nombres que empiezan por__o repetidos,genericen<script server>, en páginas, layouts,app.meekooerror.meeko, y tipos del script que usan los parámetros (véase docs/api.md). -
Eventos de componente. La macro
defineEmits<{ save: [id: number] }>()devuelve unemittipado y el padre escucha conon:save="handler"en la etiqueta del hijo. Cada evento es la prop de callback opcionalonSave, así quebind:onSavesigue funcionando yComponentPropsincluye esas props. Nuevos diagnósticosMEEK045(ubicación),MEEK046(sin argumento de tipo),MEEK047(argumentos en tiempo de ejecución) yMEEK048(on:saveybind:onSaveen la misma etiqueta); un nombre de evento de componente que no es un identificador daMEEK011. -
Modelos de componente. La macro
defineModel<T>()odefineModel<T>("nombre", { default })devuelve unaSignal<T>escribible que es la misma señal que pasa el padre conmodel="signal"omodel:nombre="signal", o una señal local con eldefault. Sindefault, el modelo es obligatorio. La expresión del padre debe ser una señal escribible; uncomputedes un error de tipos. Los modelos no son props: el módulo compilado exporta su tipo enComponentModels. Nuevos diagnósticosMEEK063yMEEK064(defineModel) yMEEK066(modelen componentes, incluidomodel:xjunto abind:x). -
model="signal"en controles de formulario:<input>de texto y<textarea>(value, eninput, con una señalstring; también los numéricos), checkbox (checked,boolean), radio (marcado si la señal vale suvalue),<select>(string) y<select multiple>(string[]). Asignar la señal actualiza la propiedad viva del control, comobind:valuedesde1.0.1, y un control editado antes de hidratar (o restaurado por el navegador) conserva el valor del usuario, que pasa a la señal al terminar la hidratación, antes deonMounted, así que las regionesif:,each:y<render>y los padres que leen la señal se hidratan sin desajustes. Nuevo diagnósticoMEEK065(elemento o tipo no admitido,bind:typeobind:multiple, o conflicto convalue,checked,bind:value,bind:checked,on:inputuon:change). -
Snippets. La macro
defineSnippets<{ default: []; title?: []; actions?: [close: () => void] }>()declara el contenido con nombre que un componente recibe,<render snippet="snippets.title" args="..." />lo pinta eif:when="snippets.title"comprueba si llegó uno opcional. En el padre, el contenido suelto de la etiqueta llenadefaulty<snippet name="actions" params="close">llena uno con nombre, con parámetros tipados; el contenido usa el ámbito del padre y sigue siendo reactivo. Los snippets no son props: el módulo compilado exporta su tipo enComponentSnippets, y@meekojs/meekoexporta el tipoSnippet. Nuevos diagnósticosMEEK059yMEEK060(defineSnippets),MEEK061(<snippet>) yMEEK062(<render>); unaref:dentro de un<snippet>daMEEK052. -
defineExpose(value)publica un valor del componente para el padre que lo nombra conref:. Cada módulo compilado exporta el tipoComponentExposed, y la función que devuelvehydrateScopellevaexposed. Las declaraciones.d.meeko.tsde un componente condefineExposepueden tipar parte del valor comoany;meeko checky el editor tienen el tipo exacto. Nuevos diagnósticosMEEK050(ubicación) yMEEK051(argumentos). -
Referencias a elementos. La directiva
ref:nombrey la macrouseElementRef("nombre"), tipada por la etiqueta (HTMLElementTagNameMap/SVGElementTagNameMap) o por eldefineExposedel componente. Sinawaitdevuelve una señal que valenullen SSR y mientras el destino no existe; conawaitdevuelve el elemento y el resto del setup continúa en el navegador; dentro deeach:ofdevuelve la lista de filas. Nuevos diagnósticosMEEK052(directiva inválida),MEEK053(nombre repetido),MEEK056(argumento o ref inexistente, oawaitsobre una ref de lista) yMEEK080(await useElementRef("x")sobre un componente sindefineExpose, que en ejecución rechaza con ese código cuando el componente se hidrata).ref="x"ybind:refse comportan como en1.0. -
onMounted(fn)en@meekojs/meeko, también autoimportado: se ejecuta una vez en el navegador tras hidratar o montar el componente, con sus refs ya asignadas, y nunca en SSR. Los callbacks esperan al final de la hidratación o del montaje en curso, cuando ya existen los hermanos posteriores y los bindings del padre; los hijos se ejecutan antes que el padre y una función devuelta se registra como limpieza.ErrorSourceañade"mounted"para sus fallos: unswitchexhaustivo sobre él necesita esa rama. -
each:keyopcional. Sin clave, la clave es el índice y cada fila se actualiza en su sitio. Se retiraMEEK023; el código existente no cambia. -
Directiva
html="expression"para insertar HTML sin escapar en un elemento: en SSR se normaliza en el contexto del elemento (se cierran las etiquetas abiertas y se descartan los cierres sobrantes, así que el contenido nunca sale del elemento ni imita marcadores de Meeko fuera de él; el que el navegador sacaría del elemento, como un<div>en un<p>, deja el elemento vacío y se llena al hidratar), la hidratación no toca un contenido que coincide (y sustituyeinnerHTMLsi difiere) y cada cambio del valor lo sustituye. Meeko nunca ejecuta los<script>del contenido al insertarlo en el navegador: ni al hidratar, ni al cambiar el valor, ni en un montaje tardío ni al navegar. Es insegura con contenido no confiable.MEEK095señala los usos no admitidos (hijos, elementos vacíos o de texto,<template>,model). El servidor de lenguaje la tipa (string | null | undefined) y explica su riesgo en el hover, Prettier formatea su valor como expresión y la gramática de VS Code lo resalta como TypeScript. -
awaitde nivel superior en el<script>sobreuseApi,useRpcQuery,useElementRef,useSocketyuseSocketClient. El setup se divide en el primerawaitde navegador (useElementRef,useSocket,useSocketClient): lo anterior se ejecuta en SSR y al hidratar, y lo posterior sólo en el navegador, una vez montado el componente; trasawait useSocket(...), con el socket ya abierto. La continuación admite, además de declaraciones, cualquier sentencia de expresión,if, bucles,try,switchy bloques.useElementRef,useApi,useRpcQueryyuseRpcMutationsólo se llaman en el nivel superior;useState,useSocketyuseNavigationsiguen pudiendo llamarse desde código anidado. Nuevos diagnósticos:MEEK054(composable fuera del nivel superior),MEEK055(awaitno admitido),MEEK057(la plantilla usa un nombre declarado tras el primerawaitde navegador) yMEEK058(macros tras eseawait); cualquier otra sentencia de la continuación daMEEK018. -
Composables de datos
useApi(ruta, opciones),useRpcQuery(procedimiento, input)yuseRpcMutation(procedimiento), autoimportados y tipados desde los contratos generados; devuelven{ data, error, pending, refresh }o{ mutate, data, error, pending }. Conawait, el SSR renderiza en varias pasadas hasta cargar los datos (como mucho 10), los envía en el campo opcionalresourcesdel payload y la hidratación los lee sin repetir la petición; una clave que falta daMEEKO_RESOURCE_MISSING. Sinawait, la petición empieza en el navegador al montar. Las señales de la entrada recargan los datos yrefresh()los vuelve a pedir. Un fallo de unawaitde datos muestra la página de error, como un fallo del loader. Un componente que se monta más tarde (bajo unif:, en una fila nueva deeach:, en otro<component is>o con props nuevas) y espera datos que no están en caché deja su zona vacía mientras los carga; si se libera antes, la petición se aborta, y si falla, el error va alprovideErrorHandlermás cercano o alonErrorde la aplicación con la fuente"mounted".renderToStringfuera del framework lanzaMEEKO_SUSPENDEDsi un componente espera datos. Las opciones deuseApi, elinputdeuseRpcQueryy las opciones de los métodos deapi.*se pueden omitir cuando el contrato no exige ningún campo;useRpcQuerysininputenvía{}.@meekojs/meeko/frameworkexportacreateUseApi,createUseRpcQuery,createUseRpcMutationy sus tipos. -
Plugin de Prettier
@meekojs/meeko/prettier(parsermeeko). Formatea<script>y<script server>como TypeScript,<style>como CSS y la plantilla como HTML, con los valores de las directivas y el texto{expr}formateados como expresiones y las etiquetas PascalCase intactas. Distingue los elementos personalizados con la misma regla que el compilador, así que los nombres SVG/MathML reservados con guion (<font-face>,<annotation-xml>…) son elementos estándar y suhtmlse formatea como expresión. La opciónmeekoIndentSections(ajustemeeko.format.indentSectionsen el editor, por defectofalse) indenta un nivel el contenido de cada sección, comovueIndentScriptAndStyle. El servidor de lenguaje formatea ahora con Prettier: el del proyecto y su configuración en un workspace de confianza, o el incluido; el formateador anterior queda sólo como último recurso. Sólo se formatea el documento completo y no un archivo que no compila.prettierpasa a ser una dependencia del paquete (véase docs/editors.md). -
Comando
meeko clean [directorio]: borra.meeko/y la caché de Vite; con--all, tambiénnode_modules; con--dry-run, sólo informa. Exige un proyecto Meeko, nunca sigue symlinks ni junctions (seguro conlink:de pnpm) y no toca lockfiles,.envni código. -
Funciones del editor en el servidor de lenguaje (véase docs/editors.md):
- modificadores de token semántico
signal,computed,resource,reactiveymodification, decididos por el tipo (props.valueno es una señal); - hovers de señales,
computedy recursos con sus lecturas, escrituras y cómo llegan al navegador; de componentes con sus props, eventos, modelos, snippets y valor expuesto (con los parámetros de tipo resueltos en un componente genérico); y de rutas dehref, endpoints deuseApi/api.*y procedimientos deuseRpcQuery/useRpcMutation/rpc.*, con ir a definición hasta la página o el handler; - CodeLens de lecturas y escrituras, y de la ruta, el render, los layouts y los datos que una página envía al navegador;
- acciones rápidas para añadir
each:keyy mover un sidecar a<script server>, y completado demodel:, de nombres de snippet y de sus parámetros; - zonas de ejecución con la petición propia
meeko/executionZones: las líneas que se ejecutan sólo en el servidor, en ambos lados o sólo en el navegador, incluidas las funciones del setup que sólo se usan desdeon:,onMountedo la continuación de navegador y las flechas de varias líneas de unon:*, con el motivo en el hover; - los usos de un símbolo marcado
@deprecated, como el aliasuseSocketde@meekojs/meeko/framework, se publican tachados (DiagnosticTag.Deprecated) como pistasTS6385oTS6387con el motivo de su etiqueta.
La petición
meeko/executionZonesy los modificadores de token propios son internos de la extensión de VS Code y pueden cambiar en un minor (véase docs/stability.md). - modificadores de token semántico
-
Valores en vivo en el editor.
meeko devpublica los valores de las señales de las páginas abiertas en un puente local (SSE yPOSTcon un token por sesión, sólo en loopback y con comprobación deHostyOrigin), que la extensión de VS Code muestra junto a cada declaración y permite editar. Cada servidor escribe su descriptor.meeko/dev/editor-bridge-<puerto>.json, así que variosmeeko devfuncionan a la vez, y al arrancar borra los descriptores huérfanos de procesos terminados. Un editor nuevo recibe la instantánea cuando las pestañas abiertas envían su estado (como mucho unos 300 ms después). Una señal compartida, como el modelo que un hijo recibe condefineModel, lleva el campoaliascon su señal principal: la extensión la muestra comoopen = true (= dialogOpen), la describe como «alias dedialogOpen» en el hover y edita la principal.MEEKO_EDITOR_BRIDGE=0desactiva el puente. Su protocolo es sólo de desarrollo y no forma parte del contrato público (véase docs/devtools.md). -
Protocolo DevTools 1: campo opcional
signalSitesenDevtoolsInstance, con la declaración (DevtoolsSignalSite:file,line,column) de cada señal.
Cambios#
- Mensajes en español. Los textos de la CLI (ayuda, mensajes de éxito y de error, salida de
meeko clean), los diagnósticos de Meeko del compilador, demeeko checky del framework, y los hovers, logs y diagnósticos propios del servidor de lenguaje están en español;MEEK001yMEEK008explican el error del parser HTML en español y conservan su código entre paréntesis ((non-void-html-element-start-tag-with-trailing-solidus)). Los códigos (MEEK0xx,MEEK_*) no cambian; una herramienta que lea los diagnósticos debe usar el código y no el texto, que no forma parte del contrato (véase docs/stability.md). meeko checkes más estricto con las props de los componentes. Las props de una etiqueta (<Etiqueta>o<component is>) se comprueban sólo contra las que declara el hijo: una prop desconocida junto a otras válidas ya es un error (antes pasaba),bind:variant="'secondary'"conserva su tipo literal frente a una unión y una prop obligatoria que falta se señala en la etiqueta, ahora conTS2345en lugar deTS2769. Las aplicaciones que pasaban props sobrantes verán errores nuevos. Además,meeko checkinforma una sola vez de cada error, aunque el compilador compruebe la misma expresión o etiqueta en varias rutas de render.- Nombres reservados en la plantilla.
ref:nombrees una directiva, ymodel="..."yhtml="..."en un elemento HTML estándar son directivas en lugar de atributos HTML (no son atributos válidos de ningún elemento estándar; véase la excepción en docs/stability.md); en un elemento personalizado (<x-markdown html="intro">) siguen siendo atributos literales, como en1.0.bind:refybind:htmlsiguen escribiendo el atributo. En una etiqueta de componente,is,refyhtmlno son props estáticas (MEEK032). - Render previo de la navegación. Antes de confirmar cada navegación, el navegador renderiza la página nueva sin mostrarla para cargar sus
awaitde datos, conuseNavigation().pendingentrue. Esa pasada es en seco: no ejecutaeffect,onMountedni la continuación de navegador, yuseSocketno conecta. El código del nivel superior del setup de la página y de los layouts nuevos sí se ejecuta una vez más, con un estado provisional que se descarta. La pasada se salta cuando ningún módulo del árbol nuevo tiene unawaitde datos, según las marcashasDataAwaity__meekoChildrenque el compilador añade a cada módulo; un componente elegido en tiempo de ejecución obliga a hacerla, y uno que pinta, sin ser layout, el componente de una propoutletno se recorre, y su contenido se monta tarde. En el navegador,useSocketysocket.xtampoco abren conexión en el render que produce el HTML de un componente antes de montarlo: la abre el montaje, una sola vez. - Páginas de error al navegar. Una navegación cliente cuya petición de datos responde
404(pornotFound()o por unApiError404del loader) muestra el+error.meekomás cercano, oapp/error.meeko, con{ status: 404, message }, como el documento SSR; antes la navegación fallaba con un error. Las páginas de error se titulanErroren SSR y al navegar, incluida la404deapp/error.meekopara una URL sin ruta. - Páginas de capas y módulos dentro del shell del proyecto.
app/app.meekoy el layout raíz del proyecto envuelven las páginas deextendsymodules, y los+layout.meekode la capa se anidan dentro; si el proyecto tieneapp.meekoo layout raíz, sustituyen a los de la capa. Una página de capa que falla usa su+error.meekomás cercano, después elapp/error.meekode la capa y por último la página de error raíz del proyecto. Antes se renderizaban sueltas en#appy el layout desaparecía al navegar hacia ellas.discoverRoutes(root, { shell })y el tipoDiscoverRoutesOptionsde@meekojs/meeko/buildexponen la opción. meeko devregistra archivos nuevos sin reiniciar: las páginas, layouts,app/app.meeko,app/error.meeko, middlewares y módulos deserver/api,server/rpcyserver/socketque se crean o se borran, en el proyecto y en sus layers y módulos. Vuelve a descubrir las rutas y crea de nuevo la aplicación, y las peticiones esperan a la nueva. Al recrearla se cierran las conexiones Socket y se vuelve a llamar aserver/runtime.ts, así que se pierden las sesiones de un almacén en memoria; si la aplicación nueva falla, sigue la anterior. Editar un canal Socket existente omeeko.config.tssigue exigiendo reiniciar.- Rechazos de sesión y autorización del handshake de Socket. Un handshake sin sesión en un canal
auth: "required", o queauthorizerechaza, ya no recibe401/403por HTTP, que elWebSocketdel navegador no expone y que el cliente reintentaba sin fin. Los adaptadores de Node, Bun, Deno y Cloudflare (este último desde el Worker, sin despertar el Durable Object) completan el upgrade y cierran en seguida con4401(«Autenticación requerida») o4403(«Acceso denegado»), sin llamar a ningún handler ni entregar mensajes; el cliente de Meeko pasa a"unauthorized"también en el navegador.openSocketSessioncierra igual cuando el hub rechaza una conexión ya aceptada, como al despertar un Durable Object, en lugar de1008. El Origin no permitido (403INVALID_ORIGIN), el canal inexistente (404), las peticiones inválidas (400) y los límites siguen respondiendo por HTTP: un origen ajeno nunca completa un upgrade. EncreateSocketUpgradeHandler, la acciónrejectdeSocketUpgradegana el campo opcionalclose; un adaptador propio que no lo atiende sigue respondiendostatus, ycreateNodeSocketUpgradeHandlerya no pasa esos dos rechazos a sureject. Un cliente WebSocket propio que esperaba el401/403HTTP debe tratar los cierres4401/4403como rechazo definitivo (véase docs/migration-1.1.md).
Correcciones#
- Un cliente Socket (
createSocketClient,useSocketClient, el autoimportuseSocketosocket.x) que el servidor rechazaba por autenticación o autorización reintentaba sin fin e informaba «Socket transport error». Ahora, ante el cierre de política del hub (1008conUnauthorized,Session expiredoSession revoked), los códigos4401/4403o un handshake401/403cuyo estado expone la implementación deWebSocket(comows), no reintenta:statepasa al nuevo estado terminal"unauthorized"y el observador de errores recibe unClientError401(UNAUTHENTICATED) o403(FORBIDDEN). ElWebSocketdel navegador no expone el estado de un handshake rechazado; por eso los adaptadores de Meeko cierran ahora esos handshakes con4401/4403(véase «Cambios» y docs/framework.md). - Los autoimports de framework (
useSocket,api,rpc,useApi…) en los.tsdeapp/(composables, utils, stores) usaban el cliente del navegador también en SSR, ymeeko devomeeko buildfallaban con «Client-only module reached the server bundle». Ahora se resuelven por entorno, como en los.meeko; los.client.tssiguen siendo sólo de navegador. - Un clic en un enlace a un fragmento de la página actual (
#seccion) se trataba como una navegación completa: Meeko volvía a pedir y renderizar la página y no se desplazaba hasta el ancla. Ahora el navegador lo atiende, y volver atrás o adelante entre fragmentos no renderiza la página. Una navegación nueva empieza arriba o en el elemento de su fragmento; antes conservaba el scroll de la página anterior. - Si la petición de datos de una navegación recibe HTML en lugar de un payload (por ejemplo, de un hosting estático que sirve el
index.htmlprerenderizado), Meeko carga el documento. Antes la navegación fallaba y el clic no hacía nada. - Al navegar a una página sin
title,document.titleconservaba el de la página anterior. Ahora vuelve al título del host, o a ninguno, como en SSR. - El documento SSR (y el prerenderizado) sólo incluía en
<head>el CSS de la página y de sus layouts: el de los componentes hijos llegaba con el bundle del cliente, así que la página se pintaba primero sin esos estilos y se desplazaba al cargar el JavaScript. Ahora incluye también el CSS de cada componente que renderiza. meeko buildcopiabapublic/también en la raíz de la salida de servidor (.meeko/output/node/, Bun y Deno), donde nadie lo sirve. Ahora sólo está enclient/.meeko devy el plugin de Vite ya no vigilan.meeko/salvo.meeko/generated/, ni lo incluyen en el escaneo de dependencias, combinando la exclusión con losserver.watch.ignoredyoptimizeDeps.entriesdel usuario. Antes,meeko check, el servidor de lenguaje o un build que escribían en.meeko/recargaban el navegador, y con carpetas grandes bajo.meeko/Vite tardaba segundos más en arrancar porque abría unfs.watchpor archivo. También se respetaserver.watch: null, que el plugin reactivaba.- Después de que Vite se reiniciara en
meeko dev(al cambiar un.envo una dependencia de la configuración, o conserver.restart()), todas las rutas, incluida la página de error, respondían500(«Vite module runner has been closed») hasta reiniciarmeeko dev. Ahora la aplicación de desarrollo se vuelve a crear sobre el servidor de Vite nuevo y se cierra la anterior; las peticiones que llegan mientras tanto la esperan. Como al añadir o borrar una página, lo que la aplicación guardaba en memoria (las sesiones de unMemorySessionStore, el estado de los módulos de servidor) empieza vacío y las conexiones Socket abiertas se cierran. useStatey el resto del contexto de Meeko fallaban enmeeko dev(«Missing Meeko context») cuando la aplicación consume Meeko conlink:, después de que Vite recargara sus módulos. El SSR de desarrollo externaliza ahora@meekojs/meeko, como el build.meeko checkcomprueba los.tsque importa un.meekocon los autoimports de su ámbito y con las rutas generadas. Antes dabaTS2304para composables, stores o utilidades autoimportados dentro de esos archivos, aceptabahref("/ruta-inexistente")y rechazabahref(id, { search })en rutas estáticas. Puede mostrar ahora errores que los proyectostsconfig.*generados ya daban.- Los
enumy las clases autoimportados se pueden usar también como tipo, incluidos sus miembros (Scope.App). Un autoimport que sólo se usa como tipo se emite comoimport type. - Los contratos de API, RPC y Socket aceptan schemas autoimportados desde
shared/schemas, incluidos los que usan unenumautoimportado. Antes exigían un import explícito («query must come from shared/»). - Con resolución
NodeNext(la de los proyectos demeeko init-framework), el servidor de lenguaje tipaba las etiquetas de un componente del proyecto con su declaración generada.d.meeko.tsde.meeko/types, que podía estar desactualizada y no lleva los parámetros resueltos de un componente genérico: el hover de la etiqueta no mostrabaT = Usery los parámetros de<snippet params>y de los manejadoreson:podían mostrar otro tipo. Ahora el editor usa el componente tal como está, también con cambios sin guardar. meeko checky el editor informaban dos veces de un error dentro de un<snippet>(por ejemplo{row.name}sinname), la segunda sobre la última letra. Ahora lo informan una vez, sobre la propiedad.- Los hovers, el completado y la ayuda de firmas mostraban
{@link X}literal en la documentación. Ahora muestran el nombre en código, su texto o un enlace. - Un comando o una opción desconocidos de la CLI (
meeko frobnicate,meeko dev --port 1) sólo mostraban el uso. Ahora indican antes el problema (Comando desconocido: frobnicate,Opción desconocida para dev: --port), y la ayuda describe las variablesPORT,HOST,PUBLIC_ORIGIN,MEEKO_HMR_PORTyMEEKO_EDITOR_BRIDGE.
Deprecado#
useSocket(url, contract, options)de@meekojs/meeko/frameworkpasa a llamarseuseSocketClient, con la misma firma, los mismos tipos y el mismo comportamiento (ligado al componente, admiteawaitde nivel superior). Así deja de compartir nombre con el autoimportuseSocket("canal", opciones)de los clientes generados, que tiene otra firma y no cambia.useSocketsigue exportándose como alias obsoleto (@deprecated; es la misma función) durante toda la serie 1.x y se retirará en2.0: sustituyaimport { useSocket } from "@meekojs/meeko/framework"porimport { useSocketClient } from "@meekojs/meeko/framework". En un proyecto sin clientes generados, el autoimportuseSocketresuelve ahora auseSocketClient, que es la misma función, sin el aviso de obsoleto.createSocketClientno cambia (véase docs/framework.md).
Documentación#
- Las reglas del nivel superior del
<script>(MEEK014,MEEK015,MEEK018), cómo usar un.client.tsdeapp/desde una página, qué es un store y por qué su estado debe vivir enuseState, y en qué se diferencianextendsymodules. - La política de estabilidad lista todos los códigos de diagnóstico en uso y los retirados (
MEEK005,MEEK023yMEEK049).
[1.0.1] - 2026-10-25#
Correcciones#
bind:value,bind:checkedybind:selectedactualizan también la propiedad viva deinput,textarea,selectyoption. Antes sólo cambiaban el atributo, que el navegador ignora una vez que el usuario edita el control, así que asignar el signal desde código no limpiaba el campo y el signal quedaba desincronizado de lo que se veía. Al hidratar, untextareao unselectsimple que el usuario aún no cambió toma el valor enlazado; unselect multipleno se modifica.- Cambio de comportamiento: el control pasa a reflejar siempre el signal. Si el manejador de
inputguarda un valor transformado (por ejemplo,Number(value)ovalue.trim()), el campo muestra ese valor transformado mientras el usuario escribe. Guarde el texto tal cual en un signal de tipostringy conviértalo donde se use.
- Cambio de comportamiento: el control pasa a reflejar siempre el signal. Si el manejador de
- El handshake de Socket acepta los otros nombres de loopback (
localhost,127.0.0.1,[::1]) con el mismo esquema y puerto cuando el origen público es de loopback. Antes, una página abierta enhttp://localhost:4173recibía403 INVALID_ORIGINde un servidor que escuchaba en127.0.0.1:4173, aunque sus peticiones HTTP sí funcionaban. Los orígenes que no son de loopback siguen sin tomar su identidad de una cabecera.
[1.0.0] - 2026-10-25#
Primera versión estable. El código es el de 1.0.0-rc.1, sin cambios; los cambios respecto a 0.1.0 se recogen en la sección siguiente y en la guía de migración. Desde esta versión, Meeko sigue SemVer según docs/stability.md.
[1.0.0-rc.1] - 2026-10-25#
Primera versión estable candidata. Desde 1.0, Meeko sigue SemVer según docs/stability.md; la guía docs/migration-1.0.md recoge la migración desde 0.1.0.
Cambios incompatibles y migración#
- La raíz
@meekojs/meekosólo exporta la API pública documentada:signal,computed,effect,batch,untrack,createRoot,onCleanup,createContext,provide,inject,provideErrorHandler,serializeJsony sus tipos. Los helpers de DOM, SSR, HMR y DevTools (bind*,render*,keyToken,withStateSnapshot,escapeHtml,attributeValue,renderAttribute,enableHotModule,registerHot*,replaceHot*,restoreSignals,snapshotSignals,registerDevtoolsInstance) quedan sólo en@meekojs/meeko/internal, que es lo que importa el código generado desde0.0.2. Vuelva a compilar los módulos generados con0.0.1. ComponentErrorHandlerrecibe un segundo argumentosource: ErrorSource. Los handlers de un solo parámetro siguen siendo válidos.- El payload de hidratación y navegación lleva
v: 2,renderyhead. La versión 2 añade la listadeferred,nullen cadapathdiferido y la respuestaapplication/meeko+ndjsonde las rutas"stream". El cliente rechaza un payload sin versión o con otra versión (incluidav: 1) con un mensaje explícito; servidor y navegador deben salir del mismo build, y losindex.meeko.jsonprerenderizados deben regenerarse conmeeko build. - Se elimina
FrameworkHandlerOptions.renderLayout. Los layouts se componen siempre mediante el outlet<component is="props.outlet">. - El protocolo DevTools pasa a la versión 1:
DevtoolsHookexigeprotocolVersionysetSignal, yDevtoolsSnapshotexigeprotocolVersion. Un hook o una captura construidos a mano, por ejemplo en pruebas, deben añadir esos miembros.DevtoolsReadSite.kindadmite ademáscomputed,if,eachyprop. Unswitchexhaustivo sobretext/attributedebe cubrir los nuevos valores.- El panel integrado y la extensión de Chrome 1.0.0 rechazan, con un mensaje explícito, los runtimes sin
protocolVersion. Use la extensión 1.0.0 con esta versión de@meekojs/meeko, y la extensión anterior con runtimes anteriores.
server/session.tsdeja de cargarse y se sustituye porserver/runtime.ts, que exporta por defectodefineServerRuntime(({ env, production, platform, prerendering }) => ({ sessions, services, socket, publicOrigin, onError, close })). Mueva elSessionManagerdecreateSessionManager(production)asessionsy elimine el archivo anterior;meeko check,meeko devymeeko buildse detienen con estas instrucciones mientras exista.meeko buildllama a la fábrica conprerendering: truepara renderizar las páginas prerenderizadas.@meekojs/meeko/serveres ahora el núcleo portable (Web APIs ynode:async_hooks) y ya no carga TypeScript ni el sistema de archivos. Las herramientas de build pasan a la nueva subruta@meekojs/meeko/build(discoverRoutes,discoverConfiguredRoutes,composeFrameworkRoutes,loadMeekoConfig,readMeekoConfigSync,resolveConfigSources,discoverServerManifest,prerenderRoutes,prerenderProject,buildProject) ytoNodeListener,attachNodeWebSocketServerycreateNodeSocketUpgradeHandlera la nueva subruta@meekojs/meeko/node. Actualice esos imports.createServerDispatcher,createLocalCalls,generateOpenApiyloadSocketChannelsexigen un cargador de módulos explícito (load); ya no importan rutas del disco por defecto. En Node puede pasar(file) => import(pathToFileURL(file).href).meeko buildescribe un build Node autónomo en.meeko/output/node/(index.mjs,chunks/,client/,prerender/) en lugar de.meeko/output/server/entry.js, y el prerender pasa de.meeko/output/prerender/a.meeko/output/node/prerender/, con un índice{ version: 1, pages }enmanifest.json.prerenderRoutesrequiere que cada documento contenga<head>y escribe también el payload de navegación.- El valor de
renderde un sidecar se lee sin ejecutarlo: debe ser un literal de texto endefinePage({ ... }), en un objeto exportado o en una constante local del mismo archivo. Un valor calculado, una clave computada no seguida de unrenderliteral, un spread sinrenderexplícito posterior o una reexportación detienen el build. - Sólo se sirven los archivos públicos listados en el manifiesto del build; un archivo no listado, incluido un enlace añadido a
public/después del build, responde como cualquier ruta de la aplicación (normalmente404) en lugar de403. - El HTML ya no incluye
<script id="meeko-routes">: la entrada cliente importa las rutas desde el módulo virtualvirtual:meeko-manifest/client. Los módulos virtualesvirtual:meeko-server-pagesyvirtual:meeko-server-routesse sustituyen porvirtual:meeko-server-app. PageDefinition.rendersólo acepta los modos de servidor ("ssr","stream"y"prerender"); las páginasrender: "client"usan el nuevo tipoClientPageDefinition.PageRenderincluye ahora"client", de modo que unswitchexhaustivo sobre él debe cubrir ese valor.- El servidor compara el
renderdel sidecar con el del manifiesto aunque el sidecar lo omita: un manifiesto conrender: "prerender"y un sidecar sinrender(o con otro valor) responden con error hasta reconstruir. Un manifiesto escrito a mano sinrendersigue aceptando sidecars"ssr"y"prerender". - La entrada cliente generada importa
virtual:meeko-client-routes;.meeko/types/virtual-pages.d.tslo declara. Regenere los tipos conmeeko checksi su proyecto incluye una entrada propia. - Cambia el contrato de broadcast de Socket:
hub.broadcast()se resuelve cuando el broker acepta el mensaje y ya no rechaza por fallos de entrega a los peers.hub.broadcast()rechaza por tamaño: si el mensaje serializado conserializeJson(que escribe<,>y&con 6 bytes) superamaxMessageBytes, rechaza conSOCKET_LIMIT_EXCEEDED(413) antes de publicar, en lugar de cerrar con1009a todos los peers de la sala.- Un peer que falla se cierra (
1008,1009,1011, o1013si su cola de entregas se llena) y su error llega aSocketHubOptions.onDeliveryError(error, { channel, room, event })de la instancia que tiene ese peer.createServerApplo conecta por defecto alonErrordel runtime. LocalSocketPubSub.publishdeja de agregar errores de sus listeners y los envía aonListenerError.- En un broadcast,
authorizeSendque devuelvefalseomite a ese peer sin cerrarlo. - Quien dependía del rechazo de
broadcastdebe observaronDeliveryErroroonError.
SocketPubSub.subscribepuede devolverUnsubscribe | Promise<Unsubscribe>, yUnsubscribepuede ser asíncrona. El hub espera la suscripción antes de resolveraccept. Un broker propio debe aceptar esa firma, y quien llame asubscribedirectamente debe esperar el resultado conawait.RequestContext.signalya no esrequest.signalsin más: combina esa señal con la revocación de la sesión y puede abortarse conSessionRevokedErroraunque el cliente siga conectado.PageRenderincluye"stream", yPageDefinition.renderlo acepta: unswitchexhaustivo sobrePageRenderdebe cubrirlo. El payload puede llevardeferred: [{ id, path, status, value?, error? }]connullen cadapath.- El servidor sella el
StateStorede la petición tras el render síncrono yhead, en todos los modos de render (ssr,stream,prerendery los layouts declient): unuseStatecon clave nueva, unseedo una escritura posteriores lanzanSTATE_SEALED. Migración: el trabajo asíncrono que escribe estado debe esperarse en el loader (await) o devolverse condefer; una promesa suelta que escribe estado tras el render rechaza conSTATE_SEALEDy, si nadie la captura, es un rechazo no manejado que termina el proceso de Node con su configuración por defecto.headse sigue calculando después del render, con el estado que este escribió. PageRequestContext.signal(lasignaldel loader del servidor) ya no esrequest.signal: combinaRequestContext.signal(desconexión y revocación de la sesión) con la cancelación del trabajo diferido (stream cancelado,HEADo error de la petición).SessionHandle.create,rotateyrevokerechazan conSESSION_LOCKED(500) cuando las cabeceras de una respuesta"stream"ya se enviaron, sin tocar el almacén.- Con
render: "prerender", un valordeferrechazado hace fallar la petición con500(ymeeko buildcon la ruta afectada) en lugar de guardar el error en el HTML.prerenderRoutestoma el payload de navegación del propio documento (#meeko-payload) y sólo pideapplication/meeko+jsonal handler cuando el documento no lo incluye.
Añadido y corregido#
-
Una navegación cliente cuyo loader o middleware de servidor redirige (por ejemplo, una página privada que envía a
/login) ya no falla con «Meeko route data mismatch»: el cliente navega al destino sin recargar si es una ruta de la aplicación en el mismo origen, o hace una carga completa en otro caso. Cubre tanto la redirección que sigue elfetchdel navegador como un3xxdevuelto por unfetchpropio. -
Streaming SSR con
render: "stream"ydefer(promise, { timeoutMs })odefer((signal) => promise, { timeoutMs })desde@meekojs/meeko/framework(tiposDeferred,DeferredError,DeferredStatus,DeferOptions,DeferredFactory). La señal de una fábrica se aborta por timeout, desconexión, revocación de la sesión, stream cancelado,HEADo, en el navegador, al salir de la ruta. Antes del primer byte se ejecutan middleware, validación, el loader crítico,heady el render síncrono de layouts y página; después se sella el estado y se comprueba la sesión consession.verify()(una sesión revocada responde401). La respuesta (cache-control: no-store, no-transform,x-accel-buffering: no;no-transformevita que un intermediario comprima o reescriba el cuerpo con buffer) envía el documento con#meeko-payloady una entrada<script type="module" async>que respeta el nonce CSP, y luego un<script type="application/json" data-meeko-deferred="N">por valor resuelto. Los rechazos se envían como{ code, status }(los5xxllegan aonErrorcon elrequestId),DEFERRED_TIMEOUT/504al agotar el plazo (10 s por defecto), y los diferidos se abortan conRequestContext.signal, al cancelarse el stream o enHEAD. Los cambios de sesión en curso terminan antes del primer byte y los posteriores fallan conSESSION_LOCKEDsin cerrar la sesión del usuario. El navegador hidrata con los valores pendientes, aplica los bloques recibidos tras la hidratación, detecta los posteriores con unMutationObserver, sólo acepta bloques hijos directos de<body>situados después de#meeko-payloady rechaza conDEFERRED_INCOMPLETE/502los que nunca llegan. La navegación cliente a una ruta"stream"recibeapplication/meeko+ndjson(payload en la primera línea, un valor por línea después), informa las líneas mal formadas sin detenerse, rechaza conDEFERRED_INCOMPLETElo pendiente si la lectura falla y cancela la lectura al salir de la ruta. Los valores diferidos pueden ir bajo clavesconstructoroprototype; bajo__proto__el servidor responde500. Conrender: "ssr"o"prerender",deferse espera antes de renderizar. La suite de conformidad comprueba en Node, Bun, Deno y workerd que el primer byte llega antes que el valor diferido. En local,wrangler devymeeko preview --adapter cloudflare(Miniflare) comprimentext/htmlcon gzip aunque la respuesta lleveno-transformy sólo escriben el cuerpo al terminar; un cliente que envíaaccept-encoding: identityrecibe el streaming. -
Páginas
render: "client": el servidor ejecuta los middlewares, validaparamsysearchy renderiza sólo los layouts con el outlet vacío; el navegador ejecutaloadyheadcon el nuevoClientPageLoadContext(url,params,search,signal,state) y renderiza la página. La navegación entre páginas SSR y de cliente funciona en ambos sentidos y conserva los layouts comunes. Sólo los sidecars"client"entran al bundle cliente, mediantevirtual:meeko-client-routesy la nueva opciónmountClientApp({ loadClientRoute }); importar código de servidor desde ellos detiene el build. DeclararprerenderEntriesconrender: "client"es un error. Sus datos deben llegar por API o RPC con guardas propias, y el HTML inicial no incluye datos ni<title>de la página. -
useNavigation()devuelve{ pending }, una señal de sólo lectura que indica si hay una navegación en curso (tipoNavigationState).@meekojs/meeko/frameworkexporta además los tiposClientPageDefinition,ClientPageLoadContext,PageHeadyPageRender. -
RedisSocketPubSub({ publisher, subscriber, prefix, maxMessageBytes?, sharded?, onError? })en@meekojs/meeko/serverdistribuye los broadcasts de Socket entre instancias mediante Redis pub/sub:- usa un canal Redis por canal y sala, con conteo de referencias por instancia;
- envía un sobre versionado
{ v: 1, event, payload }; - limita el tamaño antes de publicar;
- el receptor vuelve a validar el schema y la autorización;
sharded: trueactiva el pub/sub con shards de Redis 7.
Tipos nuevos:
RedisPublisherClient,RedisSubscriberClient,RedisMessageListener,RedisSocketPubSubOptions,SocketMessage,SocketDeliveryErrorContext,UnsubscribeyLocalSocketPubSubOptions.ServerRuntime.socket.onDeliveryErrorsustituye al observador por defecto. -
Revocación durante una petición:
SessionStore.subscribeRevocations?es una capacidad opcional.RedisSessionStorela ofrece con la opciónsubscriber: el script derevokeFamilypublica en<prefix>revoked.MemorySessionStorenotifica dentro del proceso.SessionHandle.signalyRequestContext.signalse abortan conSessionRevokedError, exportado y equivalente aApiErrorSESSION_REVOKEDcon estado401.- Los sockets de la familia revocada se cierran con
1008. SessionHandle.verify()relee el store antes de efectos irreversibles.SessionHandle.signalyRequestContext.signalse abortan aunque el código sólo conserve la señal;RequestContext.signalse combina al leerla por primera vez.
-
Contrapresión de Socket con brokers asíncronos como Redis, configurable en
SocketHubOptionsy ensocketdeserver/runtime.ts:maxPendingDeliveries(256 por defecto) limita las entregas pendientes de cada peer; un peer que lo supera se cierra con1013y se informa conSOCKET_BACKLOG_EXCEEDED(503) aonDeliveryError.sessionRefreshMs(1000 ms por defecto) hace que las entregas de broadcast reutilicen la última lectura de la sesión durante ese intervalo; las revocaciones siguen cerrando el peer al instante yauthorize/authorizeSendse ejecutan en cada entrega. Con0se relee en cada entrega, como antes.
-
mountClientApp({ onError })observa los fallos de la aplicación (navegación, Socket, y eventos, efectos o limpiezas sin unprovideErrorHandlermás cercano) junto con suErrorSource.provideErrorHandlerrecibe también los rechazos deeffectyonCleanupque devuelven una promesa por error. -
La navegación cliente es transaccional: la vista nueva se hidrata junto a la actual y la anterior sólo se retira tras una hidratación correcta; un outlet restaura su página anterior. Sólo si también falla esa restauración se recarga la última URL confirmada.
-
La navegación actualiza
document.titlecon elheadde la página siguiente. -
FrameworkHandlerOptions.documentañade marcado del host a<head>y al final de<body>, también en la página+error.meeko, y aplica unnonceCSP por petición a los estilos y al script de entrada.headybodyEndpueden ser funciones que reciben la petición y su nonce. -
Un evento, efecto o limpieza de un componente sin observador antepasado sólo se entrega al
onErrorde la aplicación o a la consola, nunca alprovideErrorHandlerde otro componente. -
Las interpolaciones con entidades HTML (
©,<) o saltos CRLF se ubican en la misma posición enmeeko checky en VS Code: el compilador analiza el texto fuente y decodifica cada parte como el parser HTML. El analizador trata como división el/trasi++,i--o una propiedad con nombre de palabra clave (obj.return / 2) y acepta continuaciones de línea CRLF dentro de cadenas. -
meeko --helpymeeko --version. -
DevTools edita señales:
setSignal(instanceId, name, value)sustituye el valor de una señal editable de una instancia activa por un valor JSON validado y responde{ ok, error }en lugar de lanzar. Sólo existe en desarrollo. Los dos paneles añaden el formulario Editar señal, con el error visible junto a Aplicar, y la línea de tiempo marca esas escrituras como «Escrita desde DevTools». -
La línea de tiempo de DevTools indica qué escribió cada señal. Sólo la compilación de desarrollo (
hot) lo instrumenta; el código de producción y el HTML de SSR no cambian. Cubre, en el script.meekoy en las expresioneson:de la plantilla:- las asignaciones
.valuecon=,??=,||=y&&=; - las asignaciones compuestas y
++/--.
- las asignaciones
-
Las lecturas de DevTools se ubican también en
computed(() => …),if:when,each:ofy las props de componentes hijos, además de en las expresiones de texto y atributo. -
Los tipos del protocolo DevTools viven sólo en
src/devtools-protocol.ts. La extensión de Chrome usa una copia generada (pnpm run devtools:types) que sutscconcheckJscomprueba, ydevtools:checkfalla si esa copia queda desactualizada. La raíz exporta los nuevos tiposDevtoolsReadSite,DevtoolsReadKind,DevtoolsWriteSite,DevtoolsAssignmentSite,DevtoolsEditSite,DevtoolsCommandResult,DevtoolsEventKindyDevtoolsProtocolVersion. -
La extensión Meeko DevTools para Chrome pasa a la versión 1.0.0, con edición de señales, ubicaciones de escritura y lectura y aviso de protocolo no compatible.
-
Servidor de lenguaje de Meeko:
meeko lsp --stdioy el binmeeko-language-server(también--node-ipc,--socket=<puerto>y--pipe=<nombre>). Ofrece diagnósticos demeeko check, completado, hover, definición, referencias, renombrado entre archivos.meekoy.ts, ayuda de firma, símbolos, plegado, formato, tokens semánticos y acciones rápidas (añadir imports, resolver un autoimport ambiguo con un import explícito y quitarparamsde unhrefestático).initializationOptionsadmitetrusted,projectContractsydiagnosticsDelay. docs/editors.md explica la configuración para Neovim, Helix, Zed, JetBrains (LSP4IJ) y Sublime Text. Dependencias nuevas:vscode-languageserver,vscode-languageserver-textdocument,vscode-html-languageserviceyvscode-css-languageservice. -
En un workspace de confianza el servidor de lenguaje ejecuta
meeko.config.*, de modo queextends,moduleseimports.serverDirsya no tienen que ser arrays literales para el editor; sin confianza sigue leyéndolos de forma estática. -
La extensión de VS Code
meeko-language1.0.0 es un cliente del servidor de lenguaje: usa el de@meekojs/meekodel proyecto cuando está instalado y el incluido como alternativa o en modo restringido. -
Editor: los diagnósticos cubren el token completo en lugar de un carácter, los tokens semánticos de expresiones multilínea se dividen por línea, el renombrado reescribe declaraciones e imports en otros archivos y hover, completado y navegación usan el
tsconfig.jsonreal del proyecto. -
meeko checkaplica las opciones de resolución deltsconfig.jsonde la raíz del paquete (paths,baseUrl,types,typeRoots,customConditions,resolveJsonModule,allowImportingTsExtensions), igual que el editor: un aliaspathsque el editor resuelve ya no se informa comoTS2307.baseUrlse traduce a una correspondencia*porque TypeScript 7 no lo admite. Las banderas estrictas de comprobación no cambian. -
createServerApp({ manifest, load, runtime, production })crea una aplicación portable confetch(request, platform?),socket,publicOrigin,reportErroryclose();defineServerRuntimedeclara sesiones, servicios por petición, política de Socket,publicOrigin,onErroryclose.RequestContext.platformexpone{ name, env, waitUntil }. -
@meekojs/meeko/nodeañadeserveNode(app, { host, port, publicOrigin, platform, onError, handleSignals, shutdownTimeout }), que conecta también Socket y espera las tareas dewaitUntilal cerrar, ycreateNodeAssetReader. Las respuestas abiertas, como un stream, tienenshutdownTimeoutmilisegundos (10 000 por defecto) para terminar antes de que se corten sus conexiones, y un fallo al conectar Socket tras escuchar libera el puerto y cierra la aplicación.toNodeListeneraceptapublicOrigin. -
Las tareas que un callback de Socket registra con
context.platform.waitUntillas esperaapp.close()(y por tantoserveNode), y sus fallos llegan al observadoronErrordel runtime en lugar de la consola. -
meeko build [directorio] [--adapter <nombre>]yMeekoConfig.server(unión discriminada; Node aceptahostyport) eligen el destino del build. Con Node,meeko previewejecuta el build igual quemeeko start. El servidor de producción escucha enHOST/PORTo en los valores deserver, con127.0.0.1:4173por defecto; antes sólo podía escuchar en127.0.0.1. -
Adaptadores Bun y Deno:
@meekojs/meeko/bunexportacreateBunServer(app, { publicOrigin, platform, onError, maxPayloadLength, backpressureLimit, idleTimeout, sendPings }), que devuelve{ fetch, websocket }paraBun.serve, ycreateBunAssetReader.@meekojs/meeko/denoexportaserveDeno(app, { host, port, publicOrigin, platform, onError, maxMessageBytes, idleTimeout, handleSignals })ycreateDenoAssetReader.@meekojs/meeko/serverañadecreateSocketUpgradeHandler, la comprobación portable de ruta, Origin y sesión previa al upgrade que comparten Node, Bun y Deno.
-
meeko build --adapter buny--adapter denoescriben.meeko/output/bun/(index.js) y.meeko/output/deno/(main.js), junto conchunks/,client/yprerender/. Cada salida incluye todas las dependencias del servidor y no necesitanode_modules.meeko preview [--adapter <node|cloudflare|bun|deno>]ejecuta la salida en su runtime real; para Bun y Deno lo hace como proceso hijo, que debe estar en elPATH. -
Adaptador Cloudflare Workers:
@meekojs/meeko/cloudflareexportacreateWorker(app, { socketBinding?, publicOrigin?, onError? }),createSocketRoomClass(app, DurableObject, { maxMessageBytes?, autoResponse?, keepHeaders?, maxPendingPublications?, rebuildTimeoutMs?, onError? }),DurableObjectSocketPubSub,socketRoomName,D1SessionDriver(conschema()ydeleteExpired()),createCloudflareAssetReaderycreateCloudflarePrerenderSource, más sus tipos estructurales (CloudflareApp,CloudflareAppContext,CloudflareWorker,CloudflareWorkerOptions,SocketRoomOptions,MeekoSocketRoom,D1DatabaseLike,DurableObjectNamespaceLike…). No requiere@cloudflare/workers-types.- Cada sala de Socket es un Durable Object con la API de hibernación: el Worker comprueba ruta, Origin y sesión antes del upgrade, la conexión se reconstruye al despertar desde su attachment y el hub vuelve a autorizar;
pingse responde conpongsin despertar el objeto.DurableObjectSocketPubSubes el broker por defecto y publica entre salas por RPC. - El attachment de cada conexión guarda sólo la cookie de sesión de la cabecera
Cookie, además deoriginyauthorization;keepHeadersañade otras cabeceras. Si el estado supera los 16.384 bytes que admite un attachment, el upgrade responde431y el error llega aonError, en lugar de cerrar la conexión con1011tras aceptarla. - Una sala entrega cada publicación a los peers ya suscritos sin esperar a una conexión que todavía se autoriza; tras hibernar espera la reautorización como máximo
rebuildTimeoutMs(5000 ms) y rechaza conSOCKET_BACKLOG_EXCEEDED(503) por encima demaxPendingPublications(1024) publicaciones sin entregar. Con nombres de más de 1.024 bytes, que el runtime no expone enctx.id.name, la sala toma su nombre de la primera conexión o publicación y no se llama a sí misma por RPC. Si la aplicación no puede crearse al despertar, la sala cierra con1011el WebSocket que la despertó. runtime.socket.pubsubsólo admiteDurableObjectSocketPubSuben Workers: otro broker, comoLocalSocketPubSuboRedisSocketPubSub, se rechaza al crear la aplicación.socket.maxBufferBytesno se aplica en las salas porque los WebSockets de Workers no informan debufferedAmount.D1SessionDriverimplementaSqlSessionDriverconreplaceyrevokeFamilyatómicos en una sola sentencia yUNIQUE(family_id). La documentación incluye el esquema, un adaptador de la API REST de Upstash paraRedisSessionStorey por qué KV no es válido.meeko build --adapter cloudflareescribe.meeko/output/cloudflare/conworker.js(un único módulo, sinnode_modules),assets/(cliente,public/y prerender bajo_meeko/prerender/) y unwrangler.jsoncgenerado connodejs_als, assets servidos sólo a través del Worker, el bindingMEEKO_SOCKETy la declaración de la clase de salas. La migraciónmeeko-socket-v1sólo se genera cuandoserver.wrangler.migrationsestá vacío; un historial existente que no crea la clase detiene el build con la entrada exacta que hay que añadir al final, y con el mapa declarativoexportsMeeko añade la entrada de la clase si falta (combinarexportsde Durable Objects conmigrationses un error).MeekoConfig.serveracepta{ adapter: "cloudflare", name?, socketClassName?, compatibilityDate?, wrangler? };public/_meeko/queda reservado.meeko preview --adapter cloudflareejecutawrangler devsobre la salida.@meekojs/meeko/serverañadeopenSocketSession,ServerAppOptions.defaultSocketPubSuby los campos opcionalespubsubysessionCookieNamedeServerAppSocket.
-
Las conexiones Socket que nunca se cierran, como las de un Durable Object desalojado con sus WebSockets hibernados, ya no acumulan registros de revocación en el
SessionManager: los registros de sesiones recolectadas se barren sin depender de los finalizadores. -
Suite de conformidad
tests/conformance/(pnpm run conformance,conformance:node,conformance:bun,conformance:deno,conformance:workerd). Construye la misma aplicación para cada adaptador y la ejecuta sin fuentes. Comprueba SSR, API, RPC, CSRF,404/405,HEAD, assets conETag/304e intentos de traversal, prerender con CSRF, orden del middleware y Socket (Origin, autenticación, límites, orden y broadcast). La varianteworkerdejecuta la salida Cloudflare con el runtime local de Wrangler y prueba además sesiones D1, la hibernación forzada de una sala, la publicación entre salas, una cabeceraCookiemayor que el límite del attachment, una sala con nombre de más de 1.024 bytes y que la salida sea un único módulo. La CI añade los jobsbun,denoyworkerden Ubuntu y la conformidad Node en Windows; el workflow de publicación exige también esos jobs. Nueva devDependencywrangler4.152.0 (trae workerd 1.20261019.1). -
Un único
publicOrigin(oPUBLIC_ORIGIN) rige las comprobaciones de Origin de HTTP y de Socket, por lo que las mutaciones autenticadas y los WebSocket funcionan tras un proxy que termina TLS. -
Las páginas
render: "prerender"se sirven por fin desde el build, pasando por el middleware de servidor, el contexto de sesión y el middleware de ruta, con la meta CSRF de la sesión insertada en<head>. Sin sesión llevanETag,Vary: cookie,cache-control: public, max-age=0, must-revalidatey responden304; con sesión,private, no-store; una CDN delante debe respetarVary: cookieu omitir la caché en peticiones con cookie de sesión. Un fallo de lectura de una página prerenderizada ya no queda en caché: la siguiente petición vuelve a leer el archivo. Antes el prerender se generaba sin contexto ni CSRF y nunca se servía. -
Los archivos públicos del build llevan
ETag,content-length,HEADy304; sólo los archivos emitidos por Vite con hash de contenido se sirven concache-control: public, max-age=31536000, immutable, no los copiados depublic/assets/. Cada archivo se sirve en la ruta que pide el navegador, también con nombres comologo@2x.png,a+b.txt, espacios o caracteres no ASCII. El HTML enlaza la entrada, su CSS y susmodulepreloaddesde el manifiesto de Vite sin reescribir el documento completo. -
toNodeListenerinterpreta un request-target que empieza por//(por ejemplo//evil.example/api/x) como ruta del propio origen y no como otro host; antes la URL de la petición apuntaba a ese host y podía satisfacer la comprobación de Origin de una mutación con unOriginde ese host.createServerAppaplica lo mismo al reescribir la URL conpublicOrigin. -
toNodeListenercancela el cuerpo de la respuesta si el cliente se desconecta antes de recibir las cabeceras o mientras el stream espera datos, de modo que se liberan los archivos abiertos y se detienen los productores. -
El núcleo de servidor genera los IDs con
crypto.randomUUID()y los tokens de sesión concrypto.getRandomValuesy un codificador base64url propio, sinnode:crypto. -
Las expresiones de texto admiten llaves anidadas: objetos literales, plantillas literales con
${...}, cadenas que contienen{o}, comentarios y expresiones regulares. Compilador y VS Code comparten el mismo analizador, por lo que SSR, hidratación,meeko checky el editor ubican cada expresión en la misma posición. Una interpolación sin cerrar se informa conMEEK010en su llave de apertura, y{}se informa como expresión vacía (MEEK009). -
El código generado envuelve entre paréntesis las expresiones devueltas por funciones flecha, de modo que una expresión que empieza por
{, comobind:title="{ text: label }.text", no se interpreta como bloque. -
VS Code mapea los valores de directivas sin comillas, como
on:click=handleroeach:of=items, con la misma lógica que el compilador; sus diagnósticos, navegación y renombrado ya no se omiten. -
<style scoped>admite:global(<selector>): el selector interno se emite sin el atributo de ámbito, que se añade al último compuesto local (.card :global(.icon)produce.card[data-meeko-s-…] .icon).:globalsin selector, con una lista de selectores o anidado en otra pseudoclase falla conMEEK019en su posición.
Publicación#
- Una versión con sufijo de prerelease (
1.0.0-rc.1) se publica en npm con el dist-tagnexty no muevelatest. La publicación sigue usando OIDC sin token de npm. - La etiqueta
v<versión>adjunta a su release de GitHub los seis VSIX de VS Code, el ZIP de Chrome DevTools ySHA256SUMS.txt, y crea la release como prerelease cuando la versión lo es. Sólo ese job tienecontents: write, eid-token: writequeda limitado al job de npm. - Nuevos workflows
publish-vscode.yml(etiquetasvscode-v*, Visual Studio Marketplace y Open VSX) ypublish-devtools.yml(etiquetasdevtools-v*, API v2 de Chrome Web Store). Comprueban la versión de la etiqueta y ejecutan las pruebas de la extensión antes de publicar, y omiten cada tienda sin fallar mientras falten sus secretos (VSCE_PAT,OVSX_PAT,CHROME_EXTENSION_ID,CHROME_PUBLISHER_ID,CHROME_CLIENT_ID,CHROME_CLIENT_SECRET,CHROME_REFRESH_TOKEN). docs/releasing.md explica el alta de cada cuenta. package-zip.ps1 -RootLayoutgenera el ZIP de Chrome DevTools conmanifest.jsonen la raíz, como exige Chrome Web Store; sin el switch conserva la carpetameeko-devtools/.
[0.1.0] - 2026-10-25#
Este bloque contiene cambios incompatibles respecto de 0.0.4. De acuerdo con la política SemVer de Meeko para la serie 0.x, se publica como 0.1.0, no como un patch.
Cambios incompatibles y migración#
effectyonCleanupsólo aceptan callbacks síncronos. Inicie el trabajo asíncrono desde un callback síncrono, observe sus errores y gestione explícitamente su cancelación; no devuelva una promesa al runtime reactivo.- Los stores persistentes personalizados deben conservar
familyIde implementarreplaceyrevokeFamilyde forma atómica. Migre registros e índices antes del arranque en producción:SessionManager({ production: true, ... })rechaza un store incompleto; en desarrollo, la operación que necesite una capacidad ausente falla de forma explícita. render: "client"ya no forma parte del contrato de páginas. Elimínelo para usar SSR, que continúa siendo el valor predeterminado, o userender: "prerender"cuando corresponda.- Los proyectos generados activan
verbatimModuleSyntax,noUncheckedIndexedAccessyexactOptionalPropertyTypes. Useimport type, compruebe accesos por índice y declare| undefinedexplícitamente cuando la ausencia sea un valor permitido. - Los contratos Standard Schema públicos deben resolver a tipos concretos. Sustituya
any,unknown,never, parámetros genéricos no resueltos y tipos anidados inseguros por tipos públicos concretos de entrada y salida. - Las rutas estáticas ya no aceptan
params; las rutas API/RPC que forman jerarquías ambiguas o no representables deben renombrarse. Los parámetros dinámicos deben corresponder exactamente con la URL. Las queries públicas deben aceptar texto o listas de texto y modelar como opcionales las claves que puedan faltar; transforme números dentro del schema. - Los contratos de componentes, páginas, layouts y errores validan las props que el host realmente suministra. Declare esas props precisas y no ensanche un componente para ocultar requisitos obligatorios.
- Para migrar un starter full-stack de
0.0.4, adoptemeeko dev,meeko check,meeko buildymeeko start; compare primero cualquier personalización y después retireserver.mjs, las entradas cliente/servidor, las configuraciones Vite y lostsconfig.*ahora generados bajo.meeko/. Puede mover el layout raíz aapp/layouts/default.meeko, pero no lo conserve a la vez queapp/pages/+layout.meeko.
Añadido y corregido#
- La revocación de sesiones ahora invalida atómicamente la familia activa después de rotaciones concurrentes en memoria, Redis y SQL. Los drivers SQL personalizados deben migrar
familyIde implementarrevokeFamily; los stores de producción antiguos fallan al iniciar. VS Code aplica las mismas opciones estrictas de TypeScript que la CLI, HMR acepta IDs de módulo con caracteres de selector CSS y Socket comunica errores del transporte al observador activo. - El descubrimiento rechaza colisiones jerárquicas que convertirían un miembro
apiorpcsimultáneamente en función y objeto. El starter independiente observa y conserva los fallos al cerrar HTTP y Vite medianteSIGINToSIGTERM. - La generación de tipos registra las declaraciones de componentes que administra y elimina las huérfanas tras borrar o renombrar una fuente. La adopción desde versiones anteriores usa el AST del contrato generado y conserva declaraciones ajenas.
- Los autoimports conservan parámetros, restricciones y defaults de tipos genéricos reexportados y deduplican overloads del mismo símbolo. Los proyectos generados usan las mismas opciones estrictas que CLI y VS Code.
- API, RPC y Socket rechazan schemas Standard Schema degradados a
any,unknownonever, incluidos parámetros genéricos no resueltos y tipos inseguros anidados; el descubrimiento rechaza APIs raíz sin clave y rutas jerárquicas imposibles de representar. Si el arranque falla, los errores adicionales de cierre llegan al observador sin sustituir la causa original. onCleanupexige callbacks síncronos en tipos y runtime; un retorno thenable se observa sinunhandledRejection, no impide las demás limpiezas y participa en elAggregateErrorde la raíz.- VS Code tipa
each:indexcomonumber, igual que el compilador. La navegación solicita volver a la URL confirmada si fallapushStateoreplaceState, y preserva los errores de recuperación. +error.meekoconserva el contexto SSR de la petición, incluso en peticiones concurrentes; el cliente ignora tokens CSRF de respuestas anteriores a un cambio de sesión o navegación, y VS Code comprueba los tipos deon:eventyeach:keycomo el CLI.- HMR restaura listeners y todas las raíces del módulo si falla su limpieza o una actualización posterior, mientras el adaptador Node conserva el
requestIdya enviado al cliente cuando falla un stream de respuesta. - El outlet del starter y los contratos de layout validan con mayor precisión el
Component, sus props y la transferencia de estado SSR; los reemplazos de componentes y listas conservan la región previa cuando falla su hidratación o movimiento, y Socket intenta cerrar el peer aunque falle la desuscripción. provideErrorHandlerpermite observar errores de eventos por raíz de componentes con autoimport y tipounknown; HMR conserva el contexto padre al reemplazar o restaurar una instancia.- La navegación y los canales Socket de una aplicación montada consultan su observador activo;
socketautoimportado conserva conexiones por aplicación y libera listeners al desmontar componentes. HMR preserva el fallo original si también falla el rollback. - Los scripts
.meekoadmiten llamadas de expresión durante el setup para registrar hooks sin declaraciones artificiales. - Tipado de defaults de
defineProps, props de componentes hijos y contratos entre páginas y sidecars de ruta. - Hidratación de expresiones de texto inicialmente vacías sin perder eventos, verificada con el componente de login original en Chrome.
- Comprobación TypeScript por ámbito de las fuentes de app, shared, servidor, layers y módulos configurados.
- Errores de servidor normalizados con
requestIden páginas, API, RPC y WebSocket; cierre más robusto de recursos Node. - Los fallos de limpieza de raíces y componentes compilados conservan también el error original; los fallos de navegación cliente por historial y clic se observan sin rechazos pendientes.
- Los contratos de páginas, layouts y páginas de error rechazan props inseguras; el editor muestra esos diagnósticos sobre
definePropsal abrir o guardar el proyecto. - Los componentes recibidos como props comprueban también si sus props obligatorias pueden omitirse; el editor actualiza diagnósticos y colores al guardar módulos TypeScript importados.
- Las rutas sin parámetros rechazan
paramsen TypeScript; el editor vuelve a comprobar contratos de páginas cuando cambia un tipo importado por un sidecar. - Las navegaciones descartadas no conservan estado provisional ni tokens CSRF, y los observadores reactivos pendientes terminan de ejecutarse aunque otro falle.
- Socket comunica fallos de listeners, observadores de estado y reconexión, ignora mensajes de conexiones reemplazadas y continúa un broadcast hacia peers sanos si uno falla.
- Los observadores de errores de navegación y servidor pueden ser asíncronos sin producir rechazos sin manejar; si fallan, Meeko informa el fallo sin sustituir la respuesta original.
- Los handlers
on:eventasíncronos ya entregan su promesa al runtime para registrar rechazos; los errores de TypeScript de módulos.tsimportados señalan su archivo y posición reales. - Eventos, navegación, Socket y DevTools comparten una captura interna de callbacks síncronos y asíncronos; los E2E fallan también ante errores de consola.
effectrechaza callbacks que devuelven promesas en tipos y runtime; un rechazo asíncrono concurrente queda observado y registrado. Las aplicaciones que usabaneffect(async () => ...)deben iniciar el trabajo asíncrono desde un callback síncrono y manejar su cancelación.- Los contratos API rechazan
paramsque no coincidan con la ruta y valores dequeryincompatibles con el transporte URL. - La limpieza de listas libera todos los hijos aunque falle un release o una hidratación posterior; el middleware HTTP propaga fallos de
next()aunque su autor no lo espere. - El adaptador HTTP Node conserva varias cookies
Set-Cookiey detiene el streaming cuando el cliente se desconecta. - Comandos de proyecto generados por Meeko, layouts en
app/layouts, middleware de rutas y HTTP, y validación ampliada del consumidor externo. - CI selecciona el tarball generado por el build en lugar de fijar el nombre de una versión publicada.
- El workflow de publicación exige ahora las pruebas de la extensión VS Code sobre el SHA del tag.
[0.0.4] - 2026-10-24#
Framework full-stack#
- Rutas basadas en archivos con parámetros y navegación tipados, layouts persistentes, SSR e hidratación.
- Estado
useStateaislado por petición, prerender y composición de módulos y layers. - Starter
meeko init-frameworky generación de manifiestos mediante CLI y Vite.
Autoimports y tipos#
- Autoimports tipados en
.meekoy.tspara componentes, composables, stores y utilidades; símbolosapi,rpc,socketyuseSocket. - Declaraciones TypeScript generadas por ámbito, con separación entre código cliente, servidor y shared.
Servidor#
- API HTTP y RPC tipados mediante contratos Standard Schema; despacho RPC local durante SSR con la misma validación y autorización.
- Sesiones con adaptadores Redis y SQL, y canales WebSocket con transporte Node.
- Validación de entradas y salidas, autorización y errores normalizados.
Seguridad y correcciones#
- Aislamiento de peticiones SSR, protección CSRF y límites de imports entre cliente y servidor.
- Protección de rutas públicas y layers frente a enlaces simbólicos que salen del proyecto.
- Comprobaciones de Origin y autorización de WebSocket;
wsse carga sólo al activar el adaptador Node.
Verificación y herramientas#
- Mejoras de Vite y CLI, source maps de JavaScript con fuentes embebidas y baseline reproducible de rendimiento.
- Consumidor externo desde tarball, integración con Redis real en CI y E2E en Chromium, Firefox y WebKit.
0.0.3#
meeko init .configura una carpeta recién preparada con pnpm, conserva su manifiesto y archivos del gestor, y rechaza proyectos que ya contienen código o scripts incompatibles.- Hidratación sin escrituras iniciales redundantes de texto y atributos; señales sin lectores y filas que no usan el índice asignan menos memoria.
- Reconciliación de listas con claves que conserva regiones ordenadas y evita movimientos innecesarios; hidratación inicial sin reconciliación cuando el estado SSR sigue intacto.
- Aplicación piloto con SSR, hidratación, formularios, navegación, componentes anidados y API asíncrona, probada en desarrollo y producción con Chromium, Firefox y WebKit.
meeko init <directorio>genera un proyecto SSR independiente que fija la versión exacta de su compilador y runtime.- Línea de tiempo de señales en ambos paneles DevTools, con componente, valor seguro y expresiones de texto o atributo que leen cada señal, ubicadas en el
.meeko. - Primera publicación npm mediante Trusted Publishing, con procedencia firmada desde
meekojs/meekoypublish.yml.
0.0.2#
- DevTools experimental para inspeccionar instancias hidratadas, props, señales, eventos, árbol de componentes y estado de hidratación durante el desarrollo con Vite.
- Panel integrado y extensión local de Chrome DevTools con selección, resaltado y puntos de parada por instancia.
- Ruta
@meekojs/meeko/internalpara el runtime generado por el compilador. Los exports internos anteriores siguen disponibles en la raíz durante la serie experimental para conservar compatibilidad. - Configuración de depuración de la extensión de VS Code y ampliaciones de sus verificaciones y de los benchmarks reproducibles.
La sintaxis y las API de la serie 0.x siguen siendo experimentales. El compilador y su runtime deben usar la misma versión del paquete.
0.0.1#
- Primera versión pública con componentes
.meeko, SSR, hidratación, señales reactivas, plugin de Vite, HMR, CLI y extensión local de VS Code.