DÍA 6 / 2014

Introducción a Web Components: ¡El HTML ha muerto, larga vida al HTML!

¿Conoces los Web Components? ¿No? Pues este artículo te ayudará a introducirte en este nuevo estandar HTML, que cambiará el diseño web por completo. ¡No seas el último en enterarte!


Si el título de este artículo te parece exagerado, te recomiendo que busques “web components” en Google y leas un par de posts al azar. Apuesto a que en varios de ellos aparecen expresiones del tipo “revolución”, “un antes y un después” o similares. Y no es para menos. Intentaré explicar esta "revolución" de forma breve... ya que si intentase explicarlo de forma exhaustiva tardarías horas en leerlo, ¡el tema da para mucho!

Los Web Components son una reciente incorporación al HTML5 que, si siguen evolucionando al ritmo al que lo están haciendo, pueden suponer el mayor cambio en el mundo web en años y solucionar de golpe varios problemas históricos de HTML.

El estándar, en realidad, se compone de 4 subelementos complementarios, pero independientes entre si:

Cualquiera de ellos puede ser usado por separado, lo que hace que la tecnología de los Web Elements sea, además de muy útil, muy flexible.

Ahora te pregunto: ¿has visto alguna vez en acción un Web Component? Lo más seguro es que sí, aunque quizá no fueras consciente de ello...

Visita cualquier página con un reproductor de vídeo HTML5 (como esta) e inspecciona la etiqueta <video> con las Herramientas para Desarrolladores de Chrome. En las opciones (la rueda dentada), activa "Show user agent shadow DOM" y, ¡sorpresa!, verás cómo aparecen multitud de nuevos elementos que estaban ocultos inicialmente (en el Shadow DOM).

Básicamente, los Web Components permiten a los desarrolladores usar las mismas herramientas que usan los responsables de los navegadores para implementar nuevos elementos.

Custom Elements: Etiquetas personalizadas

Ahora cualquiera puede crear su propia etiqueta y marcar de forma más semántica y sencilla lo que de otro modo sería, muy probablemente, una sopa de divs. Te pondré el ejemplo que he visto más veces: un mapa de Google.

Si vas al final del tutorial de Google Maps para desarrolladores, verás que para poner un mapa en una web te dice que tienes que poner todo este tocho:

<!DOCTYPE html>
<html>
  <head>
    <style>
      #map_canvas {
        width: 500px;
        height: 400px;
      }
    </style>
    <script src="https://maps.googleapis.com/maps/api/js"></script>
    <script>
      function initialize() {
        var mapCanvas = document.getElementById('map_canvas');
        var mapOptions = {
          center: new google.maps.LatLng(44.5403, -78.5463),
          zoom: 8,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        }
        var map = new google.maps.Map(mapCanvas, mapOptions)
      }
      google.maps.event.addDomListener(window, 'load', initialize);
    </script>
  </head>
  <body>
    <div id="map_canvas"></div>
  </body>
</html>

Con Web Components, ese tocho puede convertirse en simplemente esto:

<google-map latitude="37.77493" longitude="-122.41942"></google-map>

Cambia bastante el asunto, ¿no?.

Con los custom elements, la comunidad puede crear sus propios elementos, de forma rápida y colaborativa. ¿Has echado en falta alguna vez mayor velocidad por parte del W3C para introducir X etiqueta en el estándar? ¡Problema resuelto, la puedes crear tú mismo!

Ahora será la comunidad la que creará y mejorará nuevos elementos, listos para ser añadidos al estándar si ganan popularidad. Deberías pasarte por customelements.io y ver toda la variedad que ya hay disponible. Desde el citado google-map hasta un creador de memes.

Los custom elements pueden extender al genérico HTMLElement o a otros elementos ya existentes. Puedes, por ejemplo, crear un botón para que te sigan en Twitter. ¿Para qué preocuparte de recrear toda la funcionalidad de un botón por defecto, si puedes extenderlo y simplemente añadir la tuya? Objetos "padres" con funcionalidad que pueden ser extendidos... ¿no te suena de algo? Programación Orientada a Objetos, bienvenida a HTML.

