Compilando con Gulp

02/11/2016 Build with Gulp

Introducción

El Front-End es todo lo que el Cliente visualiza, esto es: Html, CSS, Javascript, Imágenes, Fuentes, etc. Express servirá todos estos recursos compilando las Vistas .pug y sirviendo los archivos ubicados en la carpeta public. Sin embargo, no es cómodo ni recomendable trabajar directamente con dichos archivos, sino usando otras tecnologías/mecanismos:

  1. El Html se servirá a partir de la compilación de los .pug.
  2. El CSS lo escribiremos usando Sass.
  3. El javascript lo generaremos usando ES6.
  4. Las imágenes y las fuentes simplemente las moveremos de un lugar a otro (aquí se pueden aplicar técnicas de compresión y optimización)

Lo mencionado anteriormente lo conseguiremos definiendo Tareas con Gulp. Para ello lo primero que deberemos hacer es instalarlo con:

npm install -D gulp

Ya podremos usar el comando gulp, siempre y cuando contemos con un archivo gulpfile.js.

Gulpfile.js

Si ejecutamos el comando gulp Gulp buscará un archivo gulpfile.js el cual leerá y ejecutará todas la tareas definidas como default. A continuación editaremos el gulpfile.js para definir las distintas tareas.

Paquetes Gulp

Para llevar a cabo muchas de las Tareas Gulp precisaremos de distintos paquetes especializados que incluiremos como Dependencias del proyecto. Incluiremos los siguientes paquetes para Gulp:

  1. Sourcemaps con npm i -D gulp-sourcemaps
  2. Compilador Sass con npm i -D gulp-sass
  3. Vinyl-name será necesario para mantener el nombre del archivo main.scss al pasar a main.css npm i -D vinyl-named
  4. Opcional: Un Notificador con npm i -D gulp-notify

Layout.pug

Para generar el Html, lo haremos con el motor de plantillas Pug de Express. El generador Express Generator nos ha dispuesto un archivo layout.pug que servirá como plantilla base, es decir, una página desde la cual extenderán todas las demás. Este archivo lo editaremos de la siguiente manera:

                
                    doctype html
                    html
                        head
                            title= title
                            link(rel='stylesheet', href='/css/main.css')
                        body

                            block content

                            footer © ExpressDev all rights are not reserved

                            script(src='/js/main.js')

                            block scripts
                
            

De este modo hemos enlazado a este layout un main.js y un main.css, archivos que contendrán todo el Javascript y el CSS de la Aplicación.

Sass

Todo el Estilo de la Aplicación lo escribiremos usando Sass. Los archivos .sass se ubicarán dentro de resources/sass. Dentro de este directorio crearemos un archivo main.scss. Este archivo será el único que compilaremos con gulp.

Desde main.scss importaremos todos los archivos necesarios para confeccionar el estilo de la Aplicación completa. Escribiremos lo siguiente:

                
                    @charset  "UTF-8";

                    /**************** Vendor *******************/
                    @import  "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
                    @import  "../../node_modules/font-awesome/scss/font-awesome";

                    html, body{
                        height: 100%
                    }

                    body {
                        background: #2A3D45;
                        color: white;
                    }
                
            

Editar gulpfile.js

Realizaremos las modificaciones en el gulpfile.js para definir la tarea sass. Escribiremos en dicho archivo:

                
                    'use strict';
                    var gulp = require('gulp');
                    var sass = require('gulp-sass');
                    var named = require('vinyl-named');
                    var sourcemaps = require('gulp-sourcemaps');
                    var notify = require("gulp-notify");

                    const MAIN_STYLE = 'resources/sass/main.scss';
                    const SCRIPTS_DEST = 'public/js';

                    /**
                     * STYLES COMPILATION
                     */
                    gulp.task('sass', () => {
                        return gulp.src(MAIN_STYLE)
                            .pipe(named())
                            .pipe(sourcemaps.init())
                            .pipe(sass({includePaths: 'node_modules/bootstrap-sass/assets/stylesheets'}).on('error', sass.logError))
                            .pipe(sourcemaps.write('.'))

                            .pipe(gulp.dest(STYLES_DEST))
                            .pipe(notify("Styles compiled!"));
                    });

                    /**
                     * Default
                     */
                    gulp.task('default', ['sass']);
                
            

Guardamos y lo compilamos escribiendo gulp. Se generará un archivo main.css dentro de la carpeta public.

Si reiniciamos el servidor (npm start) podremos ver un resultado como éste:

Initial Express

Con esto habremos compilado el sass de nuestro proyecto.

Scripts

De manera parecida, crearemos un sólo archivo js que hará de entry point de todo el Javascript. Este archivo lo crearemos en resources/js y lo llamaremos main.js. Desde main.js importaremos todos los archivos .js necesarios mediante requires de ES6.

También crearemos otro archivo, al lado de main.js, al que llamaremos bootstrap.js. En este archivo haremos las importaciones de todas las librerías de terceros, de este modo tendremos por separado las importaciones de nuestro desarrollo javascript (en main.js) de las externas (en bootstrap.js).

Editaremos el fichero bootstrap.js con lo siguiente:

                
                    /**
                     * Vendor libraries
                     */
                    window.$ = window.jQuery = require('jquery');
                    require('bootstrap-sass');
                
            

Ahora en el main.js escribiremos:

                
                    require('./bootstrap');

                    //****** APP Scripts **********
                    $(document).ready(() => {
                        console.log('Hello World!');
                    });
                
            

De este modo, únicamente desde main.js importamos todo el Javascript necesario. Será este fichero el que compilaremos con gulp (muy parecido a lo hecho con Sass). Por ahora, lo único que hace nuestro main.js es imprimir en pantalla un Hello World!.

Webpack

ES6 no esta del todo soportado actualmente por los Navegadores, por esta razón será necesario el uso de un compilador/transpilador. En este proyecto usaremos Webpack.

Instalaremos Webpack para gulp con npm install -D webpack-stream.

También instalaremos Through2 con npm install -D through2 para adaptar la configuración de Webpack en conjunto con Sourcemaps.

Para configurar Webpack, la forma correcta es mediante un archivo webpack.config.js. Crearemos este archivo en la raíz del proyecto. En él escribiremos lo siguiente:

                
                    module.exports = {
                        devtool: 'source-map'
                    };
                
            

Ahora Webpack usará a Sourcemaps.

Editar gulpfile.js

Volveremos a modificar gulpfile.js para definir la tarea scripts encargada de compilar todo el Javascript. Lo editaremos para escribir lo siguiente:

                
                    var through = require('through2');
                    var webpack = require('webpack-stream');
                    ...
                    const MAIN_SCRIPT = 'resources/js/main.js';
                    const SCRIPTS_DEST = 'public/js';
                    ...
                    /**
                     * SCRIPTS COMPILATION
                     */
                    gulp.task('scripts', () => {
                        return gulp.src(MAIN_SCRIPT)
                            .pipe(named())
                            .pipe(webpack(require('./webpack.config')))
                            .pipe(sourcemaps.init({loadMaps: true}))
                            .pipe(through.obj(function (file, enc, cb) {
                                // Dont pipe through any source map files as it will be handled
                                // by gulp-sourcemaps
                                var isSourceMap = /\.map$/.test(file.path);
                                if (!isSourceMap) this.push(file);
                                cb();
                            }))
                            .pipe(notify("Scripts compiled"))
                            .pipe(sourcemaps.write('.'))
                            .pipe(gulp.dest(SCRIPTS_DEST));
                    });
                
            

No olvidemos modificar la tarea default con:

gulp.task('default', ['sass', 'scripts']);

Guardamos todo y finalmente, realizaremos la compilación con gulp. Esto generará un archivo main.js dentro de la carpeta public/js. Si abrimos el navegador en la ruta localhost:3000 deberemos ver algo así:

Initial Express

Si vemos el mensaje Hello World! en la consola del Navegador, sabremos que nuestros scripts se han compilado correctamente.

Fuentes

Todo desarrollo web hace uso de Fuentes, y este no iba a ser menos. Al estar usando Bootstrap y Font-Awesome, será necesaria alguna tarea encargada de ubicar las Fuentes Web en la carpeta public. De modo que, editaremos gulpfile.js:

                
                    /**
                     * Copy Fonts from node_modules to the public folder
                     */
                    gulp.task('fonts', function() {
                        gulp.src('./node_modules/bootstrap-sass/assets/fonts/bootstrap/*')
                            .pipe(gulp.dest('public/fonts/bootstrap'));

                        gulp.src('./node_modules/font-awesome/fonts/*')
                            .pipe(gulp.dest('public/fonts/font-awesome'));
                    });
                
            

