Le applicazioni moderne sono complesse e ricche di funzionalità.
Finora, abbiamo scritto il nostro codice JavaScript in un solo script, ma usare un unico file gigante non scala!
Nasce così la necessità di suddividere il codice in piccole unità indipendenti: i moduli.
Un modulo JavaScript è semplicemente un file; ogni script *.js è un modulo.
Un modulo isola il codice con una responsabilità specifica: ad esempio, invece di un unico index.js, organizziamo il codice in più file, ciascuno con uno scopo preciso:
validators.js contiene funzioni per validare i datirecipeService.js contiene funzioni per comunicare con il backendui.js contiene funzioni per rendering e UIindex.js coordina e orchestra gli altri moduliUn modulo solitamente contiene una classe o una libreria di funzioni.
I moduli sono riusabili: possono essere importati in più parti dell’applicazione o in progetti diversi.
Come quasi tutto in JavaScript, ci sono molti modi a nostra disposizione per implementare i moduli.
Per molto tempo, infatti, JavaScript è esistito senza una vera sintassi per i moduli nel linguaggio, portando a soluzioni non standard e frammentate:
Ormai tutti questi sistemi vengono lentamente abbandonati in favore di un nuovo formato standardizzato nel 2015 con ES6: gli ECMAScript modules.
CommonJS è un insieme di standard usati per implementare i moduli in JavaScript, principalmente in ambienti server-side come Node.js. Prima dell’introduzione degli ES6 Modules, CommonJS era il sistema di moduli più diffuso e ampiamente adottato.
La sintassi di CommonJS è semplice e si basa su due parole chiave principali: require() per importare moduli e module.exports per esportarli.
Di fatto, un modulo CommonJS esporta un oggetto, e tutte le funzioni o variabili che vogliamo rendere pubbliche devono essere aggiunte a questo oggetto.
Oggi possiamo considerare CommonJS come un sistema di moduli legacy: è ancora ampiamente usato in Node.js, ma non è più la scelta consigliata per nuovi progetti, vista l’esistenza di un formato di moduli “ufficiale” e standardizzato.
Gli ECMAScript modules (ES Modules o ESM) sono il sistema di moduli standardizzato per JavaScript, introdotto con ES6 (ECMAScript 2015). Sono supportati nativamente in tutti i moderni browser e in Node.js (con alcune configurazioni).
La sintassi degli ES Modules è più pulita e intuitiva rispetto a CommonJS, e si basa sulle seguenti keyword:
export per esportare elementi da un moduloimport e from per importarli in altri moduliLa modalità che abbiamo visto nella slide precedente è chiamata named export. Si può utilizzare anteponendo export a ogni elemento che vogliamo esportare.
Possiamo esportare funzioni, variabili, classi, oggetti:
Possiamo anche esportare tutto in un blocco separatamente alla fine del file:
Nella pratica, un modulo spesso ha un solo elemento principale da esportare, come ad esempio una classe. In questo caso, possiamo usare la default export.
In questo caso, andiamo a frapporre la keyword default tra export e la dichiarazione della funzione. Questo indica che questa è l’esportazione predefinita del modulo.
Possiamo importare la default export senza usare le parentesi graffe, e possiamo darle qualsiasi nome.
Un modulo può avere al massimo una default export. Tecnicamente, potremmo avere sia il default che il named export nello stesso modulo, ma in pratica si tende a non farlo.
Un modulo generalmente usa named export o default export.
A volte è utile rinominare un import per evitare conflitti di nomi o per renderlo più chiaro:
Abbiamo visto che i moduli richiedono una sintassi specifica (import/export) che non è compatibile con i vecchi script. Per usare i moduli nel browser, dobbiamo indicare che il nostro script è un modulo; per farlo, usiamo l’attributo type="module" nel tag <script>.
Il browser recupera ed elabora automaticamente il modulo importato (e i suoi import se necessario), e infine esegue lo script.
Modularizzare il ricettario
Riprendi il ricettario della lezione 6 (applicazione CRUD con localStorage).
Il file script.js che avevamo prodotto è monolitico: contiene validazione, rendering, gestione localStorage tutto insieme.
Suddividere il codice in moduli separati con una responsabilità chiara:
utils/validators.js (valida ricette)services/storage.js (carica e salva localStorage)components/recipeList.js (renderizza lista)components/recipeForm.js (gestisce form)index.js (orchestra tutto)L’applicazione deve mantenere la stessa funzionalità.
Quando il nostro codice ha bisogno di funzionalità già scritte da altri, la riusiamo invece di riscriverla da zero.
Una dipendenza esterna è una libreria JavaScript che installiamo nel progetto e che il nostro codice importa e utilizza.
Esempi:
date-fns)Redux)React, Vue)Yup)Una CDN (Content Delivery Network) è un server pubblico che ospita risorse web statiche di vario tipo.
jsDelivr e cdnjs sono due popolari esempi di CDN che ospitano migliaia di librerie JavaScript.
Possiamo importare una libreria direttamente da CDN riportando l’URL nel tag <script>:
Oppure con ES Modules:
Sono molto pratiche per prototipi veloci o per progetti senza build step.
Tuttavia, per progetti più complessi e scalabili, è consigliabile usare un gestore di pacchetti come NPM, che vedremo tra poco.
Ricettario internazionale
Riprendi il ricettario della lezione 6 e aggiungi un pulsante toggle che permette di passare da sistema metrico a sistema imperiale.
Ogni volta che l’utente clicca il toggle, tutte le misure nella lista delle ricette si aggiornano:
Requisiti:
Output: Toggle funzionante, ricette aggiornate dinamicamente.
Fino ad ora abbiamo eseguito JavaScript solo nel browser.
Node.js, invece, è un runtime che esegue JavaScript fuori dal browser:
Possiamo scaricare Node.js da nodejs.org.
Una volta installato, possiamo verificare le versioni di Node.js e NPM:
NPM (Node Package Manager) è il principale gestore di pacchetti per JavaScript.
package.json e cartella node_modules/Quando usi NPM, tutto parte da package.json: il “manifesto” del progetto.
Crea un progetto nuovo:
dependencies: librerie usate in produzione (finiscono nel bundle finale)
Esempio: convert, axios, date-fns, React, Vue, etc.
devDependencies: strumenti usati solo in sviluppo (non finiscono nel bundle)
Esempio: vite, webpack, eslint, prettier, etc.
Perché la distinzione? Quando fai il deploy, installi solo dependencies (bundle più leggero).
Se cloni un progetto con package.json, installa tutto con:
Aggiungere una nuova dipendenza:
Quando installi una libreria, NPM aggiunge la versione in package.json:
Significato dei simboli:
^5.0.0 → accetta versioni >=5.0.0 e <6.0.0 (aggiornamenti minori)~5.0.0 → accetta versioni >=5.0.0 e <5.1.0 (solo patch)5.0.0 → versione esatta (bloccata)In pratica, ^ è lo standard: ricevi aggiornamenti compatibili automaticamente.
Problema: 50 moduli nel progetto = 50 HTTP requests.
index.js
├── api/recipeService.js
│ ├── utils/validators.js
│ └── api/helpers.js
├── components/RecipeList.js
│ └── utils/dateFormat.js
└── 47 altri moduli
Ogni richiesta = ritardo. Lento in produzione.
Soluzione: Un bundler combina i 50 moduli in 1 (o pochi) bundle.
Vite è un bundler moderno, veloce, zero-config.
Caratteristiche:
Flusso:
Sviluppo: src/ → npm run dev → localhost:5173 (HMR veloce)
Produzione: src/ → npm run build → dist/ (ottimizzato)
Setup:
Vite è zero-config: non serve .config.js per progetti semplici.
Ricettario modularizzato con Vite
Ricrea il ricettario dell’Esercizio 2 con setup professionale:
Setup Vite (npm create vite@latest)
Installa convert via NPM (non da CDN)
Modularizza il codice in src/ con responsabilità chiare:
src/modules/converters.js (logica di conversione)src/modules/recipeList.js (rendering)src/modules/toggleHandler.js (gestione toggle)src/index.js (entry point)Niccolò Maltoni