El único requisito para crear un custom element es que su nombre contenga un guión (ejemplo: <super-boton>) para evitar conflictos con actuales (o futuras) etiquetas nativas de HTML (todas de una sola palabra). Para mantener la brevedad de este artículo, te recomiendo visitar los proyectos Polymer (impulsado por Google) y X-Tag (Mozilla), que contienen instrucciones y ayuda para crear tus propios elementos.

Si ves la documentación de la etiqueta google-map verás que admite multitud de parámetros, e incluso otros custom elements en su interior. ¿Cómo lo hace? Para eso necesitamos explicar los siguientes sub-componentes del estándar.

Templates

Seguro que alguna vez te has encontrado en una situación como esta: trabajas con PHP y quieres hacer una consulta a la base de datos mediante AJAX de, por ejemplo, los protagonistas de IT Crowd. Quieres que el resultado final sea algo así:

<dl>
    <dt>Chris O'Dowd</dt><dd>Roy Trenneman</dd>
    <dt>Richard Ayoade</dt><dd>Maurice Moss</dd>
    <dt>Katherine Parkinson</dt><dd>Jen Barber</dd>
</dl>

Entonces te surge la siguiente duda: ¿devuelvo el fragmento entero o simplemente los datos? El primer caso es mucho más cómodo: puedes construir el HTML igual que el resto de tu web (típicamente, iterando sobre una colección de objetos), pero es más ineficiente (al incluir el HTML en la consulta, esta tarda más). El problema de solo devolver el resultado (y nada más) es que construir el HTML con JavaScript es un engorro.

Para solucionar problemas como este, aparecieron sistemas de plantillas con JavaScript pero, ¿no sería mejor construir las plantillas de HTML... con HTML? ¡Pues sí, y por eso ya se puede hacer!

El nuevo elemento <template> nos permite definir un código HTML preparado para ser usado (y reusado) cuando sea necesario pero que no se activa hasta ese momento, para el navegador no existe. Esto último es muy importante, ya que implica que las imágenes, scripts y demás recursos externos que se incluyan en ese template no se descargaran hasta que se necesiten. Si lo piensas, simplemente con añadir de forma inteligente unas media queries estamos solucionando el problema de las imágenes responsive.

Usando <template> podrías crear una plantilla de HTML vacía lista para ser rellenada después con JavaScript:

<template id="itTemplate">
    <dl>
        <template>
               <dt></dt><dd></dd>            
        </template>
    </dl>
</template>
<script>
actores = [
          {'nombre' : 'Chris O\'Dowd', 'personaje' : 'Roy Trenneman'},
          {'nombre' : 'Richard Ayoade', 'personaje' : 'Maurice Moss'},
          {'nombre' : 'Katherine Parkinson', 'personaje' : 'Jen Barber'}
      ];

  var t = document.querySelector('#itTemplate');
  var t2 = t.content.querySelector('template');
    var dt = t2.content.querySelector('dt');
    var dd = t2.content.querySelector('dd');
    var dl = t.content.querySelector('dl');
    actores.forEach(function(actor) {
        dtClone = dt.cloneNode(true);
        ddClone = dd.cloneNode(true);
        dtClone.textContent = actor.nombre;
        ddClone.textContent = actor.personaje;
        dl.appendChild(dtClone);
        dl.appendChild(ddClone);
    });

  var clone = document.importNode(t.content, true);

  document.body.appendChild(clone);

</script>

Es mejor que estar lidiando con mil cadenas, pero aún podría ser más cómodo... y lo es si usas las comodidades que añade Polymer:

<template>
  <dl>
    <template repeat="{{actor in actores}}">
      <dt>{{actor.nombre}}</dt><dd>{{actor.personaje}}</dd>
    </template>
  </dl>
