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.
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 sviluppo | Costruisce tutto, poi apre | Apre subito, trasforma su richiesta |
| Effetto della crescita | L'attesa aumenta col progetto | Resta pressoché costante |
| Aggiornamento a caldo | Ricalcola una porzione del grafo | Sostituisce il singolo modulo |
| Build di produzione | Pacchetto ottimizzato | Pacchetto ottimizzato |
| Configurazione tipica | Lunga ed esplicita | Breve, 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.