Cómo compilar aplicaciones web con Yeoman y Polymer

Estructura tus apps web con herramientas modernas

Addy Osmani
Addy Osmani

Introducción

Allo' Allo'. Cualquier persona que escriba una app web sabe lo importante que es mantener la productividad. Es un desafío cuando tienes que preocuparte por tareas tediosas, como encontrar el código estándar correcto, configurar un flujo de trabajo de desarrollo y prueba, y reducir y comprimir todas tus fuentes.

Afortunadamente, las herramientas de frontend modernas pueden ayudar a automatizar gran parte de esto, lo que te permite enfocarte en escribir una app increíble. En este artículo, aprenderás a usar Yeoman, un flujo de trabajo de herramientas para apps web que optimiza la creación de apps con Polymer, una biblioteca de polyfills y azúcar para desarrollar apps con componentes web.

Yeoman

Conoce a Yo, Grunt y Bower

Yeoman es un hombre con tres herramientas para mejorar tu productividad:

  • yo es una herramienta que ofrece un ecosistema específico de framework, denominados generadores, que pueden usarse para realizar algunas de las tareas tediosas que mencioné anteriormente.
  • grunt se usa para crear, obtener una vista previa y probar tu proyecto, gracias a la ayuda de las tareas seleccionadas por el equipo de Yeoman y grunt-contrib.
  • bower se utiliza para la administración de dependencias, de modo que ya no tengas que descargar y administrar manualmente las secuencias de comandos.

Con solo un comando o dos, Yeoman puede escribir código estándar para tu app (o piezas individuales como modelos), compilar tu Sass, minimizar y concatenar tu CSS, JS, HTML y imágenes, y activar un servidor web sencillo en tu directorio actual. También puede ejecutar pruebas de unidades y mucho más.

Puedes instalar generadores de Módulos empaquetados de nodos (npm) y ya hay más de 220 generadores disponibles, muchos de los cuales fueron escritos por la comunidad de código abierto. Entre los generadores populares se incluyen generator-angular, generator-backbone y generator-ember.

Página principal de Yeoman

Con una versión reciente de Node.js instalada, dirígete a la terminal más cercana y ejecuta lo siguiente:

$ npm install -g yo

Listo. Ahora tienes Yo, Grunt y Bower, y puedes ejecutarlos directamente desde la línea de comandos. Este es el resultado de la ejecución de yo:

Instalación de Yeoman

Generador de polímeros

Como mencioné antes, Polymer es una biblioteca de polyfills y azúcares que permite el uso de componentes web en navegadores modernos. El proyecto permite a los desarrolladores crear aplicaciones utilizando la plataforma del mañana e informar al W3C sobre lugares donde se pueden mejorar aún más las especificaciones en tránsito.

Página principal sobre el generador de polímeros

generator-polymer es un nuevo generador que te ayuda a organizar apps de Polymer con Yeoman, lo que te permite crear y personalizar fácilmente elementos de Polymer (personalizados) a través de la línea de comandos y, luego, importarlos con HTML Imports. Esto te permite ahorrar tiempo, ya que escribe el código estándar por ti.

A continuación, ejecuta el siguiente comando para instalar el generador de Polymer:

$ npm install generator-polymer -g

Eso es todo. Ahora tu app tiene superpoderes de componentes web.

El generador que acabamos de instalar tiene algunos bits específicos a los que tendrás acceso:

  • polymer:element se usa para el andamiaje de los nuevos elementos individuales de Polymer. Por ejemplo: yo polymer:element carousel.
  • polymer:app se usa para el andamiaje de tu index.html inicial, un Gruntfile.js que contiene la configuración de tiempo de compilación de tu proyecto, así como tareas de Grunt y una estructura de carpetas recomendada para el proyecto. También podrás usar Sass Bootstrap para los estilos de tu proyecto.

Compilamos una aplicación de Polymer

Crearemos un blog sencillo con algunos elementos de Polymer personalizados y nuestro nuevo generador.

App de Polymer

Para comenzar, ve a la terminal, crea un directorio nuevo y ábrelo con mkdir my-new-project && cd $_. Ahora puedes iniciar tu aplicación de Polymer ejecutando lo siguiente:

$ yo polymer
Compilación de apps de Polymer

Esta obtiene la versión más reciente de Polymer de Bower y proporciona un índice.html, una estructura de directorios y tareas de Grunt para tu flujo de trabajo. ¿Por qué no tomar un café mientras esperamos a que la app termine de prepararse?

De acuerdo. A continuación, podemos ejecutar grunt server para obtener una vista previa de la apariencia de la app:

Servidor de Grunt