</template>

Seguro que si has usado algún motor de plantillas, ya sea en PHP o en JavaScript, te suena.

Como puedes ver, en los Web Components se aplican varias buenas prácticas habituales en otros lenguajes. Y aún hay más...

Shadow DOM: El DOM Ninja

Al hablar de las etiquetas personalizadas afirmaba que la Programación Orientada a Objetos llegaba a HTML, y no sólo lo hacía por la posibilidad de extender elementos sino porque ahora también es posible la encapsulación: el código que creemos dentro de nuestro Web Component puede ser completamente ajeno al resto del documento web (y viceversa), si así lo queremos, simplemente usando el Shadow DOM. Se acabó el CSS que afecta a donde no debe porque un selector afecta a elementos del DOM imprevistos, se acabó el JavaScript que se ejecuta donde no debe... se acabó el usar iframes para proteger un widget del exterior. Centrémonos en el CSS, que para algo es la temática del blog.

La API de Shadow DOM nos permite crear un shadow root, que se podría definir como una barrera de contención del DOM, tanto desde dentro del web component hacia el DOM del documento principal como en la dirección contraria. Solo pasa lo que nosotros queramos que pase. Por defecto, nada entra ni sale de esa barrera (el shadow boundary).

El problema

Supongamos que sigues alguna corriente de CSS tipo OOCSS o similares, por lo que empleas preferentemente selectores de clase, sin prácticamente selectores descendentes:

.boton {
   (estilos)
}
.aviso {
   (estilos)
}

Creas toda tu web siguiendo ese patrón, con felices resultados. Sin embargo, en un momento dado necesitas insertar un carrusel de fotos, un lightbox o algún módulo externo parecido. Dicho módulo viene con su CSS ya listo, que incluyes en tu web... y se acaba la felicidad. Resulta que algunas clases del módulo se llaman igual a otras usadas por ti, y el creador de la librería no sigue OOCSS ni nada que se le parezca:

.boton {
   (estilos)
}
div .aviso {
   (estilos)
}

Tienes la mala suerte de que tus .aviso también suelen estar dentro de <div> por lo que su selector gana al tuyo y los estilos de la librería sobreescriben los tuyos. Tienes que dejar de lado tu bonita filosofía OOCSS y entrar en una guerra de especificidad de selectores o, peor aún, usar !important para que cada cosa se vea como deba.

Posteriormente, insertas un widget de una red social, basado en iframes. Los desarrolladores de dicha red social usaron esa etiqueta precisamente para evitar conflictos de selectores. Sin embargo, el color de los botones combina horriblemente mal con el resto de tu web. Intentas cambiarlo pero te es imposible, ya que no se pueden modificar desde el exterior.

La solución

En el primer caso, podrías combinar una hoja de estilos con la otra, con la tranquilidad de saber que no habrá conflictos entre ellas, al estar limitadas por el shadow boundary:

<style>h3{ color: blue; }</style>
<div>
    <h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<style>h3{ color: red; }</style>' +
                 '<h3>Shadow DOM</h3>';
</script>

En los navegadores compatibles, el H3 del light DOM (en oposición al shadow DOM) es azul y el del shadow DOM es rojo.

¿Y qué pasa si quieres modificar el estilo del botón del widget de la red social? Con los nuevos selectores ::shadow y /deep/ puedes "romper" la barrera creada por el Shadow DOM y cambiar los estilos de dentro del widget. La encapsulación de los iframes sin los inconvenientes que generaban:

fb-widget::shadow .button {
background-color: #FABADA;
}

La diferencia entre ::shadow y /deep/ es que el primero te permite modificar los elementos que están en el shadow DOM inmediato, mientras que el segundo afecta a cualquiera, sin importar cuántos niveles haya:

<style>
    etiqueta-personalizada::shadow .button  {
        color: red;
    }
    etiqueta-personalizada /deep/ .button  {
        background-color: #FABADA;
    }
