È uscito il Corso Java Completo
Torna al blog

Cos'è un bundler e perché serve

Cos'è un bundler e cosa fa davvero: risoluzione dei moduli, transpilazione, minificazione, code splitting e tree shaking. Da Webpack a Vite, spiegato.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura

Scrivi il codice in venti file ordinati, lanci la build e nella cartella dist ti ritrovi tre file con nomi incomprensibili e tutto su una riga sola. In mezzo è successo qualcosa, e quel qualcosa si chiama bundler. In questo articolo trovi perché questi strumenti sono nati, cosa fanno oggi davvero — tree shaking e code splitting compresi — e la risposta onesta alla domanda se nel 2026 ti serva ancora configurarne uno a mano.

Cos'è un bundler

Un bundler è lo strumento che prende tutti i file del tuo progetto — JavaScript, CSS, immagini — segue i collegamenti tra loro e produce i pochi file ottimizzati che il browser scaricherà davvero.

È il passaggio tra il codice organizzato come piace a te e il codice organizzato come conviene al browser. Le due cose non coincidono quasi mai, ed è tutto qui il motivo per cui questi strumenti esistono.

Il problema storico da cui nasce tutto

Per capire perché serve, bisogna tornare a quando il browser non sapeva cosa fosse un modulo.

Fino a pochi anni fa, l'unico modo per caricare JavaScript in una pagina era una sequenza di tag <script>. Ogni file finiva nello stesso spazio globale, l'ordine contava, e non esisteva modo per un file di dichiarare di cosa avesse bisogno.

<!-- L'ordine è la tua unica forma di gestione delle dipendenze -->
<script src="jquery.js"></script>
<script src="utils.js"></script>
<script src="app.js"></script>

Con dieci file era scomodo, con duecento era ingestibile. E ogni tag era una richiesta HTTP in più: su connessioni lente, duecento richieste piccole erano molto peggio di una richiesta grande.

La soluzione fu la più ovvia: prendere tutti i file e concatenarli in uno solo, nell'ordine corretto, prima di metterlo online. È letteralmente questo il significato della parola "bundle": un pacchetto.

Intanto Node.js aveva introdotto un sistema di moduli vero (require), e chi scriveva per il browser lo voleva anche lì. Il bundler è diventato il traduttore: tu scrivi con i moduli, lui produce qualcosa che il browser del 2015 poteva eseguire. Se il contesto di Node ti manca, l'articolo di riferimento è cos'è Node.js.

Cosa fa un bundler oggi

Il compito si è allargato parecchio. Oggi un bundler fa cinque cose, e vale la pena distinguerle perché spesso vengono confuse tra loro.

Risoluzione dei moduli

Parte da un file di ingresso, legge ogni import, capisce a quale file corrisponde e ripete il procedimento in profondità. Il risultato è il grafo delle dipendenze: la mappa completa di cosa serve a cosa.

// src/main.js
import { formatta } from "./utils/date.js";
import Bottone from "./components/Bottone.jsx";
import "./stili.css";

Notare l'ultima riga: importare un file CSS da JavaScript non ha alcun senso per il browser. È il bundler a darglielo, e questa capacità di trattare qualunque risorsa come un modulo è ciò che ha reso Webpack dominante per anni.

Transpilazione

Il codice che scrivi non è quello che il browser capisce. JSX non è JavaScript valido, TypeScript nemmeno, e alcune sintassi recenti non sono supportate ovunque. La transpilazione converte il tuo sorgente in JavaScript eseguibile, e oggi è delegata a strumenti dedicati (Babel, esbuild, SWC) che il bundler orchestra.

Minificazione

Toglie tutto quello che serve a te ma non alla macchina: spazi, a capo, commenti, e accorcia i nomi delle variabili interne. Un file da 300 KB scende comodamente sotto i 100 KB, e con la compressione del server ancora di più.

Code splitting

