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:
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.
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.
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:
npm i -D gulp-sourcemaps
npm i -D gulp-sass
npm i -D vinyl-named
npm i -D gulp-notify
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.
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;
}
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:
Con esto habremos compilado el sass de nuestro proyecto.
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!.
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.
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í:
Si vemos el mensaje Hello World!
en la consola del Navegador, sabremos que nuestros scripts
se han compilado correctamente.
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.
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:
Añadí una clase .container
de Bootstrap por ahí ;)
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
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 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.
Lo primero que deberemos hacer es instalarlo con npm install -D gulp-nodemon
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).
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í.