Hai mai aperto un progetto JavaScript e trovato un unico file script.js lungo duemila righe? Oppure decine di <script> sparsi nell'HTML con l'ordine che sembra un rompicapo? Lo vediamo spesso nei siti che ci arrivano per un restyling: codice monolitico, variabili globali che si pestano i piedi, e pagine che caricano tutto in blocco anche quando servono solo due funzioni. Il risultato? Siti lenti da scaricare, manutenzione da incubo, e sviluppatori che perdono ore a cercare "dov'è definita quella funzione?".
I moduli ES (ECMAScript Modules) risolvono questi problemi alla radice. Niente più concatenamento manuale, niente più namespace globali sporcati, niente più bundle che include anche la parte di amministrazione che l'utente non vedrà mai. Con import ed export puoi scrivere codice modulare, mantenibile, e — con il lazy loading — caricare solo quello che serve, quando serve. Noi, di Meteora Web, li usiamo da anni nei nostri progetti con Laravel e Vue, e ogni volta che torniamo a un vecchio sito senza moduli ci ricordiamo perché sono indispensabili.
Perché usare i moduli ES invece dei vecchi script concatenati?
Prima dei moduli ES, l'organizzazione del codice JavaScript era un problema. Si usavano pattern come IIFE (Immediately Invoked Function Expression) per creare scope privati, oppure librerie come RequireJS o CommonJS (per Node.js). Ma nel browser tutto finiva in un unico spazio globale. Due librerie diverse con la stessa variabile? Errore silenzioso.
I moduli ES portano tre vantaggi concreti:
Sponsored Protocol
- Scope isolato — Ogni modulo ha il suo contesto. Niente variabili globali accidentali.
- Dichiarazioni esplicite — Ogni file dichiara cosa esporta e cosa importa. Leggendo l'intestazione capisci subito le dipendenze.
- Caricamento asincrono nativo — Il browser può caricare i moduli in parallelo, senza bloccare il rendering. Abbinato al lazy loading, riduce il peso della pagina iniziale.
Esempio concreto: Un cliente e-commerce aveva un file checkout.js da 400 KB che includeva tutto: carrello, validazione, gestione pagamenti, mappa delle spedizioni. Con i moduli abbiamo spezzato in 4 file: solo il carrello viene caricato subito, il resto si carica quando serve. La prima interazione è passata da 4 secondi a 1.2 secondi.
Cosa fare subito
Se stai partendo da zero, usa l'attributo type="module" nel tuo tag <script>:
<script type="module" src="main.js"></script>
Il browser tratterà main.js come un modulo. Puoi già usare import ed export al suo interno. Supportato da tutti i browser moderni (Chrome, Firefox, Safari, Edge).
Come funzionano import ed export nei moduli JavaScript?
Un modulo ES è semplicemente un file .js con una delle due direttive: export rende disponibile una funzione, variabile o classe ad altri moduli; import la consuma.
Export nominati vs default
Puoi esportare in due modi:
- Nominati (named): più esportazioni per modulo, ideali per librerie.
- Default: uno per modulo, utile per componenti singoli.
Esempio: Crea un file utils.js che esporta due funzioni:
// utils.js
export function somma(a, b) {
return a + b;
}
export function formattaPrezzo(valore) {
return `€ ${valore.toFixed(2)}`;
}
export default function saluta(nome) {
return `Ciao, ${nome}!`;
}
Ora in main.js importiamo solo quello che ci serve:
Sponsored Protocol
// main.js
import saluta, { somma, formattaPrezzo } from './utils.js';
console.log(somma(5, 3)); // 8
console.log(formattaPrezzo(19.99)); // "€ 19.99"
console.log(saluta('Marco')); // "Ciao, Marco!"
Nota: l'import di default non ha bisogno di parentesi graffe, mentre i nominati sì. L'ordine è arbitrario.
Path relativi e assoluti
I moduli usano sempre URL. Per file locali, usa path relativi che iniziano con ./ o ../. Per moduli da CDN, usa URL completi:
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
Non serve installare nulla: il browser scarica il modulo direttamente.
Re-export per organizzare librerie
Puoi raggruppare esportazioni da più moduli in un unico file indice:
// lib/index.js
export { somma, formattaPrezzo } from './utils.js';
export { default as saluta } from './utils.js';
Poi nel codice importi da './lib/index.js' invece che dai singoli file. Semplifica le dipendenze.
Come gestire il caricamento asincrono con lazy loading?
Il vero vantaggio dei moduli ES è la possibilità di importare codice su richiesta, senza bloccarlo all'avvio. Si chiama lazy loading (o code splitting) e si realizza con import() dinamico.
Import dinamico: import()
A differenza degli import statici (che vengono risolti al caricamento), import() è una funzione che restituisce una Promise. Puoi chiamarla quando vuoi, ad esempio quando l'utente clicca un pulsante o scorre fino a un certo punto.
Sponsored Protocol
// main.js
const bottone = document.getElementById('carica-mappa');
bottone.addEventListener('click', async () => {
const modulo = await import('./mappa.js');
modulo.inizializzaMappa();
});
Il browser scarica mappa.js solo al click. Prima non esiste nella cache né viene eseguito.
Quando usare il lazy loading
- Componenti pesanti: librerie di grafici, mappe, editor ricchi.
- Pagine a rotte (single-page app): carica il codice della sezione solo quando l'utente vi naviga.
- Funzionalità amministrative: il 90% degli utenti non le vede mai, non carichiamole subito.
Esempio concreto: Un nostro cliente ha un gestionale con modulo fatture. L'export in PDF usava jsPDF (150 KB). Con import dinamico attivato al click "Scarica PDF": la pagina principale è passata da 350 KB a 200 KB, e il tempo di interazione è calato del 40%.
Errori comuni con il lazy loading
- Dimenticare
await:import()restituisce una Promise. Se non aspetti il risultato, il codice che usa il modulo viene eseguito prima che sia caricato. - Path errati: il path dinamico è valutato al runtime, assicurati che sia accessibile dal server.
- Overhead eccessivo: non lazy-loadare moduli piccolissimi (es. una funzione di 2 righe). Il costo della richiesta HTTP supera il beneficio.
Quali sono gli errori comuni con i moduli ES e come evitarli?
Anche con un sistema pulito, puoi incappare in trappole. Ecco quelle che vediamo più spesso nei progetti che revisioniamo.
Sponsored Protocol
Moduli caricati due volte
Se importi staticamente e dinamicamente lo stesso modulo, il browser lo esegue una sola volta perché i moduli ES sono singleton. Ma attenzione: se usi path diversi (es. './utils.js' vs './Utils.js'), per il browser sono due moduli diversi. Usa sempre lo stesso case.
Dimenticare type="module"
Se metti <script src="main.js"> senza l'attributo, import e export generano un errore. Il browser li legge come script normali, dove import non è riconosciuto.
CORS nei moduli locali
I moduli ES richiedono che il server invii l'header Content-Type: application/javascript e che le richieste siano gestite tramite protocollo HTTP (non file://). Durante lo sviluppo locale, usa un server come npx serve o php -S localhost:8000.
# Avvia un semplice server HTTP nella cartella del progetto
python3 -m http.server 8000
Poi apri http://localhost:8000 nel browser.
Moduli e vecchi browser
Se devi supportare Internet Explorer o browser molto datati, i moduli ES nativi non funzionano. Soluzioni:
- Usa un bundler (Webpack, Vite, Rollup) che produce script compatibili.
- Combina
type="module"connomoduleper fornire un fallback.
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>
Come misurare l'impatto delle performance con i moduli?
Non basta implementare i moduli: devi verificare che funzionino. Usa gli strumenti di sviluppo del browser.
- Network tab: controlla che i moduli siano caricati con codice 200 e che il lazy loading avvenga solo quando interagisci.
- Coverage tab (Chrome): mostra quanto codice è stato effettivamente eseguito. Obiettivo: sotto il 50% per la prima interazione.
- Lighthouse: la metrica “Total Blocking Time” e “First Contentful Paint” migliorano quando riduci il codice inutilizzato.
Noi, di Meteora Web, integriamo sempre un piccolo test: dopo il deploy, lanciamo un audit con lighthouse-ci e confrontiamo i numeri prima/dopo. Un cliente con un blog ha visto il TBT passare da 300 ms a 80 ms solo isolando il widget meteo in un modulo lazy.
Sponsored Protocol
Cosa fare adesso
Ecco tre azioni concrete da mettere in pratica oggi stesso:
- Converti un singolo file JS in moduli: prendi il file più grosso del tuo progetto. Dividilo in due o tre file con
exporteimportstatici. Aggiungitype="module"al tag script. Verifica che tutto funzioni. - Aggiungi un import dinamico: individua una funzionalità usata raramente (es. un form di contatto avanzato). Sposta il relativo codice in un modulo separato e caricalo con
import()solo quando l'utente clicca il pulsante. - Misura il risultato: prima e dopo, esegui un audit Lighthouse. Confronta il peso del JavaScript e il TBT. Se non migliora, cerca moduli caricati troppo presto.
I moduli ES non sono una moda: sono il modo nativo del browser per scrivere codice organizzato e performante. Usali oggi, il tuo te del futuro (e i tuoi utenti) ti ringrazieranno. Se vuoi approfondire le ultime novità del linguaggio, parti dalla nostra guida pillar su JavaScript ES2024.