Editores
Meeko incluye un servidor de lenguaje (Language Server Protocol) que da a cualquier editor compatible las mismas funciones que la extensión de VS Code: diagnósticos de meeko check, completado (también de HTML en la plantilla y de CSS en <style>), hover, ir a definición, referencias, renombrado entre archivos .meeko y .ts, ayuda de firma, símbolos del documento, plegado, formato, tokens semánticos, CodeLens, edición vinculada de etiquetas, colores de <style> y acciones rápidas (añadir imports, resolver un autoimport ambiguo con un import explícito, quitar params de un href estático, añadir each:key y mover un sidecar a <script server>).
El bloque <script server lang="ts"> de una página se analiza como un módulo TypeScript propio, con los autoimports de servidor (o de cliente si su render estático es "client") y posiciones del .meeko: diagnósticos de tipos, completado, hover, navegación, referencias, símbolos y formato. Un bloque fuera de app/pages de alguna raíz configurada, o en un +layout o +error, produce MEEK044 sobre su etiqueta de apertura. Los export const de un bloque con código de nivel superior tienen su tipo en el <script> y en la plantilla de la página, y «ir a definición» sobre uno de ellos lleva a su export.
Fragmentos de código#
Escribe un prefijo y elige el fragmento en el completado. Tab recorre los campos editables y mantiene sincronizados los nombres repetidos. Los esqueletos usan el nombre del archivo como título inicial.
| Prefijo | Dónde | Contenido |
|---|---|---|
meeko, mcomp |
Raíz de un .meeko |
Componente con <script lang="ts"> y <template> |
mpage |
Raíz de una página .meeko |
definePage en <script server> y plantilla |
mlayout |
Raíz de un layout .meeko |
Layout con <MeekoOutlet /> |
mprops |
<script> |
Interfaz Props y defineProps<Props>() |
mpropsd |
<script> |
Props desestructuradas con defaults reactivos, desde Meeko 1.8 |
memits |
<script> |
defineEmits con argumentos tipados |
mmodel |
<script> |
defineModel con valor por defecto |
msnippets |
<script> |
Contrato defineSnippets |
mexpose |
<script> |
defineExpose de un valor del setup |
msignal |
<script> |
Señal con signal |
mcomputed |
<script> |
Valor derivado con computed |
meffect |
<script> |
Efecto reactivo con effect |
mmounted |
<script> |
Temporizador de navegador con onCleanup dentro de onMounted |
mref |
<script> |
useElementRef y comentario con el ref: que debes añadir a la plantilla |
mif |
<template> |
Elemento con if:when |
meach |
<template> |
Lista con each:of, each:as y each:key |
mrender |
<template> |
<render> con snippet y args |
msnippet |
Dentro de una etiqueta de componente | <snippet> con name y params |
mis |
<template> |
<component is> dinámico |
mform |
Raíz de un .meeko |
Componente con useForm y formulario enlazado |
mapi |
server/api/*.get.ts |
defineApiRoute con una respuesta JSON |
mrpc |
server/rpc/*.ts |
defineRpc con imports de schemas de shared/ |
mmiddleware |
app/middleware/*.server.ts |
defineServerRouteMiddleware con nombre |
mcomposable |
Composable .ts |
AbortController con liberación mediante onCleanup |
Los fragmentos de script y plantilla se insertan en el bloque indicado: ajusta los nombres a los valores del componente y reúne los imports si ya existen. mmounted registra la limpieza dentro del callback para respetar MEEK128. Llama al composable de mcomposable durante el setup de un componente. En mrpc, crea o reutiliza los schemas reales en shared/, ajusta la ruta relativa a la profundidad del archivo y adapta el handler al schema de salida. mmiddleware crea middleware con nombre (global: false): decláralo en middleware de la página o del grupo para ejecutarlo.
VS Code elige los fragmentos declarados en contributes.snippets por el lenguaje en la posición del cursor, y dentro de un bloque ese lenguaje es typescript o html, no meeko. Por eso la extensión sólo declara los esqueletos. Los de script y plantilla los ofrece su proveedor de completado en <script>, en <script server> y en el contenido de <template>, y no en <style>, en comentarios, en cadenas, en valores de atributo ni en expresiones {…}. De ahí salen tres límites:
- Insert Snippet lista lo declarado para el lenguaje del cursor: los esqueletos en la raíz y lo declarado para
typescripten<script>. Los fragmentos de script y plantilla no aparecen ahí: se eligen en el completado. - Con
"editor.tabCompletion": "on",Tabtambién los expande. Con"onlySnippets", sólo expande los declarados, así que dentro de un bloque no los expande. - Un fragmento tuyo declarado para
meekosale en la raíz. Para usarlo en<script>, decláralo paratypescript.
La declaración sólo selecciona el lenguaje y el archivo de fragmentos; no admite una condición por proyecto ni por archivo. Por eso los cuatro fragmentos TypeScript aparecen también fuera de proyectos Meeko mientras la extensión está habilitada, y dentro de los bloques TypeScript de un .meeko, comentarios y cadenas incluidos. Sus prefijos específicos (mapi, mrpc, mmiddleware, mcomposable) reducen las coincidencias accidentales.
Funciones propias de Meeko#
Además de las funciones de TypeScript, el servidor entiende la reactividad, los componentes (también los genéricos) y la estructura de la aplicación. Todas se calculan de forma estática, sin la aplicación en marcha; los textos que muestran no forman parte del contrato. La petición meeko/executionZones y los modificadores de token propios (signal, computed, resource y reactive) son internos de la extensión de VS Code y pueden cambiar en un minor (véase stability.md).
Tokens semánticos de reactividad#
Los tipos de token son los de TypeScript. Sobre ellos, el servidor añade modificadores:
| Modificador | Marca |
|---|---|
signal |
Una señal (signal, useState, defineModel, useElementRef) declarada en el nivel superior del <script>, y cada uso. |
computed |
Un valor computed o una señal de sólo lectura, y cada uso. |
resource |
Un recurso de datos (useApi, useRpcQuery, useRpcMutation), y cada uso. |
reactive |
Los tokens de una expresión de la plantilla que lee un valor reactivo. |
modification |
Una escritura de .value, o una llamada que vuelve a cargar un recurso (refresh, mutate). Es el modificador estándar de LSP. |
Lo que decide si un .value es reactivo es el tipo, no el nombre: sólo cuenta el value de una Signal o ReadonlySignal de Meeko (incluidas las que devuelven useElementRef y los composables). props.value, input.value o event.currentTarget.value son lecturas normales y no se marcan.
Un tema o un cliente puede colorearlos con reglas de token semántico (por ejemplo, variable.signal o *.modification). La extensión de VS Code trae colores por defecto mediante ámbitos TextMate. Desde la 1.2 de la extensión, reactive no cambia el estilo por defecto (antes ponía en cursiva lo que leía un valor reactivo); para marcarlo, añada "*.reactive": { "italic": true } en rules de editor.semanticTokenColorCustomizations.
Hover#
Además del tipo de TypeScript, el hover añade:
- Señales,
computedy recursos. Su tipo, la lista de lecturas y escrituras en el script y la plantilla, con enlaces a cada una, y cómo llega su valor al navegador: si se serializa en el estado del componente, si viaja en el payload, si se recalcula en el navegador o si sólo existe en el navegador. - Componentes. Desde la
1.4, la ficha es la firma de la etiqueta en un bloque TypeScript, con una línea por prop y su tipo declarado (variant?: CardVariant), por evento (on:save(id: number)) y por modelo (model:open?: boolean), y los snippets como contenido (header?(),default()); le siguen un enlace que abre el.meekoy el JSDoc de cada prop, evento, modelo y snippet que lo tenga. Las secciones vacías se omiten. Sobre una etiqueta de componente o el identificador que lo importa: el archivo.meekoy sus props, eventos, modelos, snippets y valor expuesto. En la etiqueta de un componente genérico, el hover muestra los parámetros de tipo que infieren sus props (T = User) y las props, eventos, modelos, snippets y valor expuesto ya resueltos con ellos. Desde la1.8, una prop con valor por defecto lo muestra tras su tipo (size?: "sm" | "md" = "md"), en la ficha, en el hover de la prop sobre la etiqueta y en el detalle del completado, tanto si el default va en el argumento dedefinePropscomo en la desestructuración. - Snippets. Sobre el
namede un<snippet>: la firma del snippet del hijo, con los tipos de sus parámetros y si es opcional, y desde la1.4su JSDoc. - Etiquetas propias de Meeko. Desde la
1.4,<MeekoLink>,<MeekoOutlet />y<MeekoThemeToggle>tienen la misma ficha que un componente: sus props con los tipos deMeekoLinkProps,MeekoOutletPropsyMeekoThemeTogglePropsde@meekojs/meeko/framework(en<MeekoLink to="/users/[id]">,toes esa ruta ybind:paramssus parámetros), su contenido (default), el elemento que renderizan, su documentación y un enlace a la guía. «Ir a definición» sobre la etiqueta abre ese tipo, y sobre el valor deto, la página. El hover y el completado deto,bind:paramsybind:searchusan los tipos y el JSDoc deMeekoLinkProps;on:clicky los demás atributos son los de un<a>, y el completado ofrece los atributos de<a>salvohref. Con un@meekojs/meekoanterior a1.4, que no declara esos tipos, la ficha usa la descripción incluida en el servidor. Un componente del proyecto con uno de esos nombres conserva su propia ficha (MEEK106). Desde la1.5,<MeekoAwait>tiene la misma ficha (MeekoAwaitProps, con sus snippetsdefault(refreshing),pendingyerror(error, retry)), sufores una expresión con hover y definición, y dentro de su contenido los datos deforse leen como cargados, sin!. También desde la1.5,<MeekoTeleport>(MeekoTeleportProps, contoydisabled; el completado deto="…"ofrecebodyy los#iddel componente, deapp/app.meekoy de los layouts),<MeekoPopover>(MeekoPopoverProps) y<MeekoSelect>(MeekoSelectProps, con el tipo de sus opciones) tienen hover, completado y la comprobación de sus props, y la gramática las colorea como etiquetas propias.
Desde core 1.8, <MeekoPopover>, <MeekoSelect> y <MeekoThemeToggle> están obsoletos: el compilador emite MEEK121 y el editor tacha la etiqueta y explica el reemplazo en el hover. Siguen funcionando en 1.x y se retiran en 2.0. Usa UIPopover, UISelect y UIThemeToggle del módulo @meekojs/ui 0.2; el sistema de temas permanece en core. Véase Migración a 1.8.
- Rutas, API y RPC. Sobre el ID de
href("/users/[id]"): la página, susparams, su modo de render y sus layouts. SobreuseApi("/api/notes")oapi.notes.get: el método, la ruta y el handler, con los schemas de su contrato. SobreuseRpcQuery("users.get"),useRpcMutation(...)orpc.users.get: el procedimiento, su URL y su handler con sus schemas. Desde la1.6, sobre un nombre demiddleware: ["auth"]endefinePage: el archivo deapp/middlewaredel middleware con nombre y sus páginas (o que es global o no existe), y «ir a definición» lo abre. Un middleware de ruta de servidor (*.server.ts) se marca como «sólo servidor».
Ir a definición en rutas, API y RPC#
«Ir a definición» sobre el ID de ruta de href abre la página, y sobre un endpoint o procedimiento abre su handler en la línea del contrato (defineApiRoute o defineRpc). Una etiqueta de componente lleva a su .meeko. El servidor descubre rutas, endpoints y procedimientos sin ejecutar sus módulos, igual que meeko routes; si el descubrimiento falla, usa .meeko/manifests/routes.json.
Desde 1.2, las cadenas de href("…"), useApi("…"), useApiMutation("…"), useRpcQuery("…") y useRpcMutation("…") son además enlaces (Ctrl+clic) a su página o handler, y una ruta que no existe no tiene enlace. La búsqueda de símbolos del workspace (Ctrl+T en VS Code) encuentra componentes, páginas (por su ruta, como /users/[id]), endpoints (GET /api/users) y procedimientos (rpc.users.get); acepta las letras salteadas (usid encuentra /users/[id]).
Pistas de tipo#
La plantilla no escribe los tipos de lo que declara: el servidor los muestra como pistas en línea después de each:as (each:as="user: User") y de cada parámetro de <snippet params>, también en los componentes genéricos. Las pistas largas se acortan a 60 caracteres.
Comandos «Meeko: nuevo…» (VS Code)#
- Nuevo componente:
AppCardcreaapp/components/App/Card.meekosi existe la carpetaApp(si no,AppCard.meeko);App/Cardes una ruta explícita. - Nueva página:
/userscreaapp/pages/users/index.meekoy/users/[id],app/pages/users/[id].meeko, con suuseHead. - Nuevo endpoint de API: pide el método y la ruta (
/users/[id], con o sin/api) y creaserver/api/users/[id].get.tscon sus schemas públicos (params,bodyenPOST/PUT/PATCHyresponse) enshared/schemas/users.ts, que añade al archivo si ya existe. Usa valibot, el validador por defecto, o zod si es el que declara elpackage.jsondel proyecto.
Usan el proyecto del archivo activo y la indentación de los .meeko, y nunca sobrescriben un archivo.
CodeLens#
- Valores reactivos. Sobre cada señal,
computedo recurso declarado en el nivel superior del<script>: cuántas lecturas y escrituras tiene y si la plantilla lo usa. Al pulsarlo se listan esas posiciones. - Páginas. En la primera línea de una página: su ruta, su modo de render y sus layouts. Si su
<script server>exporta datos desde el código de nivel superior o declaraload, otra lente muestra el tipo de los datos que envía al navegador (con código de nivel superior, un campo por cadaexport constoexport let, sinhead), y avisa si contiene valores que la serialización JSON rechaza (funciones,bigint,Date,Map…) o si el tipo es muy grande. En una páginarender: "client"indica que el loader se ejecuta en el navegador.
La lente se pide con textDocument/codeLens. Las lentes de valores reactivos usan el comando editor.action.showReferences con los argumentos [uri, posición, ubicaciones] en formato LSP; un cliente que no tenga ese comando puede ignorarlo o asociarlo a su propia lista de referencias. Las lentes de página no tienen comando.
Zonas de ejecución#
El servidor indica, por líneas completas, dónde se ejecuta el código de un .meeko:
Zona (kind) |
Motivo (reason) |
Líneas |
|---|---|---|
server |
server-block |
<script server> de una página. |
browser |
client-route-block |
<script server> de una página render: "client", que se ejecuta en el navegador. |
shared |
setup |
El setup del <script>, que se ejecuta en SSR y al hidratar. |
browser |
client-page |
El setup de una página render: "client". |
browser |
continuation |
El setup a partir del primer await de navegador. |
browser |
onMounted |
El cuerpo de cada callback de onMounted. |
browser |
browser-function |
Una función del setup que sólo se usa desde código de navegador. |
browser |
inline-handler |
Una función escrita en un on:* de la plantilla que ocupa sus propias líneas. |
Funciones de navegador. Una función declarada en el nivel superior del setup (function guardar() {} o const guardar = () => {}) sale como «Sólo navegador» cuando todos sus usos se ejecutan en el navegador: como manejador on:* de la plantilla (también en un componente), dentro de onMounted, en la continuación tras el primer await de navegador o desde otra función de navegador. El hover de la zona dice por qué (por ejemplo, «Sólo se usa como manejador de on:click y en onMounted»). La función sigue compartida si también se llama en el nivel superior del setup, dentro de effect o computed, desde una expresión de la plantilla que se renderiza, se pasa como prop o modelo de un hijo, se publica con defineExpose o tiene un uso que el análisis no clasifica. Un manejador que se llama al enlazarse (on:click="crear(guardar)") cuenta como uso compartido, porque en un componente se evalúa durante el SSR.
Manejadores en la plantilla. Una función escrita en un on:* cuyo atributo ocupa sus propias líneas, como una flecha de varias líneas que empieza en on:click="() => { y termina en }" (con > o /> como mucho detrás), también forma una zona de navegador con el nombre del evento en su hover. Una función que comparte línea con otros atributos o con contenido de la plantilla no se marca.
La plantilla (salvo esos manejadores) y los estilos no forman zonas, y las zonas no se solapan: una zona más concreta (una función de navegador, onMounted, un manejador) sustituye a la del setup en sus líneas. El análisis es sintáctico y por líneas completas; si no puede clasificar el setup, las funciones se quedan en la zona compartida. Otros clientes las piden con la petición propia meeko/executionZones:
// Petición
{ "textDocument": { "uri": "file:///app/pages/index.meeko" } }
// Respuesta
{ "zones": [{ "kind": "server", "reason": "server-block", "label": "…", "detail": "…",
"range": { "start": { "line": 0, "character": 0 }, "end": { "line": 6, "character": 9 } } }] }label es un título corto y detail una explicación de un párrafo para un hover. La respuesta es null para un documento que el servidor no modela.
Acciones rápidas#
Además de las de imports y href:
- Props leídas una sola vez (
MEEK101). «Convertir en computed» cambiaconst upper = props.label.toUpperCase()porcomputed(() => props.label.toUpperCase())y añade.valuea cada uso, en el script y en la plantilla (una propiedad abreviada conserva su clave:{ upper }pasa a{ upper: upper.value }). Una desestructuraciónconst { label, title: heading } = propsse convierte en uncomputedpor nombre. Si sólo interesa el primer valor, «Leer … sólo una vez con untrack» lo declara conuntrack(() => props.x). - Añadir
each:as(MEEK022). En una lista sineach:as, lo añade con el singular de la lista (variants.value→variant) oitem. Funciona aunque el componente no compile, que es lo que provoca ese error. - Crear un componente. En una etiqueta como
<AppNuevo bind:title="name.value" />cuyo componente no existe, creaapp/components/App/Nuevo.meeko(dentro de la carpeta más larga deapp/componentscon la que empieza el nombre, como los nombra el autoimport) con una prop por cadabind:, del tipo del valor que pasa la etiqueta. Sólo se ofrece si el cliente admite crear archivos en una edición de workspace (resourceOperationsconcreate). - Llaves dobles (
MEEK020). Por costumbre de Vue,{{ props.label }}o{ {props.label} }en el texto de la plantilla leen sus llaves interiores como un objeto literal. El compilador,meeko checky el editor dicen «Meeko interpola con una sola llave: {expr}, no {{expr}}» sobre la interpolación entera. «Convertir a {props.label}» la corrige; «Mostrar las llaves como texto» la convierte en{"{ props.label }"}, como en una llave sin cerrar, y es la única opción cuando el interior no es una expresión ({{hola mundo}}). «Corregir todas las del archivo» aplica a todas la que corresponde. Un objeto literal con sus claves ({{ a: 1 }}) es una expresión válida y no avisa. En VS Code, el segundo{escrito delante de un}cerrado automáticamente no abre otro par. - Añadir
each:key. En una lista sin clave, proponeeach:key="item.id"(u otra propiedad identificadora del elemento) o el propio elemento si es una cadena o un número. - Mover el sidecar a
<script server>. En las primeras líneas de una página con unx.route.tsjunto a ella, mueve su contenido a un bloque<script server lang="ts">y borra el sidecar en una sola edición. Los imports relativos siguen funcionando porque ambos archivos comparten carpeta. Sólo se ofrece si el cliente admite borrar archivos en una edición de workspace (workspace.workspaceEdit.documentChangesyresourceOperationscondelete).
Completado de atributos, directivas, modelos y snippets#
En el nombre de un atributo de la plantilla, el completado ofrece las directivas de Meeko que admite la etiqueta: if:when, each:of, each:as, each:key, each:index, bind:, on:, ref: y bind en cualquier etiqueta, también en las de componente; html en los elementos, model en input, textarea y select, y model: en los componentes. No repite las que la etiqueta ya tiene, y tras un prefijo (on:, model:) vuelve a abrir el completado con los nombres del hijo.
Como la extensión de HTML de VS Code y Vue, cada atributo se inserta con su valor y el cursor dentro de las comillas (title="|"), salvo que el nombre ya tenga un = detrás. En una etiqueta de componente del proyecto (<AppCard>, también autoimportada o de un módulo, como UICard), el completado ofrece primero lo que declara el hijo, con su tipo y su JSDoc, y después las directivas:
| El hijo declara | Se ofrece |
|---|---|
title: string, variant?: "a" | "b" |
title="…", variant="…" (abre sus valores) |
label?: string | number, unknown |
label="…" y bind:label="…" |
disabled?: boolean |
disabled (pasa true) y bind:disabled="…" |
item: Item y cualquier otro tipo |
bind:item="…" |
defineEmits<{ save: […] }>, onSelect?: … |
on:save="…", on:select="…" |
defineModel(), defineModel("open") |
model="…", model:open="…" |
Las props obligatorias van antes que las opcionales y no se repiten las que la etiqueta ya pasa (como texto o con bind:). Tras bind: se ofrecen todas las props, también las de texto (bind:title="todo.title"); tras on:, sólo los eventos, y tras model:, sólo los modelos con nombre. Meeko no tiene props prop={expr} al estilo de JSX: el editor nunca las inserta. Dentro de las comillas de un bind:, on:, model, if:when o each: vacíos se completan las expresiones de TypeScript, también justo al escribir la comilla de apertura, y en una prop de texto con valores literales, esos valores.
El hover de html advierte que inserta HTML sin escapar. Desde 1.3, cuando su valor es un string (o any) sin la marca TrustedHtml, el editor subraya toda la expresión con el aviso MEEK107, el mismo de meeko check; desaparece al marcar el HTML con trustHtml(…) donde se produce (véase HTML de confianza).
El completado ofrece los modelos del hijo tras model: en una etiqueta de componente, los nombres de los snippets del hijo en <snippet name="…"> y sus parámetros en params.
bind: en un elemento#
Desde 1.8, al escribir bind en un elemento HTML, además de bind (un objeto de atributos) y del bind: genérico, se ofrece cada atributo de esa etiqueta con bind:: bind:type, bind:disabled y bind:formaction en un <button>, bind:href en un <a>, bind:closedby en un <dialog>, y después los globales (bind:class, bind:style, bind:id…), el resto de globales, los aria-* y bind:data-…. Cada uno inserta bind:atributo="…" con el cursor dentro, reemplaza lo ya escrito (bind:cl) y muestra en detail el tipo que espera la expresión: bind:type: 'submit' | 'reset' | 'button', bind:disabled: boolean o bind:hidden: boolean | 'until-found' | 'hidden', porque true escribe el valor vacío. Los atributos obsoletos aparecen tachados. Los nombres, los valores y la documentación salen de los datos HTML de Meeko, con la misma tabla de atributos que el compilador. Dentro de las comillas de un atributo estático (type="…", role="…", aria-pressed="…") se ofrecen sus valores; en una lista separada por espacios, como role, sólo se reemplaza el valor que se escribe.
Lo que declara el script, según la directiva#
Desde 1.8, dentro del valor de una directiva el completado de TypeScript se ordena por lo que esa directiva espera. Primero va lo que encaja, con su tipo en detail, junto con el camino hasta el valor: name.value de una señal o un computed, props.title de defineProps, snippets.icon de defineSnippets o item.id del elemento de una lista. Después van el resto de nombres del script y de la plantilla, y por último los imports y los globales.
| Dónde | Primero |
|---|---|
ref: |
Los nombres de useElementRef("…") del script; los que aún no tienen etiqueta, antes |
on:click="…", on:save="…" |
Las funciones cuya firma admite el evento (MouseEvent, SubmitEvent, los argumentos de defineEmits); una sin parámetros, después |
model="…", model:open="…" |
Las señales escribibles del tipo del control (string en un texto, boolean en un checkbox) o del modelo; nunca un computed |
bind:type="…" en un elemento |
Los valores del atributo ('submit'), y después lo que tiene su tipo |
bind:title="…" en un componente |
Lo que es asignable al tipo de la prop |
if:when="…" |
Los booleanos y, después, lo opcional (snippets.icon) |
each:of="…", each:key="…" |
Las listas e iterables (props.items, rows.value); los campos del elemento (item.id) |
<render snippet="…"> |
Los snippets de defineSnippets; los dinámicos como snippets['cell-…'], con el cursor en el nombre |
{…} |
Las declaraciones de la plantilla y del script, antes que los imports y los globales |
Un valor vacío (model="", {}, snippet="") también se completa. Las referencias que todavía no tienen etiqueta (de tipo never) no se proponen como valor.
Mientras se escribe#
Un atributo a medio escribir no compila, y el diagnóstico dice lo que falta en cada paso: bind sin valor indica las dos formas (bind:atributo="expresión" o bind="obj"), bind: se marca sobre sí mismo (y no sobre la etiqueta) y pide el nombre del atributo, y bind:class sin = pide su valor. Cada edición vuelve a comprobar el documento; al terminar de escribir bind:class="props.class" el diagnóstico desaparece. Un resultado que llega de una versión anterior del documento se descarta.
HTML y CSS en la plantilla y en <style>#
Desde 1.2, el servidor también entiende el marcado y los estilos. Lee el texto, sin compilar el componente, así que funciona mientras una etiqueta o una regla están a medio escribir:
- Plantilla: completado de etiquetas, de atributos HTML (después de las directivas de Meeko y sin los manejadores
onclick…, que en Meeko se escribenon:click) y de sus valores (type="checkbox"), con la documentación de MDN en el hover. Trason:en un elemento, los eventos del DOM con su descripción. Los atributos de una etiqueta de componente salen del hijo (véase Completado de atributos), y los valores que son expresiones (bind:,on:,if:when,each:…,{…}) siguen en manos de TypeScript. - Etiquetas: la de apertura y la de cierre se renombran juntas (edición vinculada, que la extensión de VS Code activa con
editor.linkedEditingen los.meeko), y la extensión cierra la etiqueta al escribir>o</(ajustemeeko.autoClosingTags). Otros clientes la piden conmeeko/tagComplete, que devuelve un snippet como$0</div>. <style>: completado y hover de propiedades y valores, diagnósticos de CSS (:global(…)y las at-rules desconocidas no son errores) y el selector de color.- Clases: en
class="…"se completan las clases del proyecto (véase Completado de clases CSS), e ir a definición lleva de una clase a sus selectores. En<style scoped>, una clase que no nombran ni la plantilla ni el script se marca como sin uso (atenuada, con el códigoMEEK_UNUSED_CLASS). Cuenta cualquier aparición del nombre, también en uncomputedo enclassList.add("open"), y un prefijo antes de una interpolación (`badge--${tone}`) cubre todas las clases que empiezan por él. Las de:global(…)nunca se marcan, porque pueden alcanzar a otros componentes.
Datos HTML de Meeko#
Desde 1.8, las etiquetas, los atributos y sus valores salen de las especificaciones, no de una lista escrita a mano. packages/meeko/scripts/html-data.ts genera los módulos de packages/meeko/src/shared/html-data/, que se versionan:
tags.tsyattribute-names.ts: los nombres que necesita el runtime (etiquetas de<component is>, elementos vacíos, atributos de cada elemento parabind="obj"y atributos booleanos), como cadenas cortas.attributes.ts,aria.tsyevents.ts: los detalles para el compilador y el editor, que no llegan al bundle del navegador. Cada atributo lleva su descripción, sus valores enumerados (closedby:any,closerequestynone), si es booleano y si es experimental o está obsoleto; también los estados, propiedades y roles de ARIA, y los eventoson:.
Las fuentes son el HTML Living Standard del WHATWG (índices de elementos, atributos y manejadores, y las definiciones de valores), WAI-ARIA 1.3 con DPUB-ARIA y Graphics ARIA, Referrer Policy y @mdn/browser-compat-data, que indica qué es experimental u obsoleto y añade los atributos estándar de otras especificaciones (part, exportparts…). Lo que aceptaba Meeko 1.7 y ya no está en ninguna especificación se sigue aceptando, marcado como obsoleto, para no romper código existente.
Para actualizarlos:
pnpm -F @meekojs/meeko html-data # descarga las especificaciones y regenera
pnpm -F @meekojs/meeko html-data --offline # sólo regenera desde las instantáneasLa descarga guarda lo que extrae en packages/meeko/scripts/html-data/*.json, con la fecha y el commit de cada especificación, así que la generación es reproducible sin red. Para un @mdn/browser-compat-data nuevo basta con actualizar la dependencia y regenerar. tests/html-data.test.ts, dentro de pnpm -F @meekojs/meeko test, falla si un módulo no coincide con lo que generan las instantáneas.
Completado de clases CSS#
El servidor completa los nombres de clase sin depender de otra extensión (como «HTML CSS Support»). Se ofrecen en class="…", palabra a palabra, y dentro de los literales de cadena de bind:class (bind:class="open.value ? 'is-open' : ''" o el texto de un template literal, fuera de sus ${…}); el resto de la expresión sigue en manos de TypeScript. Cada clase aparece una vez, sin repetir las que ya tiene el valor, con su origen como detalle (este componente, Card.meeko, main.css (global)) y, al seleccionarla, la regla que la define como documentación. Las fuentes, por orden de prioridad:
- El
<style>del componente, con o sinscoped, con el texto del editor aunque no esté guardado. Mientras su CSS no se puede leer se conservan las últimas clases. - Los
<style>sinscopedde los demás.meekodel proyecto, que son globales. De un<style scoped>ajeno sólo cuentan las clases dentro de:global(…), que también son globales; las demás no se sugieren. - La hoja global. Mientras se ejecuta,
meeko devescribe en.meeko/styles/main.cssla hojaapp/styles/main.cssya procesada por Vite y PostCSS: con los@importresueltos, los@utilitiesexpandidos y lo que generen los plugins, como Tailwind. Si ese archivo existe, sus clases se ofrecen comomain.css (global, procesada). Además se lee siempreapp/styles/main.csstal cual, con los@importde archivos locales (./tokens.css,url(./base.css); los paquetes como"tailwindcss"se ignoran) y las@utility nombrede Tailwind 4, sin ejecutar PostCSS. Sin la hoja procesada, las clases que sólo generan@utilitieso Tailwind no aparecen hasta la próxima vez que se ejecutemeeko dev, y una hoja procesada antigua puede ofrecer clases que ya no existen hasta quemeeko devla reescriba.
Se leen las clases de las reglas anidadas (&.open, .a .b), de @media y @layer y de los argumentos de :is(), :where(), :not() y :global(). El hover sobre una clase muestra su origen y su regla, e ir a definición lleva a sus selectores en el componente, en los <style> de otros componentes y en las hojas del proyecto. Una clase que sólo existe en la hoja procesada lleva a su regla en .meeko/styles/main.css: no hay forma barata de saber qué @utilities la generó, y esa regla es la que llega al navegador.
El índice se guarda por archivo: un componente se vuelve a leer cuando cambia su texto y una hoja cuando cambian su fecha o su tamaño, o cuando el observador de archivos (**/*.css, también bajo .meeko/) avisa de un cambio. Al escribir sólo se vuelve a leer el <style> del componente editado; con 300 componentes y 3000 clases generadas, el completado responde en pocos milisegundos. Como sólo se lee CSS y nunca se ejecuta código del proyecto, funciona igual en un workspace sin confianza.
Componentes genéricos#
En el padre, cada etiqueta de un componente con generic se comprueba como en meeko check: los parámetros de tipo se infieren de sus props, y el hover, el completado y los diagnósticos de sus manejadores on:, modelos, snippets y refs usan ese tipo (T = User). Dentro del script del componente genérico, el hover y el completado tratan cada parámetro como su restricción, e «ir a definición» sobre T lleva al atributo generic; los diagnósticos son los de meeko check, que compila el componente con los parámetros reales. La gramática de VS Code resalta el valor de generic como una lista de parámetros de tipo de TypeScript, también con > dentro de una restricción.
Valores en vivo#
Los valores en vivo de las señales no pasan por el servidor de lenguaje: los publica meeko dev mediante el puente del editor, que la extensión de VS Code consume (véase su README). Cada meeko dev escribe su propio descriptor, .meeko/dev/editor-bridge-<puerto>.json, y la extensión se conecta a todos los que encuentra, así que funcionan varios servidores de desarrollo a la vez. Una señal que es la misma que otra, como el modelo que un hijo recibe con defineModel, lleva el campo alias con la señal principal: la extensión la muestra como open = true (= dialogOpen), su hover dice «alias de dialogOpen» y editarla edita la principal. Otro editor puede leer el mismo puente, teniendo en cuenta que su protocolo es sólo de desarrollo y no forma parte del contrato público.
Un meeko dev arrancado con MEEKO_EDITOR_BRIDGE=0 (en la shell o en el .env del proyecto), o que escucha en una dirección concreta fuera de loopback, no escribe descriptor. Desde 1.7.1 lo dice tras la URL (Valores en vivo del editor desactivados (MEEKO_EDITOR_BRIDGE=0)) y lo anota en su registro, .meeko/dev/server.json, con editorBridge: false y el motivo en editorBridgeReason. Mientras ese proceso sigue en marcha, la extensión muestra «Meeko live: desactivado en meeko dev» en la barra de estado, con el motivo y cómo activarlo en el tooltip, en lugar de «desconectado». Para recuperar los valores en vivo, quita MEEKO_EDITOR_BRIDGE=0 y reinicia meeko dev.
Arrancar el servidor#
El servidor viene en el paquete @meekojs/meeko. Instálalo como dependencia del proyecto para que el editor use la misma versión que el compilador:
pnpm add -D @meekojs/meekoHay dos formas equivalentes de arrancarlo; ambas hablan JSON-RPC por la entrada y salida estándar:
meeko lsp --stdio
meeko-language-server --stdioDesde la raíz del proyecto, npx meeko-language-server --stdio (o pnpm exec meeko-language-server --stdio) usa el binario de node_modules/.bin. Además de --stdio, el servidor acepta --node-ipc, --socket=<puerto> y --pipe=<nombre>. Sin transporte termina con código 1 y muestra el uso. Requiere Node.js 24 o posterior.
El servidor reconoce los componentes por la extensión .meeko de su ruta; el languageId que envíe el editor no importa. Basta con asociar los archivos .meeko a un tipo de archivo propio (o a HTML) y lanzar el servidor para ese tipo.
Opciones de inicialización#
Se envían en initializationOptions de la petición initialize. Todas son opcionales.
| Opción | Por defecto | Efecto |
|---|---|---|
trusted |
true |
Con true, el servidor ejecuta meeko.config.* del proyecto con Node, también el setup de sus módulos, para conocer sus autoimports, componentes y layers, y comprueba los contratos de páginas y layouts al guardar. Con false no ejecuta código del proyecto: lee la configuración de forma estática (sólo listas literales; de modules, sólo las carpetas de layer), así que los autoimports y componentes de los módulos no existen para el editor, y omite los contratos. |
projectContracts |
true |
false desactiva la comprobación de contratos de páginas (MEEK_PAGE_PROPS, MEEK_LAYOUT_PROPS, MEEK_ERROR_PROPS) al guardar. |
diagnosticsDelay |
300 |
Milisegundos de espera tras una edición antes de comprobar el componente. |
format |
{} |
Ajustes de formato para clientes sin workspace/configuration, por ejemplo { "indentSections": true }. Si el cliente admite esa petición, prevalece su sección meeko.format. |
Fuera de VS Code trusted vale true porque es el usuario quien configura el servidor para su proyecto, del mismo modo que meeko check ejecuta la configuración. Usa trusted: false al abrir código de terceros. La extensión de VS Code envía el estado de confianza del workspace.
Formato con Prettier#
El servidor formatea los .meeko con Prettier y el plugin de Meeko (@meekojs/meeko/prettier), así que el editor, la CLI de Prettier y la CI dan el mismo resultado:
- Script, estilo y plantilla.
<script>y<script server>se formatean como TypeScript y<style>como CSS (o SCSS/Less segúnlang). La plantilla se formatea como HTML: los atributos largos van uno por línea, se respetan las etiquetas PascalCase y el autocierre, y los valores debind:,bind(desde 1.5),on:,if:when,each:of,each:key,model,<component is>y<render>y el texto{expr}se formatean como expresiones TypeScript. Una expresión que Prettier no entiende se deja tal cual.<!-- prettier-ignore -->excluye el nodo siguiente. - Arrays como los objetos. Con la opción
meekoPreserveArrayBreaks(por defectotrue), un array, un patrón de desestructuración (const [a, b] = …) o una tupla de TypeScript que en el código tiene un salto de línea entre[y su primer elemento se imprime expandido, un elemento por línea (los arrays de números se reparten por líneas, como hace Prettier al romperlos). Si no hay salto ahí, decide Prettier, que lo deja en una línea si cabe enprintWidth. Es la regla deobjectWrap: "preserve"para los objetos: para colapsar un array basta con unir[y el primer elemento y volver a formatear. Los comentarios y huecos ([, a]) entre[y el primer elemento cuentan para el salto, y un array vacío no se fuerza. Con"meekoPreserveArrayBreaks": falsela salida es exactamente la de Prettier. - Atributo
classnormalizado. Desde 1.3, el valor de cadaclass="…"estático de un elemento se normaliza siempre, sin opción, como hace Prettier con el HTML suelto: se quitan los espacios del principio y del final, cualquier secuencia de espacios, tabuladores o saltos de línea queda en un espacio y una clase repetida se elimina (se conserva la primera). Sólo cuentan como separadores los espacios ASCII que separan clases en HTML; un espacio no separable forma parte de la clase.class=" "queda comoclass=""y<p class>no cambia. No se tocanbind:classni otras directivas, otros atributos, elclassde una etiqueta de componente (<Card class="…">o<component is>, donde es una prop estática) ni un valor con referencias de carácter (&). Los atributos estáticos de Meeko no interpolan{expr}: su valor es texto literal, así que el cambio no altera las clases del elemento. - Orden de las clases. La opción
meekoSortClassesordena esas mismas clases:"off"(por defecto) conserva su orden,"alphabetical"las ordena por código de carácter (Bantes quea, sin depender del idioma) y"stylesheet"sigue la hoja global del proyecto, comoprettier-plugin-tailwindcsssigue la de Tailwind. Véase Orden por la hoja global. Desde1.8, con un orden elegido también se aplica alclassde una etiqueta de componente y a las cadenas debind:class, ymeekoClassFunctionsnombra las funciones cuyas cadenas son clases (["tv", "clsx", "cn"]). Guardar en el editor da lo mismo que@meekojs/prettier-pluginen la CLI, así queprettier --checkpasa después de guardar. - Qué Prettier se usa. En un workspace de confianza, si el proyecto tiene
prettier, se usa ese Prettier con su configuración (.prettierrc,.editorconfig, plugins). Si no, se usa el que incluye Meeko, con las opciones del editor (tabSize,insertSpaces) ymeeko.format.indentSections; la configuración del proyecto prevalece sobre ambas. En un workspace no confiable se usa siempre el incluido, sin leer la configuración del proyecto, porque cargarla ejecuta código. - Espacios de la plantilla. El compilador conserva los espacios de la plantilla como nodos de texto; sólo descarta el que tiene un salto de línea en el borde de un
<snippet>y el que separa los<snippet>de un componente. Desde 1.8, con cualquierhtmlWhitespaceSensitivity, también la de por defecto, el formato no añade ni quita ningún espacio en el límite entre dos nodos, también en los bordes de<template>:<li each:of="a" each:as="b">{b}</li>sigue sin nodos de espacio, con los saltos de línea dentro de las etiquetas (>{b}</li, y el>en la línea siguiente). Si prefiere el contenido en su propia línea, escriba usted ese salto: el formato lo conserva y esos espacios son nodos de texto, invisibles en un bloque, pero no entre elementos en línea oinline-block. Un espacio sí puede pasar a ser un salto de línea con su sangría, que se pinta igual salvo en un elemento conwhite-space: prepor CSS: use<!-- prettier-ignore -->en él.<pre>y<textarea>no cambian. La sensibilidad decide dónde van los saltos de línea que el espacio permite, no si hay espacio. .prettierignorey.gitignore. Desde 1.8, un archivo que excluyen no se formatea, ni al guardar coneditor.formatOnSaveni con «Formatear documento»: el servidor no devuelve ediciones, igual que la CLI de Prettier. Busca esos archivos en la carpeta del.meekoy en las superiores, hasta la raíz del repositorio (la carpeta con.git), así que valen el de la raíz y el de un subproyecto, y cada patrón es relativo a la carpeta de su archivo. En un workspace no confiable también se respetan, porque sólo se leen sus patrones. Es la forma de conservar una plantilla escrita en una sola línea, donde los espacios entre etiquetas son texto: añada el archivo a.prettierignore.- Límites. Sólo se formatea el documento completo, no un rango. Un archivo que no compila no se formatea.
Para formatear desde la CLI o en CI, añada el plugin a la configuración de Prettier del proyecto:
{ "plugins": ["@meekojs/meeko/prettier"], "meekoIndentSections": false }npx prettier --check "app/**/*.meeko"meekoPreserveArrayBreaks no se limita a los .meeko: el plugin sustituye el printer estree de Prettier, así que con el plugin en la configuración del proyecto la regla se aplica a todo el JavaScript y TypeScript que formatee Prettier (.ts, .js, .tsx, .mjs…), al script de los .meeko y a las expresiones de la plantilla (bind:, each:of, {expr}…), con los parsers typescript, babel y babel-ts. Ese printer delega todo, al formatear, en el printer estree del Prettier que lo ejecuta (la CLI y la extensión de Prettier del proyecto, o el Prettier que incluye la extensión de Meeko si el proyecto no tiene uno): el resto del formato es el de esa versión aunque no coincida con la que instala @meekojs/meeko, y con false la salida es exactamente la de ese Prettier. Si otro plugin de la configuración también sustituye estree, Prettier usa sólo el del último de la lista plugins: si es el otro, meekoPreserveArrayBreaks no tiene efecto; si es el de Meeko, éste envuelve el printer del otro plugin, que sigue imprimiendo todo lo demás. El servidor de lenguaje no tiene un ajuste propio para esta opción: se desactiva en la configuración de Prettier del proyecto, que también lee la extensión de Prettier para los .ts.
Orden por la hoja global#
Con "meekoSortClasses": "stylesheet" (en la CLI, --plugin @meekojs/meeko/prettier --meeko-sort-classes stylesheet; la CLI sólo reconoce la opción si el plugin se pasa con --plugin), cada clase toma la posición de su primera aparición en un selector de la hoja global, y después en el <style> del componente (con o sin scoped), en su orden: las del componente cuentan como definidas después de las globales. Las clases que no aparecen en ninguna de las dos van primero, en el orden en que estaban; después, las conocidas por su posición. Con .stack antes que .card en la hoja, class="extra card stack" queda class="extra stack card".
- Qué hoja.
app/styles/main.cssde la raíz del proyecto: la primera carpeta, subiendo desde el archivo, conmeeko.config.*o con unpackage.jsonque tiene@meekojs/meekoendependenciesodevDependencies(el mismo criterio quemeeko clean). Se procesa con la API de Vite (resolveConfigypreprocessCSS, con el Vite que resuelve el proyecto) igual que enmeeko dev: con elpostcss.config.*del proyecto, resolviendo los@importy expandiendo los@utilitiesdemeekoUtilities(). La configuración de Vite que añaden los módulos demeeko.config.tsno se aplica. - Determinismo. El resultado no depende de que
meeko devesté en marcha: el plugin procesa la hoja él mismo, así que el editor, la CLI y la CI ordenan igual con los mismos archivos. - Caché. La hoja procesada se reutiliza durante todo el proceso (una ejecución de la CLI, el servidor de lenguaje) mientras no cambien la fecha de modificación ni el tamaño de
main.css, de los archivos que importa, de los módulos de@utilitiesy los que estos importan, ni de los archivos de configuración de PostCSS de la raíz. Al cambiar cualquiera de ellos, el siguiente formateo vuelve a procesarla. La caché propia de Prettier (prettier --cache, desactivada por defecto) no conoce la hoja: tras cambiarla, ejecute la CLI sin--cacheo borre esa caché. - Si no hay hoja. Si no se encuentra el proyecto, no existe
app/styles/main.csso falla al procesarse, las clases no se ordenan (sólo se normalizan) y el formateo sigue. Se avisa una vez por proyecto en la salida de errores ([meeko] Aviso: meekoSortClasses "stylesheet" no ordena las clases: …), nunca en la salida estándar donde la CLI escribe el código. Una hoja que falló se reintenta cuando cambia uno de sus archivos o, como mucho, dos segundos después. - Workspaces no confiables. Procesar la hoja ejecuta código del proyecto (la configuración de PostCSS y los módulos de
@utilities). La CLI de Prettier ya ejecuta código del proyecto al cargar su configuración; en VS Code, en un workspace no confiable el servidor no lee esa configuración y fuerza"off": sólo se normaliza. - Rendimiento. En un proyecto real con dos módulos
@utilitiesy ocho@import, la primera vez que se procesa la hoja tarda unos 220-260 ms (cargar Vite y los módulos incluidos); cada formateo siguiente sólo comprueba las fechas de los archivos, en torno a 1,5-2 ms.
El paquete @meekojs/prettier-plugin#
Plugin de Prettier para secciones TypeScript, plantilla y CSS de componentes .meeko.
export default {
plugins: ['@meekojs/prettier-plugin'],
meekoIndentSections: true,
}Requiere Prettier 3 y Meeko. Conserva el contrato de formato de la entrada anterior @meekojs/meeko/prettier, que sigue disponible por compatibilidad en Meeko 1.x. La implementación del paquete usa las entradas públicas /compiler y /parse; no importa archivos privados de Meeko.
Orden de las clases#
meekoSortClasses ordena las clases de cada atributo estático class="…" de la plantilla. En un elemento, el valor se normaliza siempre: espacios colapsados y sin clases repetidas. Con un orden elegido (cualquier valor menos "off"), también ordena y normaliza el class de una etiqueta de componente, que es la prop class, y las cadenas de bind:class (véase Cadenas en expresiones); con "off", esos valores se quedan como están.
| Valor | Orden |
|---|---|
"off" |
El del código (por defecto). |
"stylesheet" |
Primera aparición en app/styles/main.css, procesada como en meeko dev, y después en el <style> del componente; las clases desconocidas, primero. |
"tailwind" |
El oficial de Tailwind CSS 4, el mismo de prettier-plugin-tailwindcss; las clases que Tailwind no conoce, primero y en su orden. |
"alphabetical" |
Por código de carácter. |
export default {
plugins: ['@meekojs/prettier-plugin'],
meekoSortClasses: 'tailwind',
// Opcional; relativa a la raíz del proyecto Meeko. Por defecto, app/styles/main.css.
meekoTailwindStylesheet: 'app/styles/main.css',
}Con "tailwind", el plugin carga el design system de la hoja meekoTailwindStylesheet con sus @import, @theme, @plugin, @utility y @custom-variant, usando el tailwindcss 4 instalado en el proyecto (es una dependencia opcional: no se instala con el plugin). Los módulos de @plugin se cargan con @tailwindcss/node (también en TypeScript) si el proyecto tiene @tailwindcss/postcss o @tailwindcss/vite; sin ellos, sólo módulos JavaScript. El design system se reutiliza entre archivos y se recarga cuando cambia la hoja o algo que importa. Si falta tailwindcss, la hoja no existe o Tailwind la rechaza, las clases no se ordenan y se avisa una vez. En un workspace no confiable del editor, "stylesheet" y "tailwind" se comportan como "off", porque ejecutarían código del proyecto.
prettier-plugin-tailwindcss no reconoce el parser meeko; con "tailwind" no hace falta.
Cadenas en expresiones#
meekoClassFunctions nombra las funciones cuyas cadenas son listas de clases, como tailwindFunctions de prettier-plugin-tailwindcss. Por defecto es [], y sin un meekoSortClasses distinto de "off" no hace nada.
export default {
plugins: ['@meekojs/prettier-plugin'],
meekoSortClasses: 'tailwind',
meekoClassFunctions: ['tv', 'clsx', 'cn'],
}const button = tv({
base: 'flex text-white', // antes: 'text-white flex'
variants: { size: { sm: 'p-1 text-sm', lg: on ? 'block p-4' : 'flex p-2' } },
compoundVariants: [{ size: 'sm', class: 'mt-1 block' }],
})- Dónde. En todo el JavaScript y TypeScript que Prettier formatee con el plugin cargado:
<script>,<script server>, las expresiones de la plantilla y los archivos.ts,.tsx,.jsy.mjs. No hace falta otro parser: el plugin ya sustituye el printerestreede Prettier y ordena el AST antes de imprimir. En un.ts, el orden sale del proyecto Meeko que contiene el archivo; con"stylesheet", sólo de la hoja global. - Qué se ordena. Los literales de cadena y las plantillas de cadena de los argumentos, a cualquier profundidad, a través de objetos, arrays, spreads, ternarios,
&&,||,??,+yas. Sirve parabase,slots,variants,compoundVariantsycompoundSlotsdetv(). - Qué no se ordena. Las claves de un objeto, las comparaciones y la condición de un ternario, el cuerpo de una función, los argumentos de una llamada no nombrada (
cn(t('page title'))), las plantillas etiquetadas, las cadenas fuera de una función nombrada y los valores con una secuencia de escape (\t,), cuyos tokens no son el texto del código. - Nombres. Se comparan literalmente, sin seguir los
import:tvno cubre un alias nistyles.tv(…), y un miembro se escribe entero (styles.cn). - Plantillas de cadena. Los espacios junto a
${…}no cambian, y un token pegado a${…}(p-${size}) no se mueve porque es sólo parte de una clase:`text-white flex p-${size} block`queda`flex text-white p-${size} block`. Lo mismo vale para los extremos de una concatenación con+. Una plantilla sin${…}se trata como una cadena. - Clases desconocidas y repetidas. Las reglas de
class: las desconocidas, primero; sin repetidas y con los espacios colapsados. Si el orden no se puede cargar, se avisa una vez y las cadenas sólo se normalizan.
Todo literal de esos argumentos se trata como lista de clases. Una cadena con espacios que no lo es, como el nombre de una variante (defaultVariants: { size: 'x large' }), también se reordena; en ese caso, usa un nombre sin espacios o // prettier-ignore.
bind:class sigue las mismas reglas sin necesidad de nombrar una función: bind:class="on ? 'text-white flex' : ''" queda bind:class="on ? 'flex text-white' : ''". Depende sólo de meekoSortClasses, igual que el class estático.
Servidor de lenguaje#
El servidor de lenguaje formatea con el plugin del núcleo (@meekojs/meeko/prettier), que desde 1.8 conoce estas opciones y da el mismo resultado que este paquete: prettier --check pasa después de guardar. En un workspace no confiable no ordena nada, porque ordenar puede ejecutar código del proyecto.
Junto a prettier-plugin-tailwindcss#
Pueden convivir en un proyecto, en cualquier orden de plugins. El de Tailwind ordena al analizar y este al imprimir, así que, si ambos ven la misma cadena, gana meekoSortClasses. No repitas una función en tailwindFunctions y meekoClassFunctions: con "tailwind" el orden es el mismo, pero ese plugin también ordena las claves de los objetos y no quita las clases desconocidas repetidas, mientras que este sí las quita.
pnpm -F @meekojs/prettier-plugin test comprueba el resultado esperado, la idempotencia y la compilación de componentes reales.
Ajustes del editor#
El servidor pide la sección meeko.format con workspace/configuration en cada formateo, así que un cambio se aplica sin reiniciarlo. Los clientes sin esa petición pueden enviar format en las opciones de inicialización o { "meeko": { "format": { ... } } } con workspace/didChangeConfiguration.
| Ajuste | Por defecto | Efecto |
|---|---|---|
meeko.format.indentSections |
false |
Indenta un nivel el contenido de cada sección (<script>, <script server>, <template> y <style>), como vueIndentScriptAndStyle de Prettier más la plantilla. Con false, el formato quita esa indentación base. El nivel usa tabSize o un tabulador según insertSpaces. Equivale a la opción meekoIndentSections del plugin; si el proyecto la fija en su configuración de Prettier, prevalece. |
En cada editor, el ajuste va en el objeto de ajustes del servidor como settings.meeko.format.indentSections; los ejemplos de cada sección lo muestran.
Qué lee el servidor#
- El
tsconfig.jsonde la raíz del paquete (la carpeta conpackage.jsonmás cercana) define las opciones de hover, completado y navegación, por ejemplo los aliaspaths.meeko check, y por tanto los diagnósticos del editor, usan las mismas opciones de resolución (paths,baseUrl,types,typeRoots,customConditions,resolveJsonModule,allowImportingTsExtensions) y conservan sus banderas estrictas de tipos. - Un import de un
.meekodel proyecto se resuelve al componente tal como está en el editor, con sus cambios sin guardar, aunque exista su declaración generada.d.meeko.ts(junto a él o en.meeko/types), que con resoluciónNodeNextTypeScript elegiría primero. - Los usos de un símbolo marcado
@deprecateden su JSDoc, como el aliasuseSocketde@meekojs/meeko/framework, se publican como pistas tachadas (DiagnosticTag.Deprecated, códigosTS6385yTS6387) con el motivo de la etiqueta. Los hovers muestran{@link X}como el nombre en código, su texto o un enlace. - Para detectar cambios guardados en archivos
.ts,.css,tsconfig*.jsonymeeko.config.*, el servidor registra observadores conworkspace/didChangeWatchedFilescuando el cliente admite el registro dinámico. Si tu editor no lo admite, vuelve a abrir el componente o reinicia el servidor tras cambiar un módulo importado. Desde la1.4observa también la creación y el borrado de carpetas bajoapp/,server/yshared/, porque al crear, mover o renombrar una carpeta el editor puede notificar sólo la carpeta y no cada.meekode dentro. - El completado del
<script>no ofrece los tipos auxiliares del código virtual (__MeekoEmit,__MeekoDeclaredProps…), que tampoco aparecen en el hover ni en «Ir a definición». De@meekojs/meekosólo sugiere las entradas públicas de suexports(salvointernal): un nombre que exporta una entrada se importa desde ella (import { MeekoModule } from "@meekojs/meeko/framework"), también con el paquete enlazado conlink:, que TypeScript resuelve fuera denode_modulesy por el que sugeriría rutas relativas a sus archivos, y un nombre que no exporta ninguna entrada no se sugiere. Los exports de los paquetes se ordenan después de los nombres locales y de los autoimports del proyecto. Un autoimport del proyecto (un export deapp/utils,shared/types…) se completa sin import, con su archivo como detalle, como en el build: también si su archivo no está en el programa de TypeScript o si un global del DOM se llama igual (navigation). Los imports relativos que escribe el editor (completado, «Add import», autoimport ambiguo) no llevan extensión (./settings/context), salvo quetsconfig.jsonusemoduleResolutionnode16onodenext, que la exige (./settings/context.js).
Comprobación del proyecto#
En un workspace de confianza, y salvo con projectContracts: false, el servidor genera las declaraciones de los componentes y comprueba los contratos de las páginas, los layouts y la página de error (MEEK_PAGE_PROPS, MEEK_LAYOUT_PROPS, MEEK_ERROR_PROPS), como meeko check. Desde la 1.4:
- Cuándo. Se repite, con una espera de 450 ms, al abrir una página o un layout y al guardar, crear, borrar o renombrar cualquier
.meeko, módulo TypeScript, configuración o carpeta de la aplicación (también una carpeta nueva comoapp/components/Layout), y al cerrar sin guardar un componente editado. Los cambios de.meeko/y denode_modulesno la lanzan. - Con qué texto. Los componentes abiertos cuentan con su texto sin guardar, como sus diagnósticos propios; los demás, con el guardado.
- Componentes a medio escribir. Un componente que no compila no detiene la comprobación del resto: conserva su última declaración y su error sale en ese archivo (si está abierto, en sus diagnósticos propios, sin repetirlo). Un
.meekovacío, como el que crea el editor al añadir un archivo, se trata como «en edición» y no produce error en la comprobación. - Fallos. Si la comprobación falla por otra causa,
MEEK_PROJECT_CHECKsale en un solo archivo: el que no compila, el que la lanzó o, si no, un componente abierto del proyecto. Cada comprobación sustituye todos los diagnósticos de la anterior, también ese fallo, así que un error corregido desaparece en la siguiente.
Neovim#
Con Neovim 0.11 o posterior y nvim-lspconfig, declara el tipo de archivo y el servidor en tu init.lua:
vim.filetype.add({ extension = { meeko = "meeko" } })
vim.lsp.config("meeko", {
cmd = { "npx", "meeko-language-server", "--stdio" },
filetypes = { "meeko" },
root_markers = { "package.json", "tsconfig.json" },
init_options = { trusted = true },
settings = { meeko = { format = { indentSections = true } } },
})
vim.lsp.enable("meeko")npx resuelve el binario del proyecto cuando Neovim se abre dentro de él. Para resaltado sintáctico sin una gramática tree-sitter propia puedes reutilizar la de HTML con vim.treesitter.language.register("html", "meeko"); los tokens semánticos del servidor colorean las expresiones TypeScript del script y la plantilla.
Helix#
En languages.toml (global o .helix/languages.toml del proyecto):
[language-server.meeko]
command = "npx"
args = ["meeko-language-server", "--stdio"]
config = { trusted = true }
[[language]]
name = "meeko"
scope = "source.meeko"
file-types = ["meeko"]
roots = ["package.json", "tsconfig.json"]
grammar = "html"
block-comment-tokens = { start = "<!--", end = "-->" }
language-servers = ["meeko"]Helix envía la tabla config como initializationOptions y responde con ella a workspace/configuration. Para los ajustes del editor, añade la tabla meeko en config:
[language-server.meeko]
command = "npx"
args = ["meeko-language-server", "--stdio"]
config = { trusted = true, meeko = { format = { indentSections = true } } }hx --health meeko confirma que encuentra el servidor.
Zed#
Zed sólo asocia servidores de lenguaje a lenguajes que declara una extensión, y todavía no hay una extensión publicada de Meeko. Puedes instalar una extensión local con zed: install dev extension que contenga:
extension.tomlcon[language_servers.meeko-language-server],name = "Meeko Language Server"ylanguages = ["Meeko"], además de la gramáticahtmlen[grammars.html];languages/meeko/config.tomlconname = "Meeko",grammar = "html",path_suffixes = ["meeko"]yblock_comment = ["<!-- ", " -->"], junto con las consultashighlights.scmcopiadas del lenguaje HTML;src/lib.rsque devuelva el comando del servidor:
use zed_extension_api as zed;
struct Meeko;
impl zed::Extension for Meeko {
fn new() -> Self {
Meeko
}
fn language_server_command(&mut self, _id: &zed::LanguageServerId, worktree: &zed::Worktree) -> zed::Result<zed::Command> {
let command = worktree
.which("meeko-language-server")
.ok_or("meeko-language-server is not on PATH; install @meekojs/meeko")?;
Ok(zed::Command { command, args: vec!["--stdio".into()], env: Default::default() })
}
}
zed::register_extension!(Meeko);Las opciones de inicialización se pueden fijar sin recompilar en settings.json:
{
"lsp": {
"meeko-language-server": {
"initialization_options": { "trusted": true },
"settings": { "meeko": { "format": { "indentSections": true } } }
}
}
}Zed sólo responde workspace/configuration con settings si la extensión implementa language_server_workspace_configuration (por ejemplo, devolviendo LspSettings::for_worktree("meeko-language-server", worktree)?.settings). Sin ese método, usa "initialization_options": { "trusted": true, "format": { "indentSections": true } }.
JetBrains (LSP4IJ)#
Los IDE de JetBrains se conectan mediante el plugin LSP4IJ:
- Instala LSP4IJ y abre Settings → Languages & Frameworks → Language Servers; pulsa + para crear un servidor.
- En Server, nombre
Meekoy comandonpx meeko-language-server --stdio(en Windows,npx.cmd meeko-language-server --stdio). - En Mappings → File name patterns, añade
*.meekocon el identificador de lenguajemeeko. - En Configuration → Initialization Options, escribe
{ "trusted": true }, y en Configuration → Server / Configuration los ajustes del editor, por ejemplo{ "meeko": { "format": { "indentSections": true } } }.
Para el resaltado sintáctico, registra la gramática TextMate de la extensión de VS Code en Settings → Editor → TextMate Bundles apuntando a la carpeta packages/vscode del repositorio o a una extensión VSIX descomprimida.
Sublime Text#
Con el paquete LSP y Sublime Text 4, crea primero una sintaxis que herede de HTML en Packages/User/Meeko.sublime-syntax:
%YAML 1.2
---
name: Meeko
scope: text.html.meeko
version: 2
extends: Packages/HTML/HTML.sublime-syntax
file_extensions:
- meeko
contexts: {}Después añade el cliente en Preferences → Package Settings → LSP → Settings:
{
"clients": {
"meeko": {
"enabled": true,
"command": ["npx", "meeko-language-server", "--stdio"],
"selector": "text.html.meeko",
"initializationOptions": { "trusted": true },
"settings": { "meeko.format.indentSections": true }
}
}
}Limitaciones#
- Sólo se tiene en cuenta el
tsconfig.jsonde la raíz del paquete; los tsconfig anidados o referenciados porreferencesno cambian el análisis. - El renombrado y las referencias que abarcan archivos
.meekose inician desde un.meeko. Desde un.tsresponde el servidor TypeScript del editor, que no conoce los componentes. - El modelo semántico necesita secciones sintácticamente cerradas. Mientras se escribe un archivo incompleto los diagnósticos siguen llegando, pero completado y navegación pueden pausarse hasta que vuelva a ser válido.
- Un proyecto con más de 2000 componentes
.meekosólo carga los primeros 2000 para referencias y renombrado entre archivos.
Tailwind CSS#
Instala la extensión oficial Tailwind CSS IntelliSense (bradlc.vscode-tailwindcss) y configura Tailwind en el proyecto. Esta extensión aporta las utilidades, variantes, valores y documentación de Tailwind; Meeko aporta el análisis de componentes y las clases CSS escritas en sus <style> y en app/styles/main.css, incluidos sus imports locales.
Meeko Language registra automáticamente tailwindCSS.includeLanguages: { "meeko": "html" } y activa editor.quickSuggestions.strings para [meeko]. Son valores predeterminados de VS Code: respetan tus ajustes explícitos y no escriben en settings.json. VS Code documenta configurationDefaults para otras configuraciones registradas.
meeko.classes.completion controla el completado y hover de clases de Meeko:
auto(predeterminado): si Tailwind IntelliSense está activo y el proyecto declaratailwindcsscomo dependencia o lo importa desde sus hojas fuente, delega las clases de la hoja procesada.meeko/styles/main.cssa Tailwind. Conserva las clases escritas en componentes y hojas fuente.on: incluye también las clases de la hoja procesada.off: desactiva el completado y hover de clases de Meeko. La navegación a definiciones y las variables CSS siguen disponibles.
Las cadenas de bind:class="active.value ? 'bg-blue-500' : 'bg-gray-500'" reciben sugerencias de clases; no hace falta añadir bind:class a tailwindCSS.classAttributes, porque el atributo class predeterminado reconoce el prefijo :. Meeko conserva además el análisis TypeScript de la expresión.
Para funciones como clsx y cn dentro del <script>, configura las funciones que realmente usa tu proyecto:
{
"tailwindCSS.classFunctions": ["clsx", "cn"]
}La detección de Meeko lee el manifiesto del paquete del componente y las hojas fuente indexadas, sin ejecutar plugins CSS. No clasifica individualmente la hoja procesada: en auto también delega sus clases de terceros que no tienen una definición fuente indexada. En otros clientes LSP, comunica meeko.classes mediante workspace/configuration (o initializationOptions.classes), incluido tailwindActive: true si el cliente tiene un proveedor Tailwind activo.