</style>
<etiqueta-personalizada>
    <div id="primerNivel" class="button"></div>
    <otra-etiqueta>
        <div id="segundoNivel" class="button"></div>
    </otra-etiqueta>
</etiqueta-personalizada>

La primera regla sólo afectaría a #primerNivel , mientras que la segunda afectaría tanto a #primerNivel como a #segundoNivel .

Rob Dodson tiene una cheetsheet con los nuevos selectores CSS de Shadow DOM, que te recomiendo visitar.

HTML Imports

Hasta ahora, se podían cargar desde otras URL scripts, imágenes, hojas de estilos... pero no HTML (sí, está el shtml, pero todos sabemos que no es la cosa más cómoda de usar, que digamos...). Ahora podemos crear nuestros web components de forma modular, y cargarlos fácilmente con esta etiqueta en el <head>:

<link rel="import" href="/ruta/al/import.html">

El navegador obtiene el contenido del archivo y ya lo tienes listo para usar. Ojo, no es un import del tipo del SHTML... no intentes esto:

<body>
    <div class="header">
        <link rel="import" href="/partials/header.html">
    </div>
</body>

Imagínatelo más bien como una consulta AJAX, que luego tienes que insertar en la web mediante JavaScript.

Tienes un artículo sobre cómo funcionan los imports en HTML5 Rocks. De todos modos, te lo resumo: olvídate de usar un slider y tener que cargar la hoja de estilos por una parte, el javascript con sus dependencias por otra, y luego poner justo el HTML adecuado y rezar porque no haya conflictos. Con Web Components, simplemente cargas el módulo y usas la etiqueta (<image-gallery>, por poner un ejemplo) cuando te apetezca.

Conclusión

En este artículo he buscado explicar, de forma general, en qué consisten los Web Components y por qué, pese a que aún están en una fase muy temprana de su evolución, pueden suponer una auténtica revolución en el mundo del desarrollo web.

Volviendo al ejemplo del mapa de google, ¿no te recuerda esa etiqueta a un shortcode de WordPress?

<google-map latitude="37.77493" longitude="-122.41942"></google-map>
[google-map latitude="37.77493" longitude="-122.41942"]

Imagínate que tus shortcodes de WordPress fueran válidos, de repente, para Drupal, Joomla, Symfony, Laravel, HTML a pelo... Simplemente creando un web component podrías usar tu "shortcode" (ahora una etiqueta HTML propia) en cualquier aplicación que acepte HTML.

Si esto fuera una presentación en California, ahora te preguntaría "how cool is that?".

Muy probablemente, muchas ideas aquí expuestas ya te sonaban de otras tecnologías. Ese es, a mi juicio, uno de los grandes aciertos de los Web Components: integrar en una misma API un montón de buenas prácticas y metodologías ya establecidas, devolviendo al HTML una importancia que en cierto modo había estado perdiendo en favor de (principalmente) JavaScript.

Si visitas la web de angular.js verás que se define como "HTML mejorado para aplicaciones web". Parece como si los responsables de los Web Components (muchos de ellos de Google, al igual que los de Angular) se hubieran preguntado: ¿por qué vamos a mejorar HTML desde JavaScript si podemos mejorarlo internamente?

Sé que Angular es mucho más que eso, pero la idea de fondo es muy similar, y ese es el punto que quiero destacar.

"Todo esto está muy bien, pero si no hay un buen soporte de navegadores, es ciencia ficción...", te oigo decir. Más bien, "¿qué hay de Internet Explorer?". El soporte nativo es bajo, pero tanto X-Tag como Polymer implementan polyfills para mejorarlo. En el primer caso, se puede usar con IE9+, en el segundo con IE10+.

Espero que este artículo haya hecho crecer tu curiosidad sobre esta novedosa tecnología. Si quieres saber más, te recomiendo algunos enlaces: