La mayoría de las veces que trabajamos con HTML nos vemos en la necesidad de repetir secciones en multiples partes de una página e incluso en diferentes páginas. Un ejemplo claro es la sección de footer (pie de página) o menú. Quienes han escrito HTML de manera tradicional en un proyecto de multiples páginas saben que dichas secciones deben ser duplicadas una y otra vez.

El problema es que esta técnica hace que dicha secciones se conviertan en algo poco mantenible a medida que empezamos a incluir más páginas en nuestro sitio. Por ejemplo, si tenemos un sitio web que contiene 4 paginas y en cada una de estas tenemos que incluir nuestro footer, al tener que agregar un nuevo link, debemos visitar cada una de las páginas y actualizar cada uno de nuestros footers.

Para evitar todo este trabajo y centralizar los cambios, hacemos uso de los motores de plantillas de HTML (HTML Templates Engines). Es muy posible que ya los uses sin haberte dado cuenta. Por eso, vamos a revisar las diferentes técnicas y recomendaciones actuales.

Un ejemplo sencillo de la situación de los footers puede ser vista aquí. En el tenemos 2 páginas: index.html y history.html ambas con un footer. Si queremos agregar un nuevo link debemos repetir el proceso por cada página, como lo hablamos con anterioridad.

Document.createElement()

La manera más tradicional de crear templates en HTML es mediante la ayuda de JavaScript. El DOM (Document Object Model) es una API de JavaScript que poseen los navegadores web para manipular el HTML. Mediante la funcionalidad de document.createElement() podemos crear tags HTML. De esta manera, podríamos crear un script que al inicio de la página nos inserte el footer dinámicamente.

Así que, para agregar un nuevo link solo tendríamos que modificar el código de nuestro script y los cambios se reflejarán en todas las páginas en las que éste, este incluido.

Similitudes con las Single Page Apps

Las SPA (Single Page App) usan un principio muy similar. La diferencia es que todas la páginas se construye mediante JavaScript haciendo uso de solo una pagina de HTML. Allí la razón de su nombre, "Aplicaciones de una sola página".

Si te fijas en el siguiente código, el footer del HTML esta vacío. Pues, realmente los creamos mediante JavaScript haciendo uso de document.createElement.

Más información

HTML Content Template

En el ejemplo anterior revisamos la construcción de un footer de manera dinámica mediante el uso document.createElement. El problema es que, al tener aplicaciones más complejas esta técnica se convierte en una pesadilla de mantenimiento. Además,  pueden haber problemas de rendimiento si no se emplea adecuadamente. La buena noticia es que hay una manera más moderna de realizar esta tarea gracias a uno de los nuevos estándares de los Web Components, llamado HTML templates.

El tag de HTML <template> es un elemento especial que nos permite definir una plantilla que posteriormente será usada mediante JavaScript. Lo interesante es que esta plantilla no se pinta de inmediato, generando una ventaja frente a otras alternativas que solo usan JavaScript.

Cómo podemos ver el código anterior es mucho más simple. También si, tuviéramos estructuras más complejas este método es mucho más conveniente.

Más información

JavaScript Template Engines

Finalmente, tenemos los motores de plantillas en JavaScript. Es muy posible que hayas usado uno antes. Algunos ejemplos, son los clásicos motores que se usan de manera independiente como Handlebars o Mustache. También, tenemos algunos que funcionan de manera mixta, es decir, pueden ser usados de manera independiente o dentro de un framework como Underscore.js. (este el motor de plantillas por defecto de Backbone.js). Finalmente, tenemos los más modernos como JSX de React y la sintaxis de plantillas integrada de Angular y Vue.

Todos estos motores tienen en común la necesidad de centralizar plantillas conectadas con datos que posteriormente serán pintadas en el navegador.

Cada uno tiene sus ventajas y desventajas y en general se pueden dividir en  dos grandes categorías:

  1. Logic-less: Su función principal es solo pintar los datos y evitar el uso de lógica en las plantillas o funciones complejas.
  2. Full-logic: Además de pintar datos, nos da la opción de transformarlos, hacer usos de condicionales, ciclos, asignación de variables y funciones complejas.

Hay que resaltar que la mayoría de estos motores están "llevando JavaScript al HTML", pero fue con la llegada de JSX, que se hizo una inversión de control al "llevar el HTML al JavaScript".

Esto cada vez es más común debido a lo poderoso puede ser y lo podemos apreciar por ejemplo en Vue, que oficialmente soporta JSX y frameworks como Flutter o Swift UI que aunque no hacen uso directo de HTML usan el mismo concepto de inversión de control de la interfaz gráfica.

Más información

Virtual DOM

Uno de los procesos más costosos de los motores de plantillas es la inserción de las plantillas en la pagina, debido a que esto bloquea el proceso principal de JavaScript. El Virtual DOM es un concepto en el que se hacen cálculos previos de manera "virtual" antes de hacer la inserción real. Gracias a esta técnica se mejora la experiencia y rendimiento de una manera increíble.

¿Cómo funciona lit-html?

lit-html es un motor de plantillas de HTML simple, moderno, liviano y rápido para JavaScript.

Nos permite escribir plantillas de HTML haciendo uso de la tecnología template literals de JavaScript con la cual podemos escribir HTML de una forma literal. Lo más interesante es que lit-html es capaz de identificar las partes estáticas y dinámicas con el fin de actualizar eficientemente solo las partes que han cambiando. Esto quiere decir, que hace uso de una metodología similar a la del Virtual DOM.

Interpolación nativa en JavaScript

Los templates literals son strings (cadenas de texto) en JavaScript que permiten integrar expresiones, agregar multi-líneas e interpolación, es decir reemplazo de variables en la misma cadena de texto.

Escribiendo templates con lit-html

Si quisiéramos escribir nuestro footer haciendo uso de lit-html basta con hacerlo de una manera muy literal.

// Importamos lit-html (solo esta disponible como modulo)
import {html, render} from 'lit-html';

// Declaramos nuestro template en este caso vamos a incluir todo nuestro footer
const footerTemplate = html`
  <footer>
    <ul>
      <li><a target="_blank" href="index.html">Home</a></li>
      <li><a target="_blank" href="https://www.twitter.com/glrodasz">Twitter</a></li>
      <li><a target="_blank" href="https://www.linkedin.com/in/guillermorodas/">LinkedIn</a></li>
      <li><a target="_blank" href="https://github.com/glrodasz">GitHub</a></li>
    </ul>
  </footer>
`;

// Agregamos nuestro template al cuerpo de nuestro documento
render(footerTemplate, document.body);

Ahora bien, esto nos facilita el hecho de no tener que repetir el código del footer multiples veces, pero, ¿qué tal si usamos la interpolación de los template literals para hacerlo mucho más dinámico?

// Importamos lit-html (solo esta disponible como modulo)
import {html, render} from 'lit-html';

const linkList = [
  {
    url: "index.html",
    text: "Home"
  },
  {
    url: "https://www.twitter.com/glrodasz",
    text: "Twitter"
  },
  {
    url: "https://www.linkedin.com/in/guillermorodas",
    text: "LinkedIn"
  },
  {
    url: "https://github.com/glrodasz",
    text: "GitHub"
  }
];


// Declaramos nuestro template esta vez usando un map
const footerTemplate = html`
  <footer>
    <ul>
      ${linkList.map(({ url, text}) => html`<li><a target="_blank" href=${url}>${text}</a></li>`)}
    </ul>
  </footer>
`;

// Agregamos nuestro template al cuerpo de nuestro documento
render(footerTemplate, document.body);

¡Es sorprendente lo sencillo que podemos escribir esto con lit-html!  Notemos que el valor del atributo href no tiene la necesidad de ser escrito entre comillas como si fuera una cadena de texto. Esto es la manera como lit-html crea un binding con los atributos, es decir, crea un lazo entre el valor dinámico y el atributo del elemento HTML.

lit-html también trae un par de características por defecto, como la posibilidad de adjuntar eventos fácilmente a nuestros elementos HTML.

// Esto es equivalente a hacer addEventListener('click', clickHandler)
const buttonTemplate = () => html`<button @click=${clickHandler}>Click Me!</button>`;

// El manejador puede ser una función tradicional o un objeto de la siguiente forma
const clickHandler = {
  // El método handleEvent es requerido.
  handleEvent(e) { 
    console.log('clicked!');
  },
  // Tambien se puede definir cero o mas opciones como:
  // capture, passive, y once.
  capture: true,
};

Hay muchas características disponibles, pero lo que más debemos tener en cuenta es que cualquiera funcionalidad asociada a JavaScript puede ser usada con lit-html, lo que lo hace muy poderoso e intuitivo a diferencia de otros motores.

Tomando como referencia nuestro ejemplo anterior, la solución con lit-html sería así de sencilla.

Más información

Estilos en los templates con lit-html

lit-html es compatible prácticamente con cualquier sistema de estilos. Desde hojas de estilos tradicionales hasta estilos expresados mediante Shadow DOM. También, nos ofrece un par de directivas para la manipulación dinámica de los atributos class y style.

Más información

Motor de plantillas similares

Finalmente, recuerden que no hay una tecnología absoluta y que siempre debemos analizar cuales son las necesidades de nuestros proyectos para la elección de esta. En perspectiva, lit-html es una alternativa moderna que ofrece gran interoperabilidad.

También es importante tener en cuenta que lit-html es motor de plantillas que usa la librería de Web Componentes LitElement de Google. Así que conocerla nos ayuda a entender mejor el uso de LitElement.

Hay una alternativa muy interesante de JSX que funciona de manera independiente de React llamada htm. Es curioso lo similar que es a lit-html.

// La diferencia aquí es que estamos usando html/preact
import { html } from 'htm/preact';

// Aquí podriamos usar la misma librería de render de lit-html o React DOM
import { render } from 'preact';

// Declaramos nuestro template de la misma manera
const footerTemplate = html`
  <footer>
    <ul>
      <li><a target="_blank" href="index.html">Home</a></li>
      <li><a target="_blank" href="https://www.twitter.com/glrodasz">Twitter</a></li>
      <li><a target="_blank" href="https://www.linkedin.com/in/guillermorodas/">LinkedIn</a></li>
      <li><a target="_blank" href="https://github.com/glrodasz">GitHub</a></li>
    </ul>
  </footer>
`;

// Agregamos nuestro template al cuerpo de nuestro documento
render(footerTemplate, document.body);

Más información