Sep 5, 2015

Symfony Front-end Workflow: Assetic, Bower & Grunt

Después de desarrollar un par de proyectos con Symfony he encontrado un flujo de trabajo que funciona bastante bien para implementar diferentes librerías front-end en dicho framework PHP

Symfony Front-end Workflow: Assetic, Bower, Grunt

La idea consiste en utilizar Bower para gestionar las dependencias front-end del proyecto. Grunt para seleccionar y copiar dichas dependencias a nuestro bundle principal. Y finalmente dejamos que Assetic realice el resto de tareas: aplicar los filtros (sass, rewrite, uglify, etc.) y gestionar el flujo dentro de Symfony.

Implementar todo esto por primera vez requiere una importante carga de configuración, sobre todo si eres completamente nuevo en el mundo Symfony (como era mi caso hasta hace 6 meses), pero una vez le pillas el truco todo va como la seda.

Lo mejor de usar conjuntamente Bower y Grunt, es que toda esta configuración la podemos copiar a otro proyecto y en 5 minutos ya tendremos todos los assets bajados y copiados a sus rutas correspondientes.

¿Tienes alguna duda o pregunta?. Me encantaría saber como trabajaís vosotros en proyectos similares.


Bower

Bower es una herramienta open source liberada por Twitter que sirve para gestionar todas las dependencias de tu proyecto front-end. La idea es similar a Composer en PHP o Bundler para Ruby.

El funcionamiento de Bower es bastante simple. Primero creas la configuración necesaria en un archivo bower.json. Esto lo puedes hacer lanzando el comando bower init en tu consola. El atributo más importante es dependencies, el cual será un objeto (key: value) con el nombre y la versión de la dependencia.

Para añadir una nueva librería lo primero que debes hacer es buscarla en bower.io. Esto también lo puedes hacer por consola lanzando el siguiente comando:

$ bower search NOMBRE_DE_LA_LIBRERIA

Si la librería se encuentra registrada como package en Bower entonces la podrás añadir directamente por consola con el flag --save

$ bower install --save jquery

Esto además de bajar la librería, modifica automáticamente el fichero bower.json añadiendola como dependencia y también especificará la versión. Por ejemplo la versión ~2.1.1. El simbolo ~ significa que las siguientes veces que solicites a Bower actualizar las dependencias del proyecto, comenzará siempre con la versión 2.1.1 hasta un máximo de 2.2.0. Puedes encontrar más información sobre este tema en Bower Version Syntax.

Si la librería que necesitas no se encuentra registrada como package en Bower, entonces puedes modificar el bower.json a mano, añadiendo la url del repositorio (público o privado). Es buena práctica especificar la versión que deseas utilizar.

"bootstrap-datepicker": "https://github.com/eternicode/bootstrap-datepicker.git#~1.4.0"

Una vez finalizado este proceso basta con lanzar una línea en la terminal para tener todas las dependencias disponibles en local. Da igual si son 5 o 50, Bower las bajará todas por ti en un pis pas.

$ bower install

Los ficheros se guardarán en una carpeta llamada bower components. Dependiendo del autor de la librería encontrarás muchas carperas y ficheros que no serán necesarios para tu proyecto (por ejemplo: test, build, changelogs, licencias, readme, etc.). Pero no hay problema, esto lo resolveremos con un plugin para Grunt llamado bower copy.

Así es como quedaría un archivo bower.json completamente configurado:


Grunt

Grunt creado y mantenido por la gente de Bocoup, es una herramienta para automatizar tareas en tu entorno de desarrollo.

Para configurar Grunt son necesarios dos ficheros: package.json y Gruntfile.js.

En package.json deberás listar todas las dependencias necesarias para correr las tareas. En nuestro caso solo necesitamos bower copy, ya que el resto de tareas que normalmente realizaríamos con Grunt, en el entorno de Symfony será Assetic quien se encargará de ellas.

Como he comentado anteriormente dependiendo del autor de la librería encontrarás muchas carperas y ficheros que no serán necesarios para tu proyecto. Hago uso de bower copy para copiar únicamente los ficheros que necesita mi proyecto.

Para instalar las dependencias de Grunt deberás escribir en tu consola:

$ npm install

En Gruntfile.js creas la configuración de cada tarea o grupo de tareas. En nuestro caso solo tendremos una única tarea llamada ‘default’.

En este caso, todos se copiaran a la ruta /src/AppBundle/Resources/public. He dividido los assets en varios grupos: CSS, JS, sass, fonts e imágenes. Dependiendo del grupo al cual pertenezcan, cada asset se copiará a una carpeta distinta. Incluso podemos cambiar el nombre al fichero antes de llevarlo a su destino final.

Para lanzar esta tarea simplemente escribes en tu consola:

$ grunt

Si existieran más tareas, deberíamos especificar el nombre, pero en este caso no es necesario.

