Nel primo post introduttivo abbiamo affrontato i concetti base di module loader, module bundler ed introdotto webpack. In questa seconda parte ci concentreremo su quello che più ci attira: il codice!! Andremo a sviluppare un progetto assieme e vedremo come configurare webpack, senza utilizzare “seed” sparsi in rete, ma con un nuovo progetto in sintassi ECMSCript 6/7, al fine di capire con più semplicità e naturalezza le feature ed i vantaggi che questo strumento ci mette a disposizione.
webpack v2.2
Nel mentre di questi post, webpack ha rilasciato la release 2, in questo post potete trovare l announcement ufficiale, ed i prerequisiti d’ installazione li potete trovare nel sito ufficiale, disponibile al seguente indirizzo.
Setup
Andiamo a creare ora, le directory ed i file necessari per il nostro progetto, inizializziamolo con npm, ed installiamo l’ ultima release di webpack.
mkdir webpack-tutorial cd webpack-tutorial npm init -y npm install webpack@beta --save-dev mkdir src touch index.html src/app.js webpack.config.js
In accordo alle specifiche fornite da webpack, abbiamo creato il webpack.config.js, motore del progetto. Questo non è altro che un oggetto Javascript, un modulo di codice contenente determinate funzionalità, che necessita di essere “esportato” osservando la sintassi NodeJS, module.exports, e richiamato attraverso la funzione require()
in altri files.
Introduciamo per prima cosa tre concetti basici, context, entry ed output, alcuni di essi già introdotti nella prima parte del tutorial:
const webpack = require('webpack') const path = require('path') const config = () => { return { context: path.resolve(__dirname, 'src'), entry: './app.js', output: { path: path.resolve(__dirname, 'build'), publicPath: '/build/', filename: 'bundle.js' } } } module.exports = config;
La configurazione riportata è un punto di partenza comune a molti progetti: entry
, comunica a webpack quale files saranno gli entry point
dell’ applicazione. Questi, all’ interno dell’ albero delle dipendenze (dependency tree), saranno situati proprio sulla sommità.
L’ opzione output
, fornisce informazioni a webpack, sulle caratteristiche del prodotto di compilazione o processo di bundling, e nel nostro caso abbiamo fornito le seguenti informazioni:
-
path
: è il percorso assoluto dove i file compilati saranno scritti; -
filename
: i file compilati, saranno nominati secondo questa opzione. -
publicPath
: questa opzione è utilizzata per specificare l indirizzo pubblico dei file. Viene utilizzata da i loaders quando si rende necessaria la scrittura di tags script o tags link , oppure risulta molto utile in caso si stia utilizzando framework che hanno delle folder ben precise dove salvare gli assets statici. Ad esempio Spring Boot utilizza la cartella resources/static per le risorse statiche, di conseguenza questa dovrà anche essere il valore da fornire apublicPath
;
Il markup del nostro index.html è il seguente:
CodingJam | Webpack Tutorial
Aggiorniamo la sezione script
del package.json
, lanciamo npm start
, e se non vi sono errori, otterremo il seguente risultato:
Hash: 58d8e64d2ff0a7d0e061 Version: webpack 2.3.2 Time: 7ms Asset Size Chunks Chunk Names bundle.js 12 kB 0 [emitted] app [./app.js] ./app.js 28 bytes {0} [built] + 2 hidden modules
Che rispecchia perfettamente quanto definito. Il nostro entry-point
, app.js, è contenuto nel file bundle.js prodotto in fase di compilazione.
Per maggiori dettagli su package.json, sue parti ed npm, sarà molto utile la lettura del post di Andrea Como, I miei primi passi con Node.js ed Express.js.
Bene, ma la nostra intenzione è quella di creare un progetto in sintassi ES6/7, per cui è necessario arricchire la nostra configurazione, affinché supporti la nuova sintassi e contempli le diverse tipologie di files. Per ottenere questo risultato abbiamo bisogno dei Loaders.
Loaders Webpack
When you encounter this kind of file, do this with it
Un loader è una particolare funzione o processore che elabora il contenuto di una o più risorse statiche, come file Javascript, immagini, fogli di stile, e ci consente di aggiungere funzionalità a webpack.
La Naming Convention per un loader segue la sintassi xxx-loader, come sass-loader oppure babel-loader.
Come anticipato, vogliamo poter scrivere Javascript moderno fino alla versione ES2017, e per farlo abbiamo proprio bisogno del babel-loader, uno tra i più famosi transpiler , il quale ci garantisce la massima compatibilità di ES6/7 con i maggiori browser, compilando il codice scritto, in Standard Vanilla.
I loaders non sono rilasciati con webpack, motivo per il quale dovranno sempre essere installati prima di essere utilizzati, installazione che avviene mediante npm:
npm install xxx-loader --save.
Procediamo con l istallazione del babel-loader e di alcune librerie aggiuntive, necessarie al corretto funzionamento del famoso transpiler;
npm install babel-core babel-loader babel-preset-latest --save
ed aggiorniamo la nostra configurazione come segue:
//webpack.config.js const webpack = require('webpack') const path = require('path') const config = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js', }, output: { path: path.resolve(__dirname, 'build'), publicPath: '/build/', filename: 'bundle.js' }, module: { rules: [{ test: /\.js$/, include: path.resolve(__dirname, 'src'), use: [{ loader: 'babel-loader', options: { presets: [ ['es2015', { modules: false }]//{module:false} abilita il Three Shaking ] } }] }] } }
Questa struttura, rispecchia le nuove API di webpack, che differenziano dalla release precedente su alcuni punti. Analizziamola:
-
module
: oggetto che funge da wrapper, per tutti i loader contenuti nel progetto, definiti all interno dell array di oggettirules
; -
test
: Regular Expression, che indica a webpack, la tipologia di file da includere nella ricerca all interno dell albero delle dipendenze, nel nostro caso file Javascript; -
include
: indica il path dove dovranno essere cercati i file, indicati nella Regular Expression; -
options
: contiene le opzioni per il loader specificato. In questo caso abbiamo definito l opzionepreset
, che descrive il tipo di trasformazione che devono subire i nostri file Javascript. Essa sostituisce il file.babelrc
.
Il codice del nostro progetto è pronto per essere scritto in ECMAScrip6/7, grazie a Babel.
Prima di passare all’ introduzione dei plugin, aggiungiamo un loader per la gestione di file Sass, previa installazione:
npm install sass-loader node-sass css-loader
Ed aggiorniamo il nostro file di configurazione come segue;
const config = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js', }, output: { path: path.resolve(__dirname, 'build'), publicPath: '/build/', filename: '[name].bundle.js' }, module: { rules: [{ test: /\.scss$/, include: path.resolve(__dirname, 'src'), loader: ['css-loader', 'sass-loader'] }, { test: /\.js$/, include: path.resolve(__dirname, 'src'), use: [{ loader: 'babel-loader', options: { presets: [ ['es2015', { modules: false }] ] } }] }] }, } module.exports = config
Plugins
Mentre i loaders operano trasformazioni su singoli file, i plugins operano su grandi parti di codice (larger chunks of code). Sono utilizzati per aggiungere comportamenti addizionali alla normale configurazione di webpack.
Molti sono rilasciati con webpack stesso, come UglifyJsPlugin, per la compressione e contestuale minificazione dei file, CommonChunksPlugin per “innescare” il Code Splitting, che vedremo nei prossimi paragrafi. Ma i plugin aggiuntivi, necessitano di essere installati come i loaders, attraverso il NodeJS Packager Manager, npm. E’ sempre possibile alterarne il comportamento, ma non è oggetto di questo post la sua delucidazione.
Ne analizzeremo ed utilizzeremo solamente alcuni nel corso del post. Iniziamo da subito con l’ Extract Text Plugin.
Extract Text Plugin
Uno dei plugin più utilizzati è l Extract Text Plugin (extract-text-webpack-plugin). Esso ci permette di estrarre in file separati, tutti i moduli di codice contenenti CSS, o particolari preprocessor come il Sass, o Less. Fino a quel momento tutte le caratteristiche di resa sono inline, contenute nei bundle Javascript.
Per prima cosa installiamo il plugin nella consueta forma npm:
npm install extract-text-webpack-plugin --save
Adesso utilizziamolo all interno del file di configurazione come segue:
const ExtractTextPlugin = require('extract-text-webpack-plugin') const extractCSS = new ExtractTextPlugin('[name].bundle.css') const config = { ............}, module: { rules: [{ test: /\.scss$/, include: path.resolve(__dirname, 'src'), loader: extractCSS.extract(['css-loader', 'sass-loader']) }, { ....... }, plugins: [ extractCSS ], } module.exports = config
L’ utilizzo del plugin è molto semiplice ed auto-esplicativa. Una volta dichiarato ed importato per mezzo della funzione require()
, lo utilizziamo all interno del file di configurazione, nella sezione corrispondente ad i loaders.
L’ unica novità che incontriamo in questa configurazione, è l’ aggiunta della struttura dati array plugins
. Questa struttura serve a contenere tutti i plugins utilizzati in un progetto, sia rilasciati con webpack, che installati via npm.
L’ Extract Text Plugin, non è legato semplicemente all esternalizzazione in file di CSS, ma risulta molto utile alle performance dell applicazione. Infatti ogni request CSS, avviene in parallelo ed ognuna con cache separata, migliorando le prestazioni generali.
CommonChunksPlugin
Il CommonChunksPluigin, è un core-plugin, rilasciato con webpack, utilizzato per implementare il Code Splitting. Questa è un opt-in features, utilizzata per dividere, “splittare” il codice in più chunks, caricati opportunamente on demand.
Viene utilizzata anche per estrarre dal codice della nostra applicazione, le dipendenze da librerie di terze parti (vendor), ma gli autori di webpack, nella documentazione ufficiale, sottolineano che l importanza di questa tecnica è fornita principalmente dalla possibilità di avere il caricamento dei moduli on demand e non solo dalla divisione in common chunks delle dipendenze condivise, seppur di grande utilità ed importanza nello sviluppo di un applicazione web.
Ma Procediamo con l’ impementazione.
Fino ad ora, il nostro progetto conteneva un unico entry-point
, di conseguenza il prodotto del processo di build era un singolo bundle, come definito dal oggetto output
. Aggiungiamo un nuovo entry-point
all’ applicazione: meta.js
, ed aggiorniamo la nostra configurazione come segue:
const webpack = require('webpack') const path = require('path') const commonChunks = new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: 'common.bundle.js' }) const config = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js', meta: './meta.js', }, output: { path: path.resolve(__dirname, 'build'), publicPath: '/build/', filename: '[name].bundle.js' }, module: { ............ }, plugins: [ commonChunks ] } module.exports = config
Come si nota facilmente , la proprietà filename dell oggetto output è stata modificata e trasformata in [name].bundle.js.
Il Placeholder [name]
, in fase di compilazione verrà rimpiazzato dal nome dell’ entry-point (app e meta), per cui nel nostro caso verranno generati due file di output distinti : app.bundle.js
e meta.bundle.js
.
Oltre questi due bundle, il CommonChunksPlugin ne genera un terzo, il common.bundle.js
(come specificato nelle sue opzioni), includendo i moduli di codice condivisi tra entrambi i due entry-points, chiamati anche split-points, nel nostro caso, trattasi solo della libreria Javascript Lodash.
Se provassim ora a lanciare il comando per il processo di build, questo sarebbe il risultato:
Hash: 539bf2001d458bf97217 Version: webpack 2.3.3 Time: 2465ms Asset Size Chunks Chunk Names meta.bundle.js 548 bytes 1 [emitted] meta app.bundle.js 689 bytes 2 [emitted] app common.js 73 kB 3 [emitted] common
Il markup finale della nostra applicazione è il seguente:
CodingJam | Webpack Tutorial
Webpack Dev Server
Come già evidenziato nel primo post, webpack-dev-server è rilasciato con un piccolo server NodeJS (Express Server), utilizzato per servire il bundle prodotto. Grazie ad esso, gli sviluppatori possono usufruire del live reloading ed altre features, come L Hot Module Replacement.
Sotto il cofano, utilizza il webpack-dev-middleware, che provvede ad un accesso in memoria ( in-memory access) molto veloce, per attingere il più rapidamente possibile agli assets statici prodotti.
Il più semplice impiego di esso è tramite CLI, oppure è possibile configurarlo all interno del file configurazione webpack.config.js.
Anch’esso come i loaders ed i plugins, deve essere installato per poter essere utilizzato, per cui lanciamo da linea di comando : npm install webpack-dev-server --save
, ed aggiorniamo lo script start
del nostro package.json
, come segue:
"start": "webpack-dev-server --inline --open",
L opzione --open
aprirà in maniera automatica il browser all indirizzo http://localhost:8080 (se cosi non fosse provvedete manualmente :-)). Non rimane che lanciare il comando npm run start
et voilà, ogni semplice modifica al sorgente, o qualsiasi altra risorsa presente nel bundle, verrà avvertita dal server di webpack, che lancerà un nuovo processo di build.
Le modifiche saranno subito visibili senza l utilizzo del refresh, grazie al live reloading.
Qualora si desiderasse utilizzare il file di configurazione al posto delle CLI API,è necessario aggiungere e configurare un nuovo oggetto, il devServer.
Questa è la struttura.
// webpack.config.js ...... devServer: { inline: true, ..... }
Le opzioni disponibile per questo oggetto sono svariate e vi è il corrispettivo comando via CLI. Queste sono solo alcune delle più utilizzate:
-
hot
: abilità l Hot Module Replacement in concomitanza di plugins specifici; -
contentBase
:webpack
servirà le risorse statiche in questo path. In sua mancanza ha la prioritàpublicPath;
-
compress
: se impostato su true abilita la compressione gzip per gli assets prodotti; -
historyApiFallback
: gestisce l accesso al dev-server da url arbitrari, molto utile in caso di SPA con router HTML5; -
clientLogLevel
: controlla il livello di log nella console del browser in presenza dell opzioneinline=true
. Può assumere i valorierror, warning, info, none
; -
proxy
: oggetto che definisce le proprietà del http-proxy-middleware di webpack;
Hot Module Replacement
Terminiamo il post con un discorso introduttivo su l’ HMR, Hot Module Replacement.
E’ un opt-features, simile al live reloading, ma opera ad un livello di accuratezza molto più sottile. Infatti l’ HMR effettua l’ aggiornamento solamente dei moduli che hanno subito una modifica e non dell intero bundle prodotto. Questa caratteristica rende l’ HMR, uno strumento molto più efficace del live reloading.
Per impiegarlo, anche in questo caso vi sono sempre le stesse identiche due possibilità, come potete immaginare: via CLI o file di configurazione.
Nel primo caso dobbiamo aggiungere l opzione --hot
alle opzioni del webpack-dev server
, per cui il nostro start script diventerà:
webpack-dev-server --inline --hot --open
In caso di abilitazione via file di configurazione, bisogna aggiungere l HotModuleReplacementPlugin e configurare l oggetto devServer
, come segue:
const config = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js', meta: './meta.js', }, output: { path: path.resolve(__dirname, 'build'), publicPath: '/build/', filename: '[name].bundle.js' }, module: { ........ }, plugins: [ new webpack.HotModuleReplacementPlugin(), ], devServer: { hot: true, inline: true } } module.exports = config
Conclusioni
In questo post abbiamo visitato molte parti di questo tool, nonostante sia stata visitata solamente la superficie per la moltitudine di proprietà e teorie in merito. Ad oggi webpack viene utilizzato nei migliori Framework in commercio ed anche i più recenti, come Ionic2, Angular 2, angular-cli , Electron e molti altri.
Questo ultimo anno ha visto la notevole crescita di Rollup, con supporto built-in del Tree Shaking, e per alcuni il successore di webpack ed in questi mesi invece, ha raccolto enorme approvazione FuseBox, module loader e module bundler. In questo post potete trovare il benchmark inerente la velocità di building messa a confronto con quella di webpack, e se l argomento è di vostro interesse queste sono due Applicazioni di esempio con Angular2 e React.
Nel prossimo post, vedremo inoltre come utilizzare l http-proxy-middleware di webpack (proxy-server), altra features molto interessante. Per il momento potete trovare il codice del post in questo repository GitHub. Alla prossima…