No nos olvidamos de incluir esta tarea en el array de tareas default con:

gulp.task('default', ['sass', 'scripts', 'fonts']);

Si ejecutamos gulp ahora, observaremos cómo se generan nuevos archivos dentro de public.

Incluir las Fuentes en main.scss

Las fuentes deberán estar localizadas desde el CSS de la Aplicación, así que será necesario crear algunas variables que contengan esta información. Crearemos un partial _variables.scss dentro de un nuevo directorio resources/js/globals.

Editaremos main.scss con:

                
                    /*********************** App variables **************************/
                    //Fonts
                    $fonts-directory: "../../fonts/";

                    //Colors https://coolors.co/7a6c5d-2a3d45-ddc9b4-bcac9b-c17c74
                    $color-1: #7A6C5D;
                    $color-2: #2A3D45;
                    $color-3: #DDC9B4;
                    $color-4: #BCAC9B;
                    $color-5: #C17C74;

                    /*********************** Vendor variables **************************/
                    $node_modules: "../../../node_modules/";
                    //Bootstrap
                    $icon-font-path: $fonts-directory + "/bootstrap/";

                    //Font-Awesome
                    $fa-font-path: $fonts-directory + "font-awesome";
                
            

Nótese la definición de las variables $fonts-directory, $icon-font-path y $fa-font-path (estas variables sobreescriben la ubicación por defecto de las librerías de Bootstrap-sass y Font-Awesome para adaptarlas a nuestro desarrollo).

También he incluído unas variables de colores Estos colores proceden de una Paleta de Colores, generada desde aquí.

No olvidemos incluir a _variables.scss en main.scss:

                
                    @charset  "UTF-8";
                    @import  "globals/variables";
                
            

Añadamos algún elemento con una Fuente al index.pug, de este modo sabremos si se cargan las fuentes adecuadamente:

                extends layout

                block content
                    .container
                        h1= title
                        p Welcome to #{title}
                        p Probando fuentes
                            span.fa.fa-heart.fa-fw
            

Ahora, tras compilarlo todo con gulp, dispondremos de las fuentes y deberíamos ver algo asi:

Initial Express

Añadí una clase .container de Bootstrap por ahí ;)

Imágenes

La tarea de las imágenes es la más sencilla. En este desarrollo sólo se limitará a copiarlas desde resources/images a public/images aplicándoles una minificación para optimizar los tiempos de carga.

Instalamos imagemin con npm install -D gulp-imagemin

Editar gulpfile

Añadiremos la nueva tarea images en el gulpfile.js. Escribimos:

                
                    var imagemin = require('gulp-imagemin');
                    ...
                    /**
                     * Copy Images to the public folder
                     */
                    gulp.task('images', function() {
                        gulp.src('./resources/images/*')
                            .pipe(imagemin())
                            .pipe(gulp.dest('public/images'));
                    });
                
            

No nos olvidamos de añadir la tarea a la default con:

gulp.task('default', ['sass', 'scripts', 'fonts', 'images']);

Descargamos cualquier imagen y la ponemos dentro de resources/images. Tras ejecutar gulp, veremos cómo nuestra imagen aparece dentro de public/images con un tamaño menor.

Nodemon

Nodemon es una herramienta excelente para agilizar el desarrollo de aplicaciones basadas en NodeJS. Lo que hace simplemente es reiniciar el servidor de Node cada vez que detecte algún cambio en el código fuente de nuestra Aplicación.

Gulp Nodemon

Lo primero que deberemos hacer es instalarlo con npm install -D gulp-nodemon

Editar gulpfile.js

Ahora añadiremos una nueva tarea al gulpfile.js, escribiendo:

                
                    var nodemon = require('gulp-nodemon');
                    ...
                    /**
                     * Gulp Nodemon!
                     * It watches all files from backend
                     */
                    gulp.task('nodemon', () => {
                        nodemon({ script: './bin/www'
                            , ignore: ['./public', './node_modules']
                            , watch: ['app.js', './server']
                            , ext: 'js scss'});
                            //.on('start', ['scripts', 'sass', 'fonts']);
                    });
                
            

Con esto, cada vez que modifiquemos app.js o un archivo dentro de la carpeta server Nodemon volverá a ejecutar al archivo bin/www, logrando así el reinicio del Servidor.

En este caso, no modificaremos la tarea default, dejaremos por separado el Front-end del Back-end. No queremos que se reinicie el server al modificar el front-end y viceversa.

Ahora será Gulp quien iniciará el servidor, en lugar de npm, es decir, para arrancar la aplicación, de ahora en adelante usaremos gulp nodemon en lugar de npm start. (Si queremos, podríamos modificar la entrada scripts del package.json para que sea npm quien ejecute la tarea nodemon).

Gulpfile final

Todo el archivo gulpfile.js, adaptado a ES6, nos quedaría así:

                
                    'use strict';
                    const gulp = require('gulp');
                    const sass = require('gulp-sass');
                    const named = require('vinyl-named');
                    const sourcemaps = require('gulp-sourcemaps');
                    const through = require('through2');
                    const webpack = require('webpack-stream');
                    const nodemon = require('gulp-nodemon');
                    const imagemin = require('gulp-imagemin');
                    const notify = require("gulp-notify");

                    const MAIN_SCRIPT = 'resources/js/main.js';
                    const MAIN_STYLE = 'resources/sass/main.scss';
                    const SCRIPTS_DEST = 'public/js';
                    const STYLES_DEST = 'public/css';

                    /**
                     * STYLES COMPILATION
                     */
                    gulp.task('sass', () => {
                        return gulp.src(MAIN_STYLE)
                            .pipe(named())
                            .pipe(sourcemaps.init())
                            .pipe(sass({includePaths: 'node_modules/bootstrap-sass/assets/stylesheets'}).on('error', sass.logError))
                            .pipe(sourcemaps.write('.'))

                            .pipe(gulp.dest(STYLES_DEST))
                            .pipe(notify("Styles compiled!"));
                    });

                    /**
                     * SCRIPTS COMPILATION
                     */
                    gulp.task('scripts', () => {
                        return gulp.src(MAIN_SCRIPT)
                            .pipe(named())
                            .pipe(webpack(require('./webpack.config')))
                            .pipe(sourcemaps.init({loadMaps: true}))
                            .pipe(through.obj(function (file, enc, cb) {
                                // Dont pipe through any source map files as it will be handled
                                // by gulp-sourcemaps
                                var isSourceMap = /\.map$/.test(file.path);
                                if (!isSourceMap) this.push(file);
                                cb();
                            }))
                            .pipe(notify("Scripts compiled"))
                            .pipe(sourcemaps.write('.'))
                            .pipe(gulp.dest(SCRIPTS_DEST));
                    });

                    /**
                     * Copy Fonts from node_modules to the public folder
                     */
                    gulp.task('fonts', function() {
                        gulp.src('./node_modules/bootstrap-sass/assets/fonts/bootstrap/*')
                            .pipe(gulp.dest('public/fonts/bootstrap'));

                        gulp.src('./node_modules/font-awesome/fonts/*')
                            .pipe(gulp.dest('public/fonts/font-awesome'));
                    });

                    /**
                     * Copy Images to the public folder
                     */
                    gulp.task('images', function() {
                        gulp.src('./resources/images/*')
                            .pipe(imagemin())
                            .pipe(gulp.dest('public/images'));
                    });

                    /**
                     * Gulp Nodemon!
                     * It watches all files from backend
                     */
                    gulp.task('nodemon', () => {
                        nodemon({ script: './bin/www'
                            , ignore: ['./public', './node_modules']
                            , watch: ['app.js', './server']
                            , ext: 'js scss'});
                            //.on('start', ['scripts', 'sass', 'fonts']);
                    });

                    /**
                     * Default
                     */
                    gulp.task('default', ['sass', 'scripts', 'fonts', 'images']);
                
            

Y ya sabéis, el código completo lo podéis encontrar aquí.

Si tienes alguna duda o sugerencia, no dudes en participar!