Qui si passa dalle cose ovvie a quelle che contano.

Concatenare tutto in un file solo funziona finché il file è piccolo. Su un'applicazione vera diventa un blocco da un megabyte da scaricare ed eseguire prima di vedere qualunque cosa — anche se poi userai tre schermate su venti.

Il code splitting spezza il pacchetto in più pezzi caricati solo quando servono davvero. Il meccanismo è l'import dinamico:

// Statico: finisce nel pacchetto iniziale, sempre
import { generaPdf } from "./pdf.js";

// Dinamico: diventa un file separato, scaricato solo qui
async function esporta() {
  const { generaPdf } = await import("./pdf.js");
  generaPdf(dati);
}

Quella libreria PDF da 200 KB la scaricano solo le persone che premono "Esporta". Tutte le altre non la vedono nemmeno. Nei framework la stessa cosa avviene automaticamente per pagina: cambi rotta e arriva il codice di quella rotta, non di tutte.

È una delle leve più efficaci sui tempi di caricamento, e si vede direttamente nelle metriche di cui parlo in Web Core Vitals.

Tree shaking

L'altro concetto che conta davvero, e il nome aiuta: scuoti l'albero delle dipendenze e cade tutto ciò che nessuno usa.

// utils.js esporta tre funzioni
export function somma(a, b) { return a + b; }
export function sottrai(a, b) { return a - b; }
export function calcoloEnorme() { /* 400 righe mai usate */ }

// main.js ne usa una
import { somma } from "./utils.js";

Nel file finale finisce solo somma. Le altre due vengono eliminate perché il bundler, analizzando gli import staticamente, può dimostrare che nessuno le richiama.

E qui arriva la parte che fa la differenza in pratica: il tree shaking funziona solo con gli ES Modules (import/export), non con require. Il motivo è che import è statico e analizzabile prima dell'esecuzione, mentre require può stare dentro un if e dipendere da una variabile: nessuno può sapere in anticipo cosa verrà caricato.

Conseguenza concreta sulle librerie che installi:

// Importa l'intera libreria: il tree shaking non può fare nulla
import _ from "lodash";

// Importa solo ciò che serve
import debounce from "lodash/debounce";

Un secondo ostacolo sono gli effetti collaterali: se un modulo, solo per essere importato, modifica qualcosa di globale, il bundler non può rimuoverlo in sicurezza anche se non ne usi le esportazioni. È a questo che serve il campo "sideEffects": false nel package.json di una libreria.

Da Webpack a Vite: cosa è cambiato

Per quasi dieci anni Webpack è stato lo standard, e il suo webpack.config.js una specie di rito di passaggio. Funzionava, ma con un difetto che cresceva insieme ai progetti: in sviluppo, prima di poter aprire il browser, doveva costruire l'intero pacchetto. Su un progetto grande erano decine di secondi all'avvio, e un'attesa a ogni modifica.

Poi è cambiata una cosa fuori dai bundler: i browser hanno imparato a gestire i moduli ES nativamente.

<script type="module" src="/src/main.js"></script>

Con questa riga il browser legge il file, vede gli import, e richiede da solo i file collegati. Esattamente il lavoro per cui erano nati i bundler — ma fatto dal browser, gratis.

Il salto logico è quello che ha fatto Vite: in sviluppo non impacchettare niente. Serve i file al browser quasi come sono, trasformando solo quello che il browser richiede davvero. L'avvio diventa istantaneo, e non peggiora quando il progetto cresce.

In produzione, invece, il pacchetto serve ancora: là contano il numero di richieste, la minificazione e il tree shaking. Quindi Vite impacchetta eccome, con Rollup. Questa doppia natura è il punto che confonde di più, ed è spiegata in cos'è Vite.

Approccio classico (Webpack)Approccio moderno (Vite)
Avvio in sviluppoCostruisce tutto, poi apreApre subito, trasforma su richiesta
Effetto della crescitaL'attesa aumenta col progettoResta pressoché costante
Aggiornamento a caldoRicalcola una porzione del grafoSostituisce il singolo modulo
Build di produzionePacchetto ottimizzatoPacchetto ottimizzato
Configurazione tipicaLunga ed esplicitaBreve, molto è predefinito

Una precisazione onesta: Webpack non è morto e non è sbagliato. Regge configurazioni complesse che altri strumenti non affrontano, e migliaia di progetti in produzione ci girano benissimo. Semplicemente, per un progetto nuovo oggi non è più la scelta predefinita.

Cosa fa perdere ore

  • Confondere transpilazione e polyfill. La prima traduce la sintassi, il secondo aggiunge funzionalità mancanti a runtime. Il codice si compila senza errori e poi esplode sul browser vecchio del cliente: la sintassi era stata tradotta, il metodo che non esiste no.
  • Importare intere librerie per una funzione. Il caso classico è la libreria di icone importata tutta per usarne quattro. Guarda cosa finisce nel pacchetto finale con un analizzatore prima di cercare ottimizzazioni altrove.
  • Mettere variabili sensibili nel codice del frontend. Tutto ciò che il bundler impacchetta è pubblico, sempre. Una chiave API scritta lì è leggibile da chiunque apra gli strumenti di sviluppo. Il tema è in cosa sono le variabili d'ambiente.
  • Ottimizzare il pacchetto quando il problema sono le immagini. Ore passate a ridurre 40 KB di JavaScript su una pagina che carica 4 MB di foto: prima misura, poi decidi — ottimizzare le immagini di solito rende molto di più.
  • Il code splitting con percorsi costruiti a runtime. import(variabile) non dà al bundler modo di sapere in anticipo cosa creare: il percorso dev'essere almeno parzialmente letterale.

Nel 2026 devi configurarlo a mano?

La risposta onesta: quasi mai, e va benissimo così.

Se usi Next.js, Astro, SvelteKit o Nuxt, il bundler c'è ma è già configurato, e con impostazioni migliori di quelle che sceglieresti da solo. Code splitting per pagina, tree shaking, hash nei nomi dei file per il caching: tutto acceso dal primo giorno. Con npm create vite, idem.

I casi in cui metterci le mani ha senso sono pochi: una libreria da pubblicare, un progetto vecchio da mantenere, un'integrazione che nessun preset copre, o un pacchetto finale troppo grande da indagare.

Capire cosa fa un bundler serve per il debug, non per configurarlo. Quando la build funziona in locale e si rompe online, quando una variabile d'ambiente risulta vuota, quando il pacchetto triplica dopo una nuova libreria: in quei momenti sapere che esiste un grafo delle dipendenze, che il tree shaking ha bisogno degli ES Modules e che dev e build sono due percorsi diversi ti fa risparmiare un pomeriggio. Configurare da zero, no.

In sintesi

Un bundler segue i collegamenti tra i file del progetto e produce i pochi file ottimizzati che finiscono online.

È nato perché il browser non sapeva gestire i moduli e ogni file era una richiesta HTTP in più. Quel problema oggi è in gran parte risolto dal browser stesso.

I due concetti che contano per le prestazioni sono il tree shaking — elimina il codice che nessuno usa, ma funziona solo con gli ES Modules — e il code splitting, che spezza il pacchetto in pezzi caricati su richiesta.

Il passaggio da Webpack a Vite ha una causa precisa: i moduli ES nativi hanno reso inutile impacchettare in sviluppo. In produzione il pacchetto serve ancora.

E la parte onesta: oggi il bundler lo configura il tuo framework, e va bene così. Sapere cosa fa serve quando qualcosa si rompe.

Se vuoi il dettaglio sullo strumento che oggi è lo standard, vedi cos'è Vite. Per la differenza tra gli strumenti che installi e quelli che ti danno la struttura, vedi cos'è una libreria e cos'è un framework.