Motor de Plantillas HTML6
Un motor de plantillas del lado del servidor, ligero y basado en componentes, para construir HTML rápido y dinámico a partir de JavaScript puro. Es el motor que renderiza este mismo sitio.
Duración
Estable
Equipo
2 desarrolladores
Rol
Co-Desarrollador y Mantenedor
Descripción General
HTML6 es un motor de plantillas del lado del servidor que compila HTML plano en funciones de renderizado rápidas y reutilizables. La motivación era que alguien que solo sabe HTML y CSS pudiera escribir condicionales, loops y componentes sin tocar JavaScript ni un framework como React.
El Desafío
El objetivo era que HTML6 fuera el motor de plantillas más rápido que hubiera, no solo uno que funcionara bien. El problema: tags como if/elsif/else necesitan ver a sus hermanos para resolverse correctamente, y la forma obvia de manejar eso implica recorrer el árbol parseado más de una vez, justo el tipo de costo que una promesa de ser el más rápido no se puede permitir.
La Solución
HTML6 compila recorriendo el árbol parseado exactamente una vez, de abajo hacia arriba, despachando cada nodo recién cuando sus hijos ya terminaron. La técnica que lo hace posible es masking y unmasking (enmascarado y desenmascarado).
Los tags que necesitan contexto de sus hermanos, como una cadena if/elsif/else o un loop map, se enmascaran en vez de resolverse en el lugar: se reemplazan por una clave placeholder en una tabla de lookup para que el recorrido nunca tenga que volver atrás. Una vez que termina ese único recorrido, el desenmascarado reemplaza cada placeholder de vuelta por su contenido real, y el resultado se envuelve en una función de render de JavaScript nativo.
Es simplemente .html
Cada plantilla de HTML6 es un archivo .html real. No .pug, no .hbs, no un archivo .jsx disfrazado de markup. Es HTML con algunos atributos extra y {{ }} de vez en cuando, así que tu editor, Prettier y cualquier linter que ya tengas lo entienden sin nada más.
Ese es el diferenciador real, más que cualquier feature puntual de las de abajo. No hay plugin que instalar, ni una extensión nueva que tu tooling tenga que aprender, ni un build step parado entre el archivo que escribís y el archivo que el navegador ya reconocería como HTML. Un diseñador UI/UX sin experiencia en JavaScript puede abrir el archivo y seguir exactamente qué está haciendo.
JavaScript real, no un mini-lenguaje
Las interpolaciones y los condicionales ejecutan expresiones de JavaScript reales contra el scope de renderizado, no un lenguaje de plantillas simplificado que solo se le parece:
<h1>{{title}}</h1>
<div if="user.loggedIn">Bienvenido de nuevo</div>
<div elsif="user.pending">Pendiente de aprobación</div>
<div else>Por favor iniciá sesión</div>
Si es JavaScript válido, es válido dentro de {{ }} o de un if. No hay un mini-lenguaje que tener que consultar.
Loops con map
map recorre arrays directamente sobre el tag, con un índice opcional y su propio if:
<ul>
<li map="p, i of projects" if="p.title.length > 0">{{i}}: {{p.title}}</li>
</ul>
Los loops anidados funcionan igual, un atributo map por nivel:
<div map="group of groups">
<h2>{{group.name}}</h2>
<ul>
<li map="item of group.items">{{item}}</li>
</ul>
</div>
Pipes para transformaciones seguras
Las llamadas a funciones dentro de {{ }} están deshabilitadas por seguridad. |> encadena transformaciones sobre un valor en su lugar:
{{title |> upper |> truncate 20}}
{{price |> formatCurrency}}
{{date |> formatDate 'YYYY-MM-DD'}}
Los pipes son simplemente funciones registradas por nombre, así que agregar uno nuevo son unas pocas líneas de JavaScript, no un cambio al lenguaje de plantillas en sí:
var pipes = {
upper: (x) => String(x).toUpperCase(),
truncate: (x, len) => String(x).slice(0, len)
}
html6.compile(template, { pipes })
Componentes, slots y props aislados
<template is="..."> define un componente. <slot> marca dónde se insertan sus hijos. Los props quedan aislados a ese componente, mientras que todo lo demás del scope externo fluye hacia abajo automáticamente, sin tener que redeclararse en cada nivel:
<template is="card" title="string">
<div class="card">
<h2>{{props.title}}</h2>
<slot></slot>
</div>
</template>
<card title="Hola {{user.name}}">
<p>Renderizado desde el slot.</p>
</card>
Se compila una vez, corre en cada request
compile() toma la plantilla y devuelve una función de render. Esa es la parte costosa, así que solo corre una vez, al arrancar, y el resultado se guarda en caché. Cada request después de eso simplemente llama a esa función cacheada con datos frescos.
Dónde corre realmente
HTML6 se lanzó por primera vez en producción en Nobo, el lugar de trabajo donde todo esto empezó. También impulsa Ultimate Learning, una app de aprendizaje de idiomas que mantengo activamente. Dos sitios reales en producción, no una demo que se construyó una vez y se abandonó.
¿Te interesa este proyecto?
Me encantaría discutir los detalles técnicos, desafíos enfrentados y lecciones aprendidas al construir este proyecto.
Contactame