Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Grunt, automatizzare i task per l'ottimizzazione

Come creare un progetto e mettere in piedi il workflow per l'ottimizzazione della pagina. I plugin più importanti per minificare JavaScript e CSS e per ottimizzare le immagini.
Come creare un progetto e mettere in piedi il workflow per l'ottimizzazione della pagina. I plugin più importanti per minificare JavaScript e CSS e per ottimizzare le immagini.
Link copiato negli appunti

Negli ultimi anni l'importanza di ottimizzare le risorse frontend (JavaScript, CSS e immagini) ha raggiunto una grande visibilità, soprattutto alla luce della tendenza diffusa a separare le logiche backend (implementate come Backend as a Service) da quelle di frontend (Web Application).

Con il crescere delle dimensioni dei file di progetto e senza poter contare sui sistemi di ottimizzazione lato server come le pipeline o gli asset manager offerti dai principali sistemi lato server, è diventato critico usare tool di build che possano permetterci di sviluppare e debuggare i sorgenti in sviluppo per poi servirne una versione ottimizzata in produzione.

Grunt è ad oggi uno dei tool di build per il frontend più accreditati e fornisce tutti gli strumenti per realizzare un workflow efficiente per la realizzazione di web applications.

In questo articolo vedremo come configurare Grunt per ottenere una struttura di base per il build dei nostri progetti.

In particolare ci focalizzeremo su:

  • concatenazione e minificazione di JavaScript
  • minificazione CSS
  • ottimizzazione delle immagini

Preparazione dell'ambiente

Rispetto alle vecchie versioni, la struttura di Grunt è stata ulteriormente modularizzata, rendendolo più leggero ed offrendo più libertà di scelta per quanto riguarda le funzionalità da implementare. Inoltre il tool viene installato per project permettendo di utilizzarne diverse versioni o comunque garantendo la funzionalità su vecchi progetti.

Una volta installato Nodejs dobbiamo installare a livello di sistema il tool di Grunt da riga di comando:

npm install -g grunt-cli

Il secondo passo è quello di creare, se non già presente, un file package.json nella radice del progetto che verrà usato per specificare i dati del progetto stesso (nome, autori, versione) nonché la lista dei plugin di Grunt da utlizzare in fase di build:

cd mio-progetto
npm init

Dopo aver risposto alle domande del sistema, il vostro file package.json dovrebbe essere simile a questo:

{
    name: "mio-progetto",
    version: "0.0.1",
    description: "Il mio progetto",
    author: "Marco Solazzi",
    license: "MIT"
}

A questo punto installiamo la libreria principale di Grunt.

npm install grunt --save-dev

Da notare che, per questo e tutti i plugin che aggiungeremo, utilizzeremo il flag --save-dev, in modo da salvare un riferimento alla versione installata nel file package.json. Potremo così evitare di dover condividere con gli altri sviluppatori la cartella node_modules con le dipendenze del progetto perché gli basterà lanciare npm install (senza altri argomenti) per scaricare tutti i pacchetti necessari.

Struttura del progetto

Nell'immagine seguente viene mostrata l'alberatura del nostro progetto.

In questa struttura di esempio modificheremo solo i file nella cartella src, mentre la cartella dist verrà popolata con il risultato del processo di build.

Gruntfile di base

I file di configurazione di Grunt sono denominati Gruntfile e si trovano solitamente nella radice del progetto.

Per questo tutorial andremo a creare un file Gruntfile.js (attenzione alla prima lettera maiuscola) con questo contenuto:

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json')
    });
};

La prima riga module.exports rivela come un Gruntfile sia un modulo nodejs, che viene richiamato da Grunt in fase di inizializzazione.

Fra le funzionalità aggiuntive di Grunt c'è il metodo grunt.file.readJSON che permette di leggere file JSON e ne restituisce il contenuto come oggetto JavaScript. Nel nostro caso andremo ad associare il contenuto di package.json alla proprietà pkg. Vedremo più avanti come potremo utilizzarla.

Subito dopo pkg andremo a configurare i nostri task. In molti casi è possibile anche definire dei sotto task o target per diversificare le opzioni ed i file su cui lavorare. Generalmente la stintassi utilizzata è la seguente:

nome_task: {
    nome_target: {
        options: { /* opzioni del target */ },
        files: {
            src: [/* lista dei file sorgente */],
            dest: /* file di destinazione */
        }
    }
}

Tutti i percorsi dei file sono relativi alla posizione del Gruntfile. In realtà esistono varie opzioni per definire la lista dei file da usare ed è anche possibile impostare una proprietà options direttamente sul task con le opzioni comuni a tutti i target.

Ora siamo pronti ad esaminare il Workflow che ci permetterà di automatizzare l'ottimizzazione del codice delle nostre pagine

Configurazione del workflow

A parte la libreria principale, tutti i plugin installati andranno caricati nel Gruntfile. Per fare ciò, utilizzeremo il metodo grunt.loadNpmTasks appena dopo la chiusura del metodo grunt.initConfig:

grunt.loadNpmTasks('nome-plugin');
grunt.loadNpmTasks('nome-altro-plugin');

Un po' di pulizia

Anzitutto, per evitare di lasciare file non più utilizzati nella cartella di build (ad esempio immagini che abbiamo rimosso dal progetto), la prima operazione da fare è assicurarsi di rimuovere la cartella dist che verrà così ricreata da zero dai task successivi. Per tale scopo utilizzeremo il plugin grunt-contrib-clean:

npm install --save-dev grunt-contrib-clean

Aggiungendo il task nell'oggetto di configurazione dopo la proprietà pkg:

clean: {
    dist: ['dist']
}

Concatenare e Minificare i JavaScript

Per ottimizzare i file JavaScript utilizzeremo due plugin grunt-contrib-concat (per ottenere un file unico) e grunt-contrib-uglify (per minificarlo con uglify2).

npm install --save-dev grunt-contrib-concat grunt-contrib-uglify

i due task saranno configurati in questo modo:

concat: {
    dist: {
        files: {
            src: ['src/javascripts/*.js'],
            dest: 'dist/javascripts/main.js'
        }
    }
},
uglify: {
    dist: {
        options: {
            banner: '/*! <%= pkg.name %> - <%= pkg.version %> - <%= pkg.author %> */n'
        },
        files: {
            src: ['<%= concat.dist.dest %>'],
            dest: 'dist/javascripts/main.min.js'
        }
    }
}

Come avrete notato il file sorgente del target uglify.dist è in realtà un riferimento alla configurazione di concat.dist. Questa sintassi erb-like è generalmente supportata da tutti i plugin e può essere molto utile per realizzare riferimenti incrociati fra i task ed evitare ripetizioni nel file di configurazione. Oltre a cià abbiamo utilizzato i valori di pkg per aggiungere un banner, cioè un'intestazione, al file minificato. In questo modo i dati saranno sempre in linea con il progetto.

Ottimizzare i CSS

La minificazione dei CSS prevede due passaggi: la rimozione degli spazi e la compressione del codice utilizzando, ove possibile, le notazioni shorthand (un po' come fanno i minificatori JavaScript). Per questo task ci appoggeremo a grunt-contrib-cssmin, un wrapper per clean-css:

npm install --save-dev grunt-contrib-cssmin

La configurazione sarà:

cssmin: {
    dist: {
        files: {
            src: ['src/stylesheets/*.css'],
            dest: 'dist/stylesheets/application.min.css'
        }
    }
}

Preprocessori CSS

Al giorno d'oggi un workflow di sviluppo moderno non può più prescindere dall'uso di preprocessori CSS. Grunt offre plugin per Sass e Compass, ma nel caso non vogliate aggiungere ulteriori dipendenze potete usare Less e compilarlo con grunt-contrib-less:

less
    dist: {
        options: {
            cleancss: true
        },
        files: {
            src: ['src/stylesheets/*.less'],
            dest: 'dist/stylesheets/application.min.css'
        }
    }
}

L'opzione cleancss produce un CSS già minificato con clean-css, quindi non avrete bisogno del task cssmin.

Compressione delle immagini

Una delle best practices nel campo delle performance di un sito web è ottimizzare le immagini.

Con il supporto a features CSS3 come gradienti e bordi arrotondati il numero di immagini utilizzate per comporre l'interfaccia grafica di un sito è diminuito drasticamente, tuttavia in alcuni casi dobbiamo ancora farvi ricorso.

Una prima parte del lavoro di ottimizzazione può essere fatta quando le ritagliamo dal file sorgente, andando a scegliere il formato migliore e il livello di compressione, ma possiamo guadagnare ancora qualcosa utilizzando tool come OptiPNG e jpegtran che vanno a limare byte dalle immagini eliminando dati inutili ed ottimizzandone il livello di compressione.

Esistono vari plugin per automatizzare queste operazioni, quello supportato direttamente dal team di Grunt è grunt-contrib-imagemin, che funge da wrapper per jpegtran, gifsicle, OptiPNG e pngquant:

npm install --save-dev grunt-contrib-imagemin

La configurazione prevede varie opzioni per i singoli tool, comunque non saranno necessari target diversi per ogni tipo di immagine poiché sarà il plugin a determinare come comportarsi in base all'estensione dei file:

imagemin: {
    dist: {
        files: [{
            expand: true,                  // mappatura dinamica dei file
            cwd: 'src/',                   // i percorsi di src sono relativi a questa cartella
            src: ['**/*.{png,jpg,gif}'],   // file sorgenti
            dest: 'dist/'                  // cartella di destinazione
        }]
    }
}

In questo caso abbiamo utilizzato un particolare formato dell'oggetto files, che permette di realizzare una mappatura dinamica dei file uno a uno poiché, diversamente dagli altri task, non abbiamo un unico file di destinazione.

Lanciare i task

Una volta conclusa la configurazione possiamo eseguire Grunt nella directory in cui si trova il Gruntfile. Il formato del comando è:

grunt task[:target]

Quindi per eseguire l'ottimizzazione delle immagini eseguiremo:

grunt imagemin:dist

Omettendo il target, Grunt eseguirà tutti quelli configurati sul task. Possiamo anche omettere il nome del task, in tal caso il sistema cercherà di lanciare un task denominato default che possiamo definire con il metodo grunt.registerTask:

grunt.registerTask('default', ['concat', 'uglify', 'cssmin', 'imagemin']);

In questo modo abbiamo creato un task che eseguirà in sequenza tutti i task definiti nel secondo parametro.

Seguendo questa logica possiamo definire altri task, per organizzare e raggruppare più task:

grunt.registerTask('dist_js', ['concat', 'uglify']);
grunt.registerTask('dist', ['dist_js', 'cssmin', 'imagemin']);

Task aggiuntivi

La configurazione mostrata in questo articolo può essere considerata come il punto di partenza da estendere ed adattare a seconda dello stack frontend utilizzato nei vostri progetti.

Per tutte le altre necessità potete fare riferimento al repository ufficiale dei plugin di Grunt.


Ti consigliamo anche