JavaScriptNode.jsMotor de PlantillasSSRCódigo Abierto

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

Motor de Plantillas HTML6

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.

Características Clave

  • ✓ Archivos .html reales, sin build step
  • ✓ Condiciones y expresiones son JavaScript real
  • ✓ Componentes con slots y props aislados
  • ✓ Pipes encadenables: {{price |> formatCurrency}}
  • ✓ Compila una vez, renderiza cada request desde esa función
  • ✓ Si falta una variable, falla ruidoso, nunca en silencio

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.

se compila una vez, se renderiza muchas La plantilla compila una vez, al arrancar
Compilar depende solo de la plantilla, así que pasa una vez y se cachea. Renderizar depende solo de los datos, así que cada request simplemente vuelve a llamar a esa función cacheada. Un motor ya rápido, hecho todavía más rápido: la compilación nunca corre dos veces.

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