Así es como quedaría un archivo Gruntfile.js completamente configurado:


Git Ignore

Un punto importante: las carpetas bower components y node modules deberás añadirlas a .gitignore. Ya que no hace falta subir todas las dependencias de desarrollo a tu repositorio, siempre que tengas a mano los ficheros de configuración de Bower y Grunt podrás realizar la misma tarea todas las veces que quieras.

Estos datos los he sacado de gitignore.io, una herramienta muy útil para saber que carpetas debes ignorar según qué tecnología utilizas.


Assetic

Un asset es cualquier componente de nuestro front-end. Por ejemplo un fichero CSS o Javascript, una imagen o una webfont.

Assetic es el gestor de assets que utiliza Symfony. Assetic está basado en webassets, una librería del mundo Python.

Assetic trabaja conjuntamente con el componente Asset para gestionar todo el flujo de nuestros assets dentro de los entornos de desarrollo y producción de Symfony. Gracias a Assetic podremos aplicar filtros a nuestros assets antes de dejarlos disponibles para el usuario final. Por ejemplo una tarea de un filtro sería compilar un fichero sass o comprimir y combinar ficheros js.

La configuración de Assetic la haremos siempre en un fichero twig.

CSS

Comencemos con los hojas de estilo de nuestra aplicación. Trabajaremos con un fichero llamado css.html.twig que se encuentra alojado en /app/Resources/views/includes/

Como veréis primero aplicamos el filtro sass a nuestro fichero main.scss. Incluso podemos decidir el nombre que tendrá el fichero CSS final, en este caso main.css. Para hacer uso de este filtro es necesario tener instalado Ruby y Sass en nuestro entorno. Mi recomendación es instalar Ruby vía RVM para evitar problemas con las versiones de las gemas.

A continuación aplicamos a un grupo de ficheros CSS (plugins) un par de filtros. El filtro cssrewrite se encarga de sobreescribir las rutas relativas que se encuentren en nuestros estilos. El filtro uglyfycss comprime y concatena nuestros ficheros CSS. Este último filtro necesita Node y el package UglifyCSS para funcionar.

Si añadimos a cualquier filtro el signo de interrogación ? significa que solo debe ejecutar dicha tarea en el entorno de producción. La razón es que no nos interesa trabajar con archivos comprimidos en el entorno de desarrollo, básicamente porque sería un infierno debugear.

El lector avispado se preguntará por qué no he aplicado el filtro uglifycss a los ficheros sass. La respuesta es sencilla: no hace falta. Sass ya se encarga de comprimir y concatenar los ficheros que compila. Solo es necesario especificar este comportamiento en la configuración del entorno de producción de Symfony.

Javascript

Ahora es el turno de los ficheros Javascript. Trabajaremos con un fichero llamado js.html.twig que se encuentra alojado en /app/Resources/views/includes/

En este caso aplicamos un único filtro llamado uglyfyjs, cuya tarea es comprimir y concatenar nuestros ficheros Javascript. Este filtro necesita Node y el package UglifyJS para funcionar. También aplicamos la opción de realizar esta tarea únicamente en el entorno de producción.

Entornos de Symfony

Como he mencionado anteriormente, Symfony tiene dos entornos de ejecución diferenciados: desarrollo y producción. Dichos entornos de ejecución afectan directamente a la forma de trabajar con nuestros assets. Necesitaremos conocer algunos comandos extras.

El primero es assets install web que buscará dentro de todos nuestros bundles la ruta /Resources/public/. Si encuentra algún fichero los copiará a la ruta pública /web/bunles/[nombre del bundle].

$ php app/console assets:install web

El segundo es assetic dump. Este comando aplicará los filtros a los assets correspondientes y a continuación los copiará a la ruta /web. Dependiendo del entorno en el cual quieras realizar el dump, deberás utilizar el flag --env.

$ php app/console assetic:dump --env=dev
$ php app/console assetic:dump --env=prod

Nosotros tenemos deshabilitado por defecto que Symfony realice esta tarea por nosotros. Esto agiliza la carga en el entorno de desarrollo. En todo caso, existe el comando watch que se encarga de observar si hemos realizado alguna modificación en nuestros assets, y es entonces cuando aplica el assetic dump.

$ php app/console assetic:watch

Lo genial de trabajar con Symfony es que podemos tener un entorno de desarrollo donde debugear es una tarea sencilla y cómoda:

Y un entorno de producción con todos los assets concatenados y comprimidos:


Bundles externos

Este workflow funciona bastante bien si la librería front-end requerida se adapta a la forma de trabajar de Symfony. En algunos casos es mejor optar por algún bundle externo. Por ejemplo IvoryCKEditorBundle y ObHighchartsBundle son dos bundles que nos simplifican la forma de trabajar con CKEditor y Highchart.