El servidor es compatible con LiveReload, lo que significa que puedes iniciar un editor de texto y editar un elemento personalizado. El navegador se volverá a cargar cuando se guarde. Esto te proporciona una buena vista en tiempo real del estado actual de tu app.

A continuación, crearemos un nuevo elemento de Polymer para representar una entrada de blog.

$ yo polymer:element post
Crear elemento de publicación

Yeoman nos hace algunas preguntas; por ejemplo, si queremos incluir un constructor o usar una importación de HTML para incluir el elemento de la publicación en index.html. Digamos no a las primeras dos opciones por ahora y dejemos la tercera opción en blanco.

$ yo polymer:element post

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? No

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank)

    create app/elements/post.html

Esto crea un nuevo elemento Polymer en el directorio /elements llamado post.html:

<polymer-element name="post-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>post-element</b>. This is my Shadow DOM.</span>

    </template>

    <script>

    Polymer('post-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Contiene los elementos siguientes:

Trabajar con una fuente de datos real

Nuestro blog necesitará un lugar para escribir y leer entradas nuevas. Para demostrar cómo trabajar con un servicio de datos real, vamos a usar la API de Hojas de cálculo de Google Apps. Esto nos permite leer fácilmente el contenido de cualquier hoja de cálculo creada con Documentos de Google.

Configuremos esto:

  1. En tu navegador (para estos pasos, se recomienda Chrome) abre esta hoja de cálculo de Documentos de Google. Contiene datos de publicaciones de muestra en los siguientes campos:

    • ID
    • Título
    • Autor
    • Contenido
    • Fecha
    • Palabras clave
    • Correo electrónico (del autor)
    • Slug (para la URL de slug de tu publicación)
  2. Ve al menú Archivo y selecciona Crear una copia para crear tu propia copia de la hoja de cálculo. Puedes editar el contenido cuando quieras; puedes agregar o quitar publicaciones.

  3. Vuelve al menú Archivo y selecciona Publicar en la Web.

  4. Haz clic en comenzar a publicar.

  5. En Obtener un vínculo a los datos publicados, del último cuadro de texto, copia la parte de la clave de la URL proporcionada. Se ve de la siguiente manera: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. Pega la clave en la siguiente URL, donde dice your-key-goes-here: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Un ejemplo con la clave anterior podría verse de esta manera: https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script

  7. Puedes pegar la URL en tu navegador y navegar hasta ella para ver la versión JSON del contenido de tu blog. Toma nota de la URL y, luego, revisa el formato de estos datos, ya que tendrás que iterar sobre la URL para mostrarla en la pantalla más adelante.

El resultado de JSON en tu navegador puede parecer un poco abrumador, pero no te preocupes. En realidad, solo nos interesan los datos de tus publicaciones.

La API de Hojas de cálculo de Google genera cada uno de los campos de la hoja de cálculo de tu blog con un prefijo especial post.gsx$. Por ejemplo: post.gsx$title.$t, post.gsx$author.$t, post.gsx$content.$t, etc. Cuando iteramos en cada “fila” de nuestro resultado de JSON, haremos referencia a estos campos para obtener los valores relevantes de cada publicación.

Ahora puedes editar el elemento de publicación que acabas de crear con el andamiaje para bind partes de lenguaje de marcado a los datos de tu hoja de cálculo. Para ello, presentamos un atributo post, que se lee para el título, el autor, el contenido y otros campos de la entrada que creamos anteriormente. El atributo selected (que propagaremos más adelante) se usa para mostrar solo una publicación si un usuario navega al slug correcto para ella.

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2>
                <a href="#[[post.gsx$slug.$t]]">
                [[post.gsx$title.$t  ]]
                </a>
            </h2>

            <p>By [[post.gsx$author.$t]]</p>

            <p>[[post.gsx$content.$t]]</p>

            <p>Published on: [[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

A continuación, ejecutemos yo polymer:element blog para crear un elemento de blog que contenga una colección de entradas y el diseño de tu blog.

$ yo polymer:element blog

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? Yes

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank) post.html

    create app/elements/blog.html

Esta vez importamos el blog a index.html mediante importaciones de HTML como nos gustaría que apareciera en la página. Para la tercera instrucción específica, especificamos post.html como el elemento que queremos incluir.

Como antes, se crea un nuevo archivo de elemento (blog.html) y se agrega a /elements. Esta vez, se importa post.html y se incluye <post-element> en la etiqueta de la plantilla:

<link rel="import" href="post.html">

<polymer-element name="blog-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>blog-element</b>. This is my Shadow DOM.</span>

        <post-element></post-element>

    </template>

    <script>

    Polymer('blog-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Como solicitamos que el elemento de blog se importe a nuestro índice mediante importaciones de HTML (una forma de incluir y reutilizar documentos HTML en otros documentos HTML), también podemos verificar que se haya agregado correctamente al documento <head>:

<!doctype html>
    <head>

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title></title>

        <meta name="description" content="">

        <meta name="viewport" content="width=device-width">

        <link rel="stylesheet" href="styles/main.css">

        <!-- build:js scripts/vendor/modernizr.js -->

        <script src="bower_components/modernizr/modernizr.js"></script>

        <!-- endbuild -->

        <!-- Place your HTML imports here -->

        <link rel="import" href="elements/blog.html">

    </head>

    <body>

        <div class="container">

            <div class="hero-unit" style="width:90%">

                <blog-element></blog-element>

            </div>

        </div>

        <script>
        document.addEventListener('WebComponentsReady', function() {
            // Perform some behaviour
        });
        </script>

        <!-- build:js scripts/vendor.js -->

        <script src="bower_components/polymer/polymer.min.js"></script>

        <!-- endbuild -->

</body>

</html>

Fantástico.

Cómo agregar dependencias con Bower

A continuación, editemos nuestro elemento para usar el elemento de utilidad Polymer JSONP con el objetivo de leer en posts.json. Para obtener el adaptador, puedes clonar el repositorio con Git o ejecutar bower install polymer-elements mediante la instalación de polymer-elements a través de Bower.

Dependencias de Bower

Una vez que tengas la utilidad, deberás incluirla como una importación en tu elemento blog.html con lo siguiente:

<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">

A continuación, incluye la etiqueta correspondiente y proporciona el url a nuestra hoja de cálculo de las entradas de blog anterior y agrega &callback= al final:

<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/your-key-value/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

Con esto implementado, ahora podemos agregar plantillas para iterar en la hoja de cálculo una vez que se ha leído. El primero muestra un índice con un título vinculado para una entrada que señala a la babosa.

<!-- Table of contents -->

<ul>

    <template repeat="[[post in posts.feed.entry]]">

    <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

    </template>

</ul>

La segunda renderiza una instancia de post-element para cada entrada encontrada y pasa el contenido de la entrada según corresponda. Observa que estamos pasando por un atributo post que representa el contenido de la publicación de una sola fila de la hoja de cálculo y un atributo selected que propagaremos con una ruta.

<!-- Post content -->

<template repeat="[[post in posts.feed.entry]]">

    <post-element post="[[post]]" selected="[[route]]"></post-element>

</template>

El atributo repeat que ves que se usa en nuestra plantilla crea y mantiene una instancia con [[bindings ]] para cada elemento de la colección de array de nuestras publicaciones, cuando se proporciona.

App de Polymer

Ahora, para propagar el [[route]] actual, haremos trampa y usaremos una biblioteca llamada Flatiron Director que se vincula a [[route]] cada vez que cambie el hash de la URL.

Afortunadamente, hay un elemento de Polymer (parte del paquete more-elements) que podemos usar. Una vez copiado en el directorio /elements, podemos hacer referencia a él con <flatiron-director route="[[route]]" autoHash></flatiron-director> especificando route como la propiedad a la que queremos vincular y decirle que lea automáticamente el valor de cualquier cambio de hash (autoHash).

Ahora que unimos todo, obtenemos lo siguiente:

    <link rel="import" href="post.html">

    <link rel="import" href="polymer-jsonp/polymer-jsonp.html">

    <link rel="import" href="flatiron-director/flatiron-director.html">

    <polymer-element name="blog-element"  attributes="">

      <template>

        <style>
          @host { :scope {display: block;} }
        </style>

        <div class="row">

          <h1><a href="/#">My Polymer Blog</a></h1>

          <flatiron-director route="[[route]]" autoHash></flatiron-director>

          <h2>Posts</h2>

          <!-- Table of contents -->

          <ul>

            <template repeat="[[post in posts.feed.entry]]">

              <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

            </template>

          </ul>

          <!-- Post content -->

          <template repeat="[[post in posts.feed.entry]]">

            <post-element post="[[post]]" selected="[[route]]"></post-element>

          </template>

        </div>

        <polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdHVQUGd2M2Q0MEZnRms3c3dDQWQ3V1E/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

      </template>

      <script>

        Polymer('blog-element', {

          created: function() {},

          enteredView: function() { },

          leftView: function() { },

          attributeChanged: function(attrName, oldVal, newVal) { }

        });

      </script>

    </polymer-element>
Aplicación Polymer

¡Bravo! Ahora tenemos un blog simple que lee datos de JSON y usa dos elementos de Polymer con el andamiaje de Yeoman.

Cómo trabajar con elementos de terceros

El ecosistema de elementos en torno a los componentes web ha estado creciendo últimamente, y empezaron a aparecer sitios de galería de componentes, como customelements.io. Al revisar los elementos que creó la comunidad, encontré uno para obtener perfiles de gravatar. De hecho, podemos capturarlo y agregarlo a nuestro sitio de blog también.

Página principal de los elementos personalizados

Copia las fuentes del elemento gravatar en tu directorio /elements, inclúyelas a través de importaciones HTML en post.html y, luego, agrega a tu plantilla y pasa el campo de correo electrónico de nuestra hoja de cálculo como la fuente del nombre de usuario. ¡Bum!

<link rel="import" href="gravatar-element/src/gravatar.html">

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></h2>

            <p>By [[post.gsx$author.$t]]</p>

            <gravatar-element username="[[post.gsx$email.$t]]" size="100"></gravatar-element>

            <p>[[post.gsx$content.$t]]</p>

            <p>[[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Veamos lo que nos da esto:

App de Polymer con elementos personalizados

¡Hermosa!

En poco tiempo, creamos una aplicación simple compuesta por varios componentes web sin tener que preocuparnos de escribir código estándar, descargar dependencias manualmente ni configurar un servidor local o un flujo de trabajo de compilación.

Optimiza tu aplicación

El flujo de trabajo de Yeoman incluye otro proyecto de código abierto llamado Grunt, un ejecutor de tareas que puede ejecutar varias tareas específicas de compilación (definidas en un archivo Gruntfile) para producir una versión optimizada de tu aplicación. Si ejecutas grunt por tu cuenta, se ejecutará una tarea default que el generador configuró para analizar con lint, realizar pruebas y compilar:

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

La tarea jshint anterior verificará con tu archivo .jshintrc para conocer tus preferencias y, luego, la ejecutará en todos los archivos JavaScript de tu proyecto. Para obtener un resumen completo de tus opciones con JSHint, consulta los documentos.

La tarea test se ve de la siguiente manera y puede crear y entregar tu app para el framework de prueba que recomendamos de inmediato, Mocha. También se ejecutarán las pruebas por ti:

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

Como nuestra app en este caso es bastante simple, te dejaremos la escritura de las pruebas como un ejercicio independiente. Necesitaremos algunas otras tareas para tener nuestro controlador de proceso de compilación, así que veamos qué hará la tarea grunt build definida en nuestro Gruntfile.js:

grunt.registerTask('build', [

    'clean:dist',    // Clears out your .tmp/ and dist/ folders

    'compass:dist',  // Compiles your Sassiness

    'useminPrepare', // Looks for <!-- special blocks --> in your HTML

    'imagemin',      // Optimizes your images!

    'htmlmin',       // Minifies your HTML files

    'concat',        // Task used to concatenate your JS and CSS

    'cssmin',        // Minifies your CSS files

    'uglify',        // Task used to minify your JS

    'copy',          // Copies files from .tmp/ and app/ into dist/

    'usemin'         // Updates the references in your HTML with the new files

]);

Ejecuta grunt build. Se debería compilar una versión de tu app lista para la producción, lista para el envío. Vamos a probarlo.

Compilación de Grunt

¡Listo!

Si no puedes avanzar, puedes consultar una versión precompilada de polymer-blog en https://github.com/addyosmani/polymer-blog.

¿Qué más tenemos disponible?

Los componentes web aún se encuentran en un estado de evolución y, por lo tanto, lo mismo sucede con las herramientas que los utilizan.

Estamos viendo cómo se puede proceder a concatenar sus importaciones HTML para mejorar el rendimiento de carga a través de proyectos como Vulcanize (una herramienta del proyecto Polymer) y cómo podría funcionar el ecosistema de componentes con un administrador de paquetes como Bower.

Te informaremos a medida que tengamos mejores respuestas a estas preguntas, pero se aproximan muchos momentos emocionantes.

Cómo instalar Polymer independiente con Bower

Si prefieres un inicio más liviano a Polymer, puedes instalarlo de forma independiente directamente desde Bower. Para ello, ejecuta el siguiente comando:

bower install polymer

que lo agregará a tu directorio bower_components. Luego, puedes consultarlo en el índice de la aplicación de forma manual y mejorar el futuro.

¿Qué piensa?

Ahora sabes cómo crear el andamiaje de una app de Polymer mediante componentes web con Yeoman. Si tienes comentarios sobre el generador, escríbenos en los comentarios, informa un error o publica una publicación en la herramienta de seguimiento de errores de Yeoman. Nos encantaría saber si hay algo más que te gustaría que mejore el generador, ya que solo a través de tu uso y de tus comentarios podemos mejorar :)