Vai al contenuto

I Module bundler in Javascript

JavaScript è cambiato molto negli ultimi anni. Sono finiti i giorni in cui dovevamo includere manualmente jQuery, Bootstrap e React in ogni pagina web del nostro sito. Al giorno d’oggi, possiamo raggruppare tutto in un unico file statico che possiamo caricare con una sola riga di codice.

I Module Bundler sono il modo per organizzare e combinare molti file di codice JavaScript in un unico file. Un bundler JavaScript può essere utilizzato quando un progetto diventa troppo grande o quando lavoriamo con librerie che hanno più dipendenze. Una caratteristica fantastica di un bundler è che genera un grafico delle dipendenze mentre analizza i file JS. A partire dal punto di ingresso specificato, il bundler del modulo tiene traccia sia delle dipendenze dei file di origine che delle dipendenze di terze parti. Questo grafico delle dipendenze garantisce che tutti i file di codice sorgente e associati siano aggiornati e privi di errori.

Quant’era complicata la procedura prima dei bundler? Mantenere tutti i file e le loro dipendenze aggiornati è da sempre un compito oneroso per gli sviluppatori web.

Facciamo un esempio: supponiamo di avere un’app JavaScript CRUD di base (crea, leggi, aggiorna ed elimina) come una lista della spesa. Nell’era pre-bundler, potevamo progettare la nostra app in modo da avere separate le funzioni CRUD in più file JS separati. Inoltre, come quasi sempre accadeva, per rendere più dinamica e accattivante la nostra app, dovevamo includere librerie di terze parti: questo richiedeva al browser di fare diverse query al momento del caricamento.

Con un Module Bundler tutto questo non serve più. Supponiamo di voler sviluppare un’app di grandi dimensioni come un sito di e-commerce che fornisce l’accesso a migliaia di prodotti a diversi utenti. Per un caso d’uso come questo, molto probabilmente siamo “costretti” ad utilizzare librerie personalizzate o di terze parti per potenziare la UX su alcune procedure più complesse. In tal caso, utilizzando un Module Bundler il processo di sviluppo viene allegerito, in quanto è proprio il Module Bundler a mantenere tutte le dipendenze aggiornate all’ultima versione.

Oltre a fornire uno strumento che ci salva dal dolore delle dipendenze, molti popolari bundler di moduli sono dotati anche di funzionalità di ottimizzazione delle prestazioni come la divisione del codice e la sostituzione del modulo a caldo. I bundler JavaScript dispongono inoltre di funzionalità che migliorano la produttività, quale ad esempio un report dettagliato degli errori, che consente agli sviluppatori di eseguire facilmente il debug e sistemare eventuali bug.

Come funziona un bundler?

Nel complesso, l’operazione di un bundler è divisa in due fasi: generazione del grafico delle dipendenze ed eventuale raggruppamento.

Mappatura del grafico delle dipendenze

La prima cosa che fa un Module Bundler è generare una mappa delle relazioni di tutti i file serviti. Questo processo è chiamato risoluzione delle dipendenze. Per fare ciò, il bundler richiede un file di ingresso che dovrebbe idealmente essere il file principale, il quale analizzandolo dovrebbe riuscire a comprenderne le dipendenze. Ad ogni file che “vede” durante questo processo, viene assegnato in ID univoco, ed infine, estrae tutte le dipendenze e genera un grafico delle dipendenze che rappresenta la relazione tra tutti i file.

Questo processo è necessario per tre motivi:

  • Consente al modulo di costruire un ordine di dipendenza, vitale per il recupero di funzioni quando un browser le richiede

  • Previene i conflitti di denominazione poiché il bundler JS ha una buona mappa di origine di tutti i file e delle loro dipendenze

  • Rileva i file inutilizzati permettendoci di sbarazzarci dei file non necessari

Raggruppamento

Dopo aver ricevuto gli input e aver attraversato le dipendenze durante la fase di risoluzione, il bundler fornisce risorse statiche che il browser può elaborare correttamente. Questa fase di output è chiamata Packing . Durante questo processo, il bundler sfrutterà il grafico delle dipendenze per integrare i nostri file multipli e restituire un singolo bundle che il browser può caricare correttamente.

I Module Bundler più utilizzati

Webpack

Webpack è attualmente il bundler di moduli JavaScript più popolare. In quanto bundler di moduli statici, viene fornito con una serie di funzionalità sofisticate e altamente personalizzabili, che lo rendono un bundler, un trasformatore, minificatore e ottimizzatore di tutti i tipi di risorse e risorse di file. Webpack ha anche un ecosistema di plugin e loaders molto ricco.

Come tutti i moderni bundler JavaScript, Webpack inizia il processo di raggruppamento assemblando un grafico delle dipendenze. Per capire come esegue la fase di risoluzione delle dipendenze, bisogna comprendere sei concetti chiave:

  • Entry: specifica dove Webpack dovrebbe iniziare il suo grafico delle dipendenze. Possiamo avere uno o più punti di ingresso a seconda dell’architettura della nostra app. Webpack scorre i moduli elencati nel file di configurazione webpack.config.js identificando le dipendenze dirette e indirette del punto di ingresso.

    module.exports = {
      entry: './path/to/my/entry/file.js',
    };
  • Output: specifica la destinazione desiderata per l’output finale dopo che Webpack ha completato il processo di impacchettamento. La proprietà Output contiene due valori secondari: path, cioè il percorso del file, in genere la cartella /dist, e filename.

    const path = require('path');
    
    module.exports = {
      entry: './path/to/my/entry/file.js',
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'my-first-webpack.bundle.js',
      },
    };
  • Caricatori: consentono al webpack di elaborare altri tipi di file e convertirli in moduli validi che possono essere consumati dall’applicazione e aggiunti al grafico delle dipendenze. È importante ricordare che quando si definiscono le regole nella configurazione del webpack, le si definisce sotto module.rules. I caricatori hanno due proprietà nella configurazione del webpack:

    1. La proprietà test identifica il file o i file da trasformare.
    2. La proprietà use indica quale caricatore deve essere utilizzato per eseguire la trasformazione.
    const path = require('path');
    
    module.exports = {
      output: {
        filename: 'my-first-webpack.bundle.js',
      },
      module: {
        rules: [{ test: /\.txt$/, use: 'raw-loader' }],
      },
    };
  • Plugin: mentre i caricatori vengono utilizzati per trasformare determinati tipi di moduli, i plug-in possono essere sfruttati per eseguire una gamma più ampia di attività come l’ottimizzazione dei bundle, la gestione delle risorse e l’inserimento di variabili ambientali.
  • Modalità: consente a Webpack di configurare dinamicamente le proprie operazioni in modalità di produzione o sviluppo.

  • Compatibilità browser: consente a Webpack di creare bundle che supportano browser moderni e vecchi con funzionalità come promesse e polyfill.

Dopo aver creato la mappa dei moduli interni, Webpack utilizza quindi le funzioni per avvolgere i moduli associati raggruppandoli tutti insieme per essere richiamati da una singola funzione di runtime chiamata webpack.

Pro e contro

Oltre a fornire supporto immediato per i file JS, Webpack ha un ricco ecosistema di plug-in per raggruppare altri file come CSS e immagini.

La suddivisione del codice consente di suddividere i file di codice in blocchi, riducendo così i tempi di caricamento. Con la sostituzione del modulo “a caldo” possiamo gestire i moduli senza ricaricare completamente il browser. Possiamo utilizzare i programmi di caricamento per pre-elaborare i nostri file, ottenendo un runtime dell’app più rapido.

Possiede un’ampia documentazione e un solido ecosistema di strumenti di terze parti su cui puoi fare affidamento. Inoltre ha un sistema di memorizzazione nella cache interno che consente agli sviluppatori di creare app in breve tempo.

Di contro, la raffinatezza di Webpack è un’arma a doppio taglio per molti sviluppatori. Inoltre è complesso e ha una ripida curva di apprendimento. A volte si può andare incontro a over-engineering e  l’eccessivo utilizzo di plug-in per eseguire funzioni semplici può causare il rallentamento del bundler che richiede quindi un debug tecnico per renderlo ben ottimizzato.

Parcel

Parcel è un complilatore plug-and-play, a configurazione zero, che consente agli sviluppatori di configurare rapidamente i moduli multi-asset (ad es. JS, CSS e HTML) necessari per lo sviluppo. Ha oltre 39.000 stelle su Github, rendendolo il secondo bundler JS più popolare dietro Webpack.

Il processo di raggruppamento dei pacchetti in Parcel prevede tre fasi:

  • Costruzione dell’asset tree: in questa fase, Parcel prende un asset come punto di ingresso e attraversa il file per identificare le dipendenze utilizzate per creare un albero di asset simile al grafico delle dipendenze.

  • Costruzione dell’albero dei bundle: qui, le risorse presenti nell’albero delle risorse vengono combinate con le loro dipendenze per formare un albero dei bundle.

  • Impacchettamento: questa è la fase finale in cui ogni pacchetto sull’albero dei pacchetti viene associato ai suoi specifici tipi di file del pacchetto e trasformato in un file compilato.

Per iniziare con Parcel, occorre digitale da una shell il seguente comando.

npm i parcel

Supponiamo di avere un codice standard HTML:

<code class="language-html"><!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>My First Parcel App</title>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

È quindi possibile utilizzare Parcel per creare il file HTML eseguendo semplicemente:

parcel src/index.html

Parcel ha un server di sviluppo integrato, che ricostruirà automaticamente la nostra app man mano che apportiamo delle modifiche.  Questo significa che possiamo iniziare ad aggiungere dipendenze al nostro  file HTML, come un file JavaScript o CSS. Ad esempio, possiamo creare un file styles.css e farvi riferimento dall’index.html con un tag  <link> e un file app.js e farvi riferimento con un tag <script>.

//src/styles.css:
h1 {
  color: hotpink;
  font-family: cursive;
}

//src/app.js:
console.log('Hello world!');

//src/index.html:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>My First Parcel App</title>
    <link rel="stylesheet" href="styles.css" />
    <script type="module" src="app.js"></script>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Man mano che apportiamo modifiche, dovremmo riuscire a vedere la nostra app aggiornarsi automaticamente nel browser senza nemmeno aggiornare la pagina!

Oltre a eseguire Parcel tramite la CLI, può essere utile creare alcuni script nel file package.json per semplificare l’operazione. Imposteremo anche uno script per creare l’app per la produzione utilizzando il comando parcel build. Infine, possiamo dichiarare le voci in un unico posto utilizzando il campo source in modo da non doverle duplicare in ogni comando parcel.

//pacchetto.json:
{
  "name": "my-project",
  "source": "src/index.html",
  "scripts": {
    "start": "parcel",
    "build": "parcel build"
  },
  "devDependencies": {
    "parcel": "latest"
  }
}

Pro e contro

Parcel risolve i problemi di configurazione affrontati con Webpack fornendo agli sviluppatori un’architettura performante per un rapido sviluppo web. C’è anche il supporto multi-asset come Webpack che abilita i bundle per tutti i tipi di risorse non JavaScript come CSS, HTML e immagini. Fornisce rapidamente funzionalità di ottimizzazione delle risorse premium come la sostituzione del modulo “a caldo” e il caricamento lento del codice suddiviso. Secondo i benchmark più recenti, la velocità di raggruppamento di Parcel è di 9,98 secondi, rispetto ai 22,98 di Browserify e ai 20,71 di Webpack. L’utilizzo della tecnica di memorizzazione nella cache integrata di Parcel può persino fornire risultati più rapidi, con un tempo di riferimento di 2,64 secondi.

Parcel è perfetta per applicazioni di piccole e medie dimensioni, di contro però, in applicazioni complesse in cui è necessario modificare le configurazioni potrebbe essere noioso. In questa situazione, la maggior parte degli sviluppatori preferisce utilizzare Webpack.

Vite.js

Vite.js (pronunciato vit, alla francese) è uno strumento di creazione di frontend open source di nuova generazione. Evan You ha creato Vite.js nel 2020 per migliorare l’ecosistema dei bundle sfruttando gli ultimi miglioramenti dei moduli ES e risolvere alcuni dei problemi di prestazioni nei build dei precedenti bundler.  Attualmente, Vite.js ha oltre 33,9k stelle su Github e ha oltre 340.000 download ogni settimana. Vite ovviamente richiede Node.js.

Fondamentalmente, Vite fa principalmente due cose:

  1. Fa da server di sviluppo
  2. Raggruppa il codice e le risorse per la produzione

Ma questi sono principalmente ciò che fanno altri bundler JavaScript come Webpack, Parcel e Rollup. Allora perché Vite?

In passato il linguaggio JavaScript non aveva un sistema di moduli standard, quindi gli sviluppatori Web non avevano un modo nativo per suddividere il codice JavaScript in moduli per essere esportati e importati quando necessario. E questo ha portato allo sviluppo di diversi sistemi di moduli non standard per JavaScript come CommonJS – CJS e e il modulo asincrono – AMD. Da li sono appunto nati strumenti come Webpack che supporta questi sistemi di moduli e concatena più file e risorse javascript in un unico pacchetto.

Ma mentre Webpack è eccezionale e funziona bene, il processo di compilazione diventa sempre più lento man mano che l’app aggiunge più codice e dipendenze. Con l’aggiunta del modulo ES – ESM a JavaScript nel 2015, il linguaggio JavaScript ha ora un sistema di moduli standardizzato che viene eseguito in modo nativo nel browser. Dal 2020 i moduli ES sono quasi universalmente supportati da tutti i browser.

Vite utilizza ESM nativo per raggruppare file JavaScript e risorse dell’applicazione. Questo consente a Vite di caricare istantaneamente il codice, indipendentemente dalle dimensioni del file.

Vite utilizza Rollup per la build di produzione e presenta una configurazione Rollup ottimizzata pronta all’uso. Inoltre, utilizza esbuild per il pre-raggruppamento delle dipendenze. E questo porta a significativi miglioramenti delle prestazioni. In poche parole, Vite è uno strumento di compilazione JavaScript di nuova generazione che sfrutta le moderne API e strumenti JavaScript per semplificare e velocizzare il processo di compilazione. Vite è completamente tipizzato e viene fornito con molte funzionalità avanzate e ricche come la sostituzione del modulo Hot: HMR, supporto per plug-in universali, avvio istantaneo del server, supporto pronto all’uso per TypeScript, JSX, CSS e tanto altro…

Pro e contro

Sfruttando il sistema di moduli Native ES6, Vite.js può servire il codice dell’applicazione più velocemente riducendo il numero di richieste del browser. Inoltre viene fornito anche con Hot Module Replacement (HMR), rendendo la modifica un processo più rapido e quasi istantaneo. Vite.js è indipendente dal framework con supporto predefinito per molti framework Javascript popolari come React.js, Vue.js, Typescript e Preact. Le versioni recenti hanno anche integrato il supporto per moduli CSS, pre-processori e altre risorse statiche. Ha anche un ricco ecosistema di plug-in che sfrutta altri bundler come gli ecosistemi di plug-in esbuild e Rollup per fornire agli sviluppatori un ampio set di opzioni.

Di contro, Vite.js fa molto affidamento sul sistema ESM nativo del browser per produrre la velocità strabiliante per cui è noto. Ciò significa che gli sviluppatori potrebbero incorrere in problemi quando si tratta di browser meno recenti che non supportano questi aggiornamenti.