Negli ultimi anni lo sviluppo web è stato travolto dalla rinascita di Javascript: da Node.js e il nuovo ecosistema npm fino a numerosissimi front-end framework e librerie come Angular, Vue e React.

Per usare al meglio questi nuovi strumenti senza intaccare le performance della web-app sono nati Grunt, Gulp, Webpack.

In breve, aiutano ad automatizzare alcune operazioni come ad esempio ridurre la dimensione degli script inviati dal server, minificare i fogli di stile, concatenare file, fino a rendere digeribili i nuovi standard di javascript. Grazie a babel è possibile scrivere javascript ES6, ES7 e ES2017 senza preoccuparci della compatibilità del browser, javascript viene “trasformato” nel buon vecchio ES5 che funziona bene con versioni fino al giurassico.

Non finisce qui, le funzionalità di base sono estendibili grazie ai numerosissimi loader e plugin che aiutano in tutte le fasi di sviluppo fino alla production release.

In particolare Webpack si è imposto negli ultimi 1-2 anni come standard de-facto quando si lavora con React, tanto che poche settimane fa è stata rilasciato la versione 2.

Per cui, vediamo come creare un rapido settaggio per poter sviluppare con React e Javacript fino alla versione ES2017!

Nello specifico useremo yarn package manager per creare il nostro progetto e aggiungere le varie dipendenze ma di seguito scriverò anche lo specifico commando per npm.

Nel caso abbiate problemi con le dipendenze e le varie versioni consiglio l’aggiornamento di npm alla versione 3.10.10.

Potete trovare il progetto nel mio profilo di github ovviamente.

Setup del progetto

Per prima cosa prepariamo la cartella:

mkdir webpack-project

e creiamo il file package.json:

yarn init
npm init

A questo punto iniziamo ad installare i vari packages. Partiamo con webpack:

yarn add webpack --dev

npm:

npm install webpack --save-dev

Tra le tante cose noterete il nuovo file package.json in cui sono riassunte tutte le dipendenze del nostro progetto ma possiamo anche aggiungere altre informazioni come il nome, la versione, il link alla repository in github ecc.

Siccome vogliamo settare un progetto in React, installiamolo:

yarn add react react-dom

npm install react react-dom --save

Abbiamo installato anche react-dom per definire l’entry point per i componenti.

E per il progetto? Che cosa vogliamo ottenere?
Di seguito alcuni punti:

  • Vogliamo poter scrivere Javascript moderno fino alla versione ES2017. Questo significa che dovremo installare babel transpiler e lo specifico loader per webpack. Non è tutto, babel richiede alcuni settaggi che si possono definire in uno specifico file .babelrc oppure nel file package.json. Normalmente preferisco .babelrc.
  • Il file bundle.js risultante conterrà anche il css del progetto per cui installeremo una serie di loader, style-loader e css-loader. Ci tengo a precisare che mischiare javascript e css nello stesso file potrebbe non essere la soluzione ottimale per un progetto ma nel nostro caso va più che bene.

Iniziamo con l’installare babel e i suoi preset:

yarn add babel-core babel-cli babel-loader babel-preset-latest babel-preset-react --dev
npm install babel-core babel-cli babel-loader babel-preset-latest babel-preset-react --save-dev

Wow, più del previsto!

Babel-core l’ha scritto in testa, è il pacchetto principale che ci permette usare babel all’interno del nostro progetto e che legge il file .babelrc con le nostre opzioni preferite.
babel-cli permette di lanciare diversi comandi da riga di comando mentre i restanti sono loader per webpack e presets per babel:
babel-preset-latest raggruppa i preset per ES6, ES7 e ES2017 mentre il preset per react include alcuni plugin che aiutano babel a trasformare la sintassi di React in javascript compatibile con vecchi browsers.

Infine, installiamo i loader per gestire i fogli di stile:

yarn add style-loader css-loader --dev
npm install style-loader css-loader --save-dev

A questo punto possiamo iniziare a creare i file di configurazione!
Partiamo da .babelrc, creiamolo nella cartella del nostro progetto e incolliamo il seguente codice:

{
  "presets": [
    "react",
    ["latest", { "modules": false }]
  ]
}

In pratica vogliamo informare babel dei preset che abbiamo installato e che deve usare, preset-react e preset-latest. Inoltre, per diversi preset è possibile definire anche alcuni parametri come modules per preset-latest. Per chi fosse interessato consiglio la documentazione sul sito di babel.

Configurazione di Webpack

E ora possiamo creare il file di configurazione per webpack, webpack.config.js. Una volta creato incolliamo il seguente codice:

'user strict';

const loaders = require('./webpack-loaders');
const plugins = require('./webpack-plugins');
const PATHS = require('./webpack-paths');


const config = {
  entry: {
    app: PATHS.src
  },
  output: {
    path: PATHS.dist,
    filename: 'bundle.js'
  },
  module: {
    // 'rules' substitutes 'loaders'
    rules: [
      loaders.babel,
      loaders.css
    ]
  },
  plugins: [
    plugins.definePlugin,
    plugins.loaderOptionsPlugin,
    plugins.ugligyPlugin
  ],
  resolve: {
    // no need for the empty string extension anymore
    extensions: ['.js', '.jsx']
  }
};


module.exports = config;

Niente paura, cerchiamo di fare chiarezza. La tipica configurazione altro non è che un oggetto con specifiche proprietà che forniscono istruzioni a webpack. Vediamole:

  • entry: Come dice il nome, webpack necessita di un entry point, un file tipicamente index.js per iniziare il suo lavoro di transpiler. Nel nostro caso il file si trova in PATH.src. Abbiamo richiesto PATH da un altro file webpack-paths.js per una migliore organizzazione del codice. Come vedremo a breve, PATH.src altro non è che la cartella /src, per cui webpack cercherà un file index.js in /src.
  • output: Niente di esoterico, ora che webpack sa da dove partire dobbiamo fornire un nome al bundle che creerà e soprattutto dove! PATH.dist altro non è che la cartella /dist per cui una volta lanciato webpack troveremo il file bundle.js in /dist.
  • module: rules contiene tutti i nostri loader, da babe-loader al css-loader, in pratica aggiungiamo funzionalità a webpack in modo da includere altri file nel nostro bundle.js. Per esempio i fogli di stile.
  • plugins: Webpack accetta dei plugin nella sua configurazione e nel nostro specifico caso vogliamo ridurre la dimensione del bundle.js. I plugin sono richiesti da un altro file per cui li vedremo in seguito.
  • resolve: Infine, resolve definisce i file che webpack analizzerà. Di default file javascript e jsx per React. Tuttavia grazie ai loader possiamo definire altri file come ad esempio i fogli di stile (css).

Sebbene la configurazione è finita dobbiamo creare gli altri file, webpack-paths.js, webpack-loaders.js, webpack-plugins.js.

Iniziamo dal primo, sempre nella root directory del nostro progetto creiamo webpack-paths.js e incolliamo il codice:

const path = require('path');

module.exports = {
  src: path.join(__dirname, 'src'),
  dist: path.join(__dirname, 'dist'),
  css: path.join(__dirname, 'dist/css')
};

Per una migliore organizzazione definiamo delle costanti per cartelle /src e /dist.

Di seguito creiamo il file che esporta i loader della nostra configurazione, webpack-loaders.js:

const PATHS = require('./webpack-paths');

/* to define the loader:
  - 'use' instead of 'loaders'
  - must complete the loader name with -loader
  - preLoaders are now defined like normal loaders but have 'enforce' set to 'pre'
*/

exports.babel = {
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: 'babel-loader'
};

exports.css = {
  test: /\.css$/,
  use: ['style-loader', 'css-loader'],
  include: PATHS.css
};


Ecco i nostri 2 loader, la sintassi è standard:

  • test: con una regular-expression definiamo i file coinvolti.
  • exclude/include: webpack è flessibile nella sua configurazione, possiamo decidere di escludere determinati file o cartelle con exclude oppure includere file o cartelle con include. Nello specifico, non vogliamo che babel-loader tocchi le dipendenze in /node_modules mentre limitiamo a PATHS.css i file per il css-loader.
  • use: chiaramente dobbiamo definire il loader specifico.

Infine creiamo webpack-plugins.js:

const webpack = require('webpack');

exports.definePlugin = new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
});

// Not an option from UglifyJsPlugin anymore
exports.loaderOptionsPlugin = new webpack.LoaderOptionsPlugin({
  minimize: true
});

// source-map is default to false now
exports.ugligyPlugin = new webpack.optimize.UglifyJsPlugin({
  output: {
    comments: false
  },
  compress: {
    warnings: false
  }
});

I plugin che abbiamo definito aiutano nella compressione del bundle.js, nota che accettano parametri per definirne il comportamento.

Ora non ci resta che testare il tutto definendo il nostro entry-point, /src/index.js. Creiamo quindi la cartella e il file e incolliamo il seguente codice:

import React from 'react';
import ReactDOM from 'react-dom';
import '../dist/css/style.css';

const Component = () => <div>Hello World!</div>;

ReactDOM.render(
 <Component />,
 document.getElementById('content')
);

Abbiamo creato un componente che ritorna ‘Hello World!’ (originalissimo) e grazie a react-dom ne facciamo il render all’interno di un div con id content.

Abbiamo anche fatto l’import del foglio di stile, creiamolo insieme con index.html.
In /dist/css iniziamo con style.css e copia e incolla il seguente codice:

div {
  color: red;
  background-color: blue;
}

E’ solo un esempio molto semplice per confermare che il nostro bundle.js effettivamente contiene il css dell’app.

Infine in /dist/ creiamo index.html:

<html>
  <head></head>
  <body>
   <div id="content"></div>
   <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

L’elemento div con id content è dove il nostro componente apparirà come figlio. Testiamo webpack! da linea di comando eseguiamolo:

webpack

e dovremmo trovare il file bundle.js in /dist. Ora, webpack mostra nella linea di comando alcune informazioni riguardo il bundle, tra cui la dimensione.
Attualmente è di 146kb grazie alle minime ottimizzazioni dei plugin.

Per vedere se tutto è andato a buon fine, apriamo index.html nel browser:

 

index.html

Il nostro componente è in bella mostra e il css è stato incluso correttamente nel bundle da webpack.

Ora, proviamo ad eliminare i plugin dalla configurazione nel file webpack.config.js:

'user strict';

const loaders = require('./webpack-loaders');
const PATHS = require('./webpack-paths');


const config = {
  entry: {
    app: PATHS.src
  },
  output: {
    path: PATHS.dist,
    filename: 'bundle.js'
  },
  module: {
    // 'rules' substitutes 'loaders'
    rules: [
      loaders.babel,
      loaders.css,
      loaders.eslint(PATHS.src)
    ]
  },
  resolve: {
    // no need for the empty string extension anymore
    extensions: ['.js', '.jsx']
  }
};


module.exports = config;

Lanciamo di nuovo il comando webpack dal terminale:

webpack

e noterete come il bundle ora pesa 735kb!! Le ottimizzazioni apportate dai plugin riducono di molto la dimensione del bundle, questo è importante per ridurre il caricamento di una pagina web, soprattutto se la connessione ad internet è lenta.
Nel nostro caso non siamo ancora in una situazione ottimale, i quasi 150kb possono essere ulteriormente ridotti ma ci tenevo a farvi vedere le enormi potenzialità di webpack.

Conclusioni

In quest’articolo abbiamo provato webpack 2 e testato come sia facilmente possibile creare un bundle per la parte frontend della nostra prossima app.

Ci siamo soffermati sulle operazioni base, aggiunto qualche loader e plugin, un blog post non può certo catturare tutte le meraviglie di webpack. In ogni caso spero di avervi incuriosito.

Ringrazio TangoDev per l’invito a scrivere sul blog, i ragazzi stanno facendo un ottimo lavoro e sono attenti alle nuove tecnologie per il web.