Per molti sviluppatori la fase di build di un'applicazione è parte integrante del processo di realizzazione di un software. Questo passaggio è estremamente utile per automatizzare operazioni ripetitive e per ottimizzare le performance di esecuzione del prodotto finale.
Fino a qualche tempo fa, nel campo del front-end la posizione degli sviluppatori è spesso stata: il sistema di build si lancia col pulsante "refresh" del browser, a dire che non servono tool di build in quanto è il browser stesso a comporre il software.
...in molti sentono l'esigenza di automatizzare operazioni concatenazione e minificazione dei file.
Con l'aumento delle dimensioni e della complessità delle applicazioni lato client, tuttavia, in molti hanno sentito la necessità di automatizzare operazioni come la concatenazione e la minificazione dei file.
I primi passi in questa direzione sono stati compiuti adottando tool ampiamente collaudati quali Ant, anche perché strumenti come Closure Compiler e YUI Compressor sono rilasciati come pacchetti Java.
>>Leggi il nostro articolo introduttivo su ANT
Il problema principale, in questo caso, è che la sintassi XML usata da Ant è estremamente prolissa e aggiungere funzionalità per chi non conosce Java può risultare un'operazione ostica. Lo stesso dicasi per altri strumenti come Rake e Make.
Build con Node.js
La soluzione al problema è arrivata con la notorietà e diffusione di Node.js, un progetto che porta il linguaggio JavaScript sul server, rendendolo anche disponibile da riga di comando.
>>Leggi la nostra guida a Node.js
Il vantaggio principale di Node.js è che è facile da installare su tutte le principali piattaforme, ha un repository di pacchetti integrato e semplice da usare, ed il linguaggio con cui scrivere gli script è familiare a tutti gli sviluppatori front-end.
Altro fattore determinante per il successo di Node come strumento di build è la disponibilità nei repository di tutte le librerie necessarie, come UglifyJS (per la minificazione del sorgente) e JSHint (per la validazione del codice).
Task-based build con Grunt
Da questi presupporti, è partito lo sviluppo di Grunt uno strumento di build task based, cioè basato su operazioni concatenabili (alla maniera di Ant).
Al momento i task supportati nativamente sono:
Task | Descrizione |
---|---|
init | Crea i file e le cartelle di base per un progetto basandosi su una serie di template |
concat | concatena una serie di file |
lint | valida i file con JSHint. |
min | minifica file JavaScript con UglifyJS. |
qunit | unit testing con QUnit. |
server | lancia un server web statico. |
test | unit testing con nodeunit. |
watch | lancia specifici task quando dei file vengono modificati. |
Oltre a questi, l'architettura di Grunt permette di realizzare dei task aggiuntivi sotto forma di plugin, oppure di installare quelli realizzati da altri sviluppatori della community.
La maggior parte di questi task possono contenere dei sotto-task (detti target) per suddividere le operazioni da compiere oppure per diversificare il risultato finale fra ambiente di sviluppo e produzione.
Passiamo ora agli aspetti pratici dell'utilizzo di Grunt.
Installazione
L'unico requisito per l'installazione di Grunt è possedere una versione recente di Nodejs (almeno 0.8.9) ed il package manager npm (di solito compreso nei pacchetti di installazione).
Per utilizzare il task qunit sarà invece necessario installare PhantomJS, un browser WebKit headless, cioè eseguibile da riga di comando senza interfaccia grafica.
Una volta soddisfatte le dipendenze, per installare Grunt basta digitare in un terminale:
npm install grunt -g
Il flag -g
permette di installare il pacchetto globalmente, rendendolo disponibile per tutti i progetti come script da riga di comando.
Una volta finita l'installazione potete verificare che tutto sia andato a buon fine digitando il comando:
(.*)
grunt --help
Su Windows utilizzate invece:
grunt.cmd --help
JavaScript Power Tools
Grunt fa parte di una serie di "Power tools" che permettono di affrontare lo sviluppo front-end di applicazioni single page, mobile o comunque di app moderne adottando un approccio più strutturato, quasi "enterprise". Ne parla bene Marcello Teodori in questo video:
Configurazione di un progetto
È possibile utilizzare Grunt sia in progetti nuovi che pre-esistenti.
Al momento immagineremo di volerlo integrare in un progetto con la seguente struttura:
- /
-- js/ (file javascript)
-- lib/ (librerie di terze parti)
-- src/ (sorgenti del progetto)
Il primo passo per integrare Grunt è creare un file di configurazione (o gruntfile) chiamato grunt.js
nella root del progetto.
Al suo interno verranno indicati i task ed i relativi file su cui lavorare. Ecco il contenuto del file per il nostro progetto di esempio:
module.exports = function(grunt) {
// Configurazione del progetto.
grunt.initConfig({
meta: {
name: 'Il mio progetto',
autore: 'Marco Solazzi',
version: '0.1.0',
banner: '/*! <%= meta.name %> - v<%= meta.version %> - ' +
' Copyright (c) <%= grunt.template.today("yyyy") %> <%= meta.autore %> */'
},
concat: {
dist: {
src: ['<banner:meta.banner>', '<file_strip_banner:js/src/*.js>'],
dest: 'js/main.js'
},
lib: {
src: ['js/lib/**/*.js'],
dest: 'js/libs.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: 'js/main.min.js'
}
},
watch: {
files: 'js/src/*.js',
tasks: 'concat:dist min'
}
});
grunt.registerTask('default', 'concat min');
};
L'oggetto passato al metodo grunt.initConfig
contiene le proprietà di configurazione del progetto con i relativi parametri.
Vediamo alcune di queste proprietà più da vicino.
meta
La proprietà meta
può contenere sia informazioni sul progetto che una proprietà speciale banner
utilizzata per redigere il commento iniziale presente nei file creati da Grunt.
Come per la maggior parte dei task è possibile generare dinamicamente il contenuto di banner
utilizzando la sintassi dei template per Underscore.js e una serie di metodi e funzioni proprie di Grunt.
Nel nostro caso abbiamo utilizzato sia proprietà dell'oggetto meta
stesso (ad esempio meta.name
) sia metodi come grunt.template.today("yyyy")
per stampare l'anno corrente. La documentazione completa di questi ultimi è disponibile a questo indirizzo.
concat
Il task concat
serve a concatenare più sorgenti in un unico file. Gli scenari di utilizzo di questo task sono numerosi e vanno dalla concatenazione di librerie e plugin per ridurre il numero di chiamate, alla compilazione di un unico file applicazione a partire da moduli discreti.
Come altri, concat
è un task multi che offre la possibilità di impostare vari target. Nel nostro esempio abbiamo impostato due target:
dist
: per concatenare i file sorgente della nostra applicazionelib
: per concatenare le librerie di terze parti
Di default il comando grunt concat
eseguirà ambedue i target, ma è possibile specificarne uno con la sintassi grunt concat:nome_target
.
Altra particolarità di Grunt sono i percorsi dei file di input indicati in src
:
- La direttiva
'<banner:meta.banner>'
compila il banner e lo scrive all'inizio del file. 'js/src/*.js'
seleziona tutti i file JavaScript presenti nella cartellajs/src
'js/lib/**/*.js'
seleziona tutti i file JavaScript injs/lib
e nelle sue sottocartelle
min
Il task min
si comporta esattamente come concat
, con l'unica eccezione che il file finale viene processato attraverso UglifyJS, un minificatore JavaScript molto performante usato fra gli altri dal team di jQuery.
Nel nostro esempio il file di input è il risultato del task concat:dist
ed è identificato dalla direttiva '<config:concat.dist.dest>'
.
watch
Il compito di watch
è quello di rimanere in ascolto sui file indicati nella proprietà files
e scatenare uno o più task a seguito di una modifica.
Anche watch
è un task multi, e può essere eseguito con il comando:
grunt watch
Nel nostro caso non abbiamo bisogno di target separati, poiché ne avremo bisogno per tenere d'occhio i file sorgenti della nostra applicazione.
Il vantaggio di watch
è che ci permette di lavorare su file separati includendo il solo file minificato nelle pagina web.
Alias task
L'ultima riga del gruntfile serve a registrare un task personalizzato.
Nello specifico registra default
che sarà lanciato quando Grunt verrà eseguito senza parametri ed a sua volta richiamerà i task concat
e min
.
Questa sintassi permette di creare degli alias per raggruppare altri task.
Ad esempio potremmo creare un task specifico per compilare i sorgenti dell'applicazione:
grunt.registerTask('dist', 'concat:dist min');
E richiamarlo con il comando:
grunt dist
Documentazione e riferimenti
Il vantaggio di Grunt rispetto ad altre soluzioni è senza dubbio la grande versatilità, le performance di esecuzione e la sintassi estremamente sintetica e funzionale.
Questi fattori hanno fatto sì che progetti come jQuery e HTML5 Boilerplate lo abbiano adottato come tool standard di build, garantendogli visibilità ed un notevole supporto da parte della community.
Per approfondire la conoscenza di questo strumento potete fare riferimento all'esaustiva documentazione presente su GitHub.
Link utili
- Node.js
- UglifyJS (per la minificazione del sorgente)
- JSHint
- Closure Compiler
- YUI Compressor