Muchos de nosotros tenemos que manejar proyectos basados en la web que se utilizan en la producción, que brindan diversos servicios al público. Cuando se trata de proyectos de este tipo, es importante poder compilar e implementar nuestro código rápidamente. Hacer algo rápidamente a menudo conduce a errores, especialmente si un proceso es repetitivo, por lo tanto, es una buena práctica automatizar dicho proceso tanto como sea posible.
En esta publicación, veremos una herramienta que puede ser parte de lo que nos permitirá lograr dicha automatización. Esta herramienta es un paquete npm llamado Gulp.js. Para familiarizarse con la terminología básica de Gulp.js que se utiliza en esta publicación, consulte ' Introducción a la automatización de JavaScript con Gulp ”Que fue publicado previamente en el blog por Antonios Minas , uno de nuestros compañeros desarrolladores de ApeeScape. Asumiremos una familiaridad básica con el entorno npm, ya que se usa ampliamente a lo largo de esta publicación para instalar paquetes.
Antes de continuar, retrocedamos algunos pasos para obtener una descripción general del problema que Gulp.js puede resolver por nosotros. Muchos proyectos basados en web cuentan con archivos JavaScript de front-end que se sirven al cliente para proporcionar diversas funcionalidades a la página web. Por lo general, también hay un conjunto de hojas de estilo CSS que se entregan al cliente. A veces, cuando miramos el código fuente de un sitio web o una aplicación web, podemos ver un código como este:
|_+_|Hay algunos problemas con este código. Tiene referencias a dos hojas de estilo CSS separadas y cuatro archivos JavaScript separados. Esto significa que el servidor debe realizar un total de seis solicitudes al servidor, y cada solicitud debe cargar un recurso por separado antes de que la página esté lista. Esto es un problema menor con HTTP / 2 porque HTTP / 2 introduce paralelismo y compresión de encabezados, pero sigue siendo un problema. Aumenta el volumen total de tráfico que se requiere para cargar esta página y reduce la calidad de la experiencia del usuario porque lleva más tiempo cargar los archivos. En el caso de HTTP 1.1, también acapara la red y reduce la cantidad de canales de solicitud que están disponibles. Hubiera sido mucho mejor combinar los archivos CSS y JavaScript en un solo paquete para cada uno. De esa forma, solo habría un total de dos solicitudes. También habría sido bueno servir versiones minimizadas de estos archivos, que suelen ser mucho más pequeños que los originales. Nuestra aplicación web también podría romperse si alguno de los activos se almacena en caché y el cliente recibiría una versión desactualizada.
Un enfoque primitivo para resolver algunos de estos problemas es combinar manualmente cada tipo de activo en un paquete usando un editor de texto y luego ejecutar el resultado a través de un servicio minificador, como http://jscompress.com/ . Esto resulta muy tedioso de hacer continuamente durante el proceso de desarrollo. Una mejora leve pero cuestionable sería alojar nuestro propio servidor minificador, usando uno de los paquetes disponibles en GitHub. Entonces podríamos hacer cosas que se verían algo similares a las siguientes:
|_+_|Esto serviría archivos minificados a nuestro cliente, pero no resolvería el problema del almacenamiento en caché. También causaría una carga adicional en el servidor, ya que nuestro servidor esencialmente tendría que concatenar y minificar todos los archivos fuente repetidamente en cada solicitud.
Seguramente podemos hacerlo mejor que cualquiera de estos dos enfoques. Lo que realmente queremos es automatizar la agrupación e incluirla en la fase de construcción de nuestro proyecto. Queremos terminar con paquetes de activos prediseñados que ya están minificados y listos para servir. También queremos obligar al cliente a recibir las versiones más actualizadas de nuestros activos agrupados en cada solicitud, pero aún queremos aprovechar el almacenamiento en caché si es posible. Afortunadamente para nosotros, Gulp.js puede manejar eso. En el resto del artículo, crearemos una solución que aprovechará el poder de Gulp.js para concatenar y minimizar los archivos. También usaremos un complemento para romper el caché cuando haya actualizaciones.
Crearemos el siguiente directorio y estructura de archivos en nuestro ejemplo:
public/ |- build/ |- js/ |- bundle-{hash}.js |- css/ |- stylesheet-{hash}.css assets/ |- js/ |- vendor/ |- jquery.js |- site.js |- module1.js |- module2.js |- css/ |- main.css |- custom.css gulpfile.js package.json
npm hace que la administración de paquetes en los proyectos de Node.js sea una bendición. Gulp proporciona una enorme capacidad de ampliación al aprovechar el enfoque de empaquetado simple de npm para ofrecer complementos modulares y potentes.El archivo gulpfile.js es donde definiremos las tareas que Gulp realizará por nosotros. El package.json
npm lo utiliza para definir el paquete de nuestra aplicación y realizar un seguimiento de las dependencias que instalaremos. El directorio público es lo que debe configurarse para enfrentarse a la web. El directorio de activos es donde almacenaremos nuestros archivos fuente. Para usar Gulp en el proyecto, necesitaremos instalarlo a través de npm y guardarlo como una dependencia de desarrollador para el proyecto. También querremos comenzar con concat
plugin para Gulp, que nos permitirá concatenar varios archivos en uno.
Para instalar estos dos elementos, ejecutaremos el siguiente comando:
|_+_|A continuación, querremos comenzar a escribir el contenido de gulpfile.js.
|_+_|Aquí, estamos cargando la biblioteca gulp y su complemento concat. Luego definimos tres tareas.
La primera tarea (npm install --save-dev gulp gulp-concat
) define un procedimiento para comprimir varios archivos de origen JavaScript en un paquete. Enumeramos los archivos de origen, que se agruparán, leerán y concatenarán en el orden especificado. Lo canalizamos en el complemento concat para obtener un archivo final llamado var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);
. Finalmente, le decimos a gulp que escriba el archivo en pack-js
.
La segunda tarea (bundle.js
) hace lo mismo que la anterior, pero para las hojas de estilo CSS. Le dice a Gulp que almacene la salida concatenada como public/build/js
en pack-css
.
La tercera tarea (stylesheet.css
) es la que ejecuta Gulp cuando la invocamos sin argumentos. En el segundo parámetro, pasamos la lista de otras tareas a ejecutar cuando se ejecuta la tarea predeterminada.
Peguemos este código en gulpfile.js usando cualquier editor de código fuente que usamos normalmente, y luego guardemos el archivo en la raíz de la aplicación.
A continuación, abriremos la línea de comando y ejecutaremos:
|_+_|Si miramos nuestros archivos después de ejecutar este comando, encontraremos dos archivos nuevos: public/build/css
y default
. Son concatenaciones de nuestros archivos fuente, lo que resuelve parte del problema original. Sin embargo, no están minimizados y todavía no se ha eliminado la memoria caché. Agreguemos la minificación automática.
Necesitaremos dos complementos nuevos. Para agregarlos, ejecutaremos el siguiente comando:
|_+_|El primer complemento es para minificar CSS y el segundo es para minificar JavaScript. El primero usa el paquete clean-css y el segundo usa el paquete UglifyJS2. Primero cargaremos estos dos paquetes en nuestro gulpfile.js:
|_+_|Luego, necesitaremos usarlos en nuestras tareas justo antes de escribir la salida en el disco:
|_+_|El archivo gulpfile.js ahora debería verse así:
|_+_|Traguemos saliva de nuevo. Veremos que el archivo gulp
se guarda en formato minificado, y el archivo public/build/js/bundle.js
todavía se guarda como está. Notaremos que ahora también tenemos bundle-min.js, que está minificado. Solo queremos el archivo minificado, y queremos que se guarde como public/build/css/stylesheet.css
, por lo que modificaremos nuestro código con parámetros adicionales:
Según la documentación del complemento gulp-minify (https://www.npmjs.com/package/gulp-minify), esto establecerá el nombre deseado para la versión minificada y le indicará al complemento que no cree la versión que contiene la fuente original. Si eliminamos el contenido del directorio de compilación y ejecutamos gulp desde la línea de comandos nuevamente, terminaremos con solo dos archivos minificados. Acabamos de terminar de implementar la fase de minificación de nuestro proceso de construcción.
A continuación, querremos agregar la eliminación de caché y necesitaremos instalar un complemento para eso:
|_+_|Y solicítelo en nuestro archivo gulp:
|_+_|Usar el complemento es un poco complicado. Primero tenemos que canalizar la salida minificada a través del complemento. Luego, tenemos que volver a llamar al complemento después de escribir los resultados en el disco. El complemento cambia el nombre de los archivos para que se etiqueten con un hash único y también crea un archivo de manifiesto. El archivo de manifiesto es un mapa que nuestra aplicación puede utilizar para determinar los últimos nombres de archivo a los que debemos hacer referencia en nuestro código HTML. Después de modificar el archivo gulp, debería terminar luciendo así:
npm install --save-dev gulp-clean-css gulp-minify
Con la eliminación de caché adecuada en su lugar, puede volverse loco con un tiempo de caducidad prolongado para sus archivos JS y CSS y reemplazarlos de manera confiable con versiones más nuevas cuando sea necesario.Eliminemos el contenido de nuestro directorio de compilación y ejecutemos gulp nuevamente. Descubriremos que ahora tenemos dos archivos con hashtags adheridos a cada uno de los nombres de archivo, y un manifest.json guardado en var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');
. Si abrimos el archivo de manifiesto, veremos que solo tiene una referencia a uno de nuestros archivos minificados y etiquetados. Lo que sucede es que cada tarea escribe un archivo de manifiesto por separado y una de ellas termina sobrescribiendo a la otra. Tendremos que modificar las tareas con parámetros adicionales que les dirán que busquen el archivo de manifiesto existente y fusionen los nuevos datos en él, si existe. La sintaxis para eso es un poco complicada, así que veamos cómo debería verse el código y luego repasémoslo:
Estamos canalizando la salida a .pipe(minify()) .pipe(cleanCss())
primero. Esto crea archivos etiquetados en lugar de los archivos que teníamos antes. Estamos proporcionando la ruta deseada de nuestro var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify()) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);
, y contando stylesheet.css
para fusionar en el archivo existente, si existe. Luego le decimos a gulp que escriba el manifiesto en el directorio actual, que en ese momento será public / build. El problema de la ruta se debe a un error que se analiza con más detalle en GitHub.
Ahora tenemos minificación automatizada, archivos etiquetados y un archivo de manifiesto. Todo esto nos permitirá entregar los archivos más rápidamente al usuario y romper su caché cada vez que hagamos nuestras modificaciones. Sin embargo, solo quedan dos problemas.
El primer problema es que si hacemos alguna modificación a nuestros archivos fuente, obtendremos archivos nuevos etiquetados, pero los antiguos también permanecerán allí. Necesitamos alguna forma de eliminar automáticamente los archivos minificados antiguos. Resolvamos este problema usando un complemento que nos permitirá eliminar archivos:
|_+_|Lo requeriremos en nuestro código y definiremos dos nuevas tareas, una para cada tipo de archivo fuente:
|_+_|Luego nos aseguraremos de que la nueva tarea termine de ejecutarse antes que nuestras dos tareas principales:
|_+_|Si ejecutamos bundle.js
nuevamente después de esta modificación, solo tendremos los últimos archivos minificados.
El segundo problema es que no queremos seguir corriendo gulp cada vez que hacemos un cambio. Para resolver esto, necesitaremos definir una tarea de observador:
|_+_|También cambiaremos la definición de nuestra tarea predeterminada:
|_+_|Si ahora ejecutamos gulp desde la línea de comandos, encontraremos que ya no construye nada al ser invocado. Esto se debe a que ahora llama a la tarea del observador que observará nuestros archivos de origen en busca de cambios y compilará solo cuando detecte un cambio. Si intentamos cambiar alguno de nuestros archivos fuente y luego miramos nuestra consola nuevamente, veremos que el bundle.js
y .pipe(minify({ ext:{ min:'.js' }, noSource: true }))
las tareas se ejecutan automáticamente junto con sus dependencias.
Ahora, todo lo que tenemos que hacer es cargar el archivo manifest.json en nuestra aplicación y obtener los nombres de archivo etiquetados de ahí. La forma en que lo hagamos depende de nuestra pila de tecnología y lenguaje de back-end particular, y sería bastante trivial de implementar, por lo que no lo repasaremos en detalle. Sin embargo, la idea general es que podemos cargar el manifiesto en una matriz o un objeto, y luego definir una función auxiliar que nos permitirá llamar activos versionados desde nuestras plantillas de una manera similar a la siguiente:
|_+_|Una vez que hagamos eso, no tendremos que preocuparnos por las etiquetas cambiadas en nuestros nombres de archivo nunca más, y podremos enfocarnos en escribir código de alta calidad.
El código fuente final de este artículo, junto con algunos activos de muestra, se puede encontrar en este repositorio de GitHub .
En este artículo, repasamos cómo implementar la automatización basada en Gulp para nuestro proceso de construcción. Espero que esto le resulte útil y le permita desarrollar procesos de compilación más sofisticados en sus propias aplicaciones.
Tenga en cuenta que Gulp es solo una de las herramientas que se pueden usar para este propósito, y hay muchas otras como Grunt, Browserify y Webpack. Varían en sus propósitos y en el alcance de los problemas que pueden resolver. Algunos pueden resolver problemas que Gulp no puede, como agrupar JavaScript módulos con dependencias que se pueden cargar bajo demanda. Esto se conoce como 'división de código' y es una mejora con respecto a la idea de servir un archivo grande con todas las partes de nuestro programa en cada página. Estas herramientas son bastante sofisticadas, pero es posible que se cubran en el futuro. En una publicación siguiente, repasaremos cómo automatizar la implementación de nuestra aplicación.
Relacionado: Gulp Under the Hood: Creación de una herramienta de automatización de tareas basada en flujo