Integrazione con Vite
Le applicazioni JavaScript moderne richiedono strumenti di build sofisticati. Nette Assets offre un'integrazione di prima classe con Vite, lo strumento di build frontend di nuova generazione. Otterrete uno sviluppo fulmineo con l'Hot Module Replacement (HMR) e build di produzione ottimizzate senza grattacapi di configurazione.
- Zero configurazione – ponte automatico tra Vite e i template PHP
- Gestione completa delle dipendenze – un solo tag si occupa di tutti gli asset
- Hot Module Replacement – aggiornamenti istantanei di JavaScript e CSS
- Build di produzione ottimizzate – code splitting e tree shaking
Nette Assets si integra in modo fluido con Vite, così ottenete tutti questi vantaggi scrivendo i vostri template come al solito.
Impostare Vite
Impostiamo Vite passo passo. Non preoccupatevi se gli strumenti di build sono una novità per voi, spiegheremo tutto!
Passo 1: installare Vite
Per prima cosa installate nel vostro progetto Vite e il plugin di Nette:
npm install -D vite @nette/vite-plugin
Questo installa Vite e uno speciale plugin che aiuta Vite a funzionare perfettamente con Nette.
Passo 2: struttura del progetto
L'approccio standard è mettere i file sorgente degli asset in una cartella assets/ nella radice del progetto e
le versioni compilate in www/assets/:
web-project/ ├── assets/ ← file sorgente (SCSS, TypeScript, immagini sorgente) │ ├── public/ ← file statici (copiati così come sono) │ │ └── favicon.ico │ ├── images/ │ │ └── logo.png │ ├── app.js ← punto di ingresso principale │ └── style.css ← i vostri stili └── www/ ← directory pubblica (document root) ├── assets/ ← qui finiranno i file compilati └── index.php
La cartella assets/ contiene i vostri file sorgente, il codice che scrivete. Vite elaborerà questi file e
metterà le versioni compilate in www/assets/.
Passo 3: configurare Vite
Create nella radice del progetto un file vite.config.ts. Questo file dice a Vite dove trovare i vostri file
sorgente e dove mettere quelli compilati.
Il plugin Vite di Nette arriva con valori predefiniti intelligenti che rendono semplice la configurazione. Presuppone che
i vostri file sorgente frontend siano nella directory assets/ (opzione root) e che i file compilati
vadano in www/assets/ (opzione outDir). Dovete solo indicare il punto
di ingresso:
import { defineConfig } from 'vite';
import nette from '@nette/vite-plugin';
export default defineConfig({
plugins: [
nette({
entry: 'app.js',
}),
],
});
Sotto il cofano, oltre a root e outDir, il plugin imposta qualche altra opzione di Vite perché tutto
combaci: base a '' (gli asset vengono serviti direttamente dal document root),
build.manifest a true (così Nette Assets può mappare i nomi di file con hash) e
build.assetsDir a '' (i file compilati finiscono direttamente in outDir, senza una
sottocartella static/). Potete sovrascrivere qualsiasi di esse.
L'outDir predefinito (www/assets) richiede che la directory www/ esista
già. In caso contrario il plugin si ferma con l'errore “The output directory … does not exist”.
Se volete indicare un altro nome di directory in cui compilare i vostri asset, dovrete cambiare qualche opzione:
export default defineConfig({
root: 'assets', // directory radice degli asset sorgente
build: {
outDir: '../www/assets', // dove finiscono i file compilati
},
// ... altra configurazione ...
});
Il percorso outDir è considerato relativo a root, ed è per questo che all'inizio c'è
../.
Passo 4: configurare Nette
Parlate di Vite a Nette Assets nel vostro common.neon:
assets:
mapping:
default:
type: vite # dice a Nette di usare il ViteMapper
path: assets
Passo 5: aggiungere gli script
Aggiungete questi script al vostro package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
Ora potete:
npm run dev– avviare il server di sviluppo con il ricaricamento a caldonpm run build– creare i file di produzione ottimizzati
Punti di ingresso
Un punto di ingresso è il file principale da cui parte la vostra applicazione. Da questo file importate altri file (CSS, moduli JavaScript, immagini), creando un albero di dipendenze. Vite segue questi import e mette insieme tutto.
Esempio di punto di ingresso assets/app.js:
// import degli stili
import './style.css'
// import dei moduli JavaScript
import netteForms from 'nette-forms';
import naja from 'naja';
// inizializzazione della vostra applicazione
netteForms.initOnLoad();
naja.initialize();
Nel template potete inserire un punto di ingresso così:
{asset 'app.js'}
Nette Assets genera automaticamente tutti i tag HTML necessari: JavaScript, CSS e le eventuali altre dipendenze.
Più punti di ingresso
Le applicazioni più grandi hanno spesso bisogno di punti di ingresso separati:
export default defineConfig({
plugins: [
nette({
entry: [
'app.js', // pagine pubbliche
'admin.js', // pannello di amministrazione
],
}),
],
});
Usateli in template diversi:
{* nelle pagine pubbliche *}
{asset 'app.js'}
{* nel pannello di amministrazione *}
{asset 'admin.js'}
Importante: file sorgente e file compilati
È fondamentale capire che in produzione potete caricare solo i file che Vite rende disponibili, o tramite il suo manifest, oppure copiandoli invariati dalla cartella public:
- I punti di ingresso definiti in
entry(compresi i moduli che importano dinamicamente) e gli asset referenziati da JavaScript o CSS (immagini, font, …): tutti questi vengono registrati nel manifest - I file della directory
assets/public/: questi non sono nel manifest; vengono copiati così come sono e{asset}li trova tramite un ripiego sul filesystem
Non potete caricare con {asset} file arbitrari da assets/: se un file non è referenziato da
nessuna parte, non verrà compilato. Se volete far conoscere a Vite altri asset, potete spostarli nella cartella public.
Tenete presente che per impostazione predefinita Vite inserisce inline tutti gli asset più piccoli di 4 KB, quindi non potrete referenziare direttamente questi file. (Vedi la documentazione di Vite).
{* ✓ questo funziona, è un punto di ingresso *}
{asset 'app.js'}
{* ✓ questo funziona, è in assets/public/ *}
{asset 'favicon.ico'}
{* ✗ questo non funziona, è un file qualsiasi in assets/ *}
{asset 'components/button.js'}
Modalità di sviluppo
La modalità di sviluppo è del tutto facoltativa, ma quando è attiva offre vantaggi notevoli. Il principale è l'Hot Module Replacement (HMR): vedete le modifiche all'istante senza perdere lo stato dell'applicazione, il che rende lo sviluppo molto più fluido e veloce.
Vite è uno strumento di build moderno che rende lo sviluppo incredibilmente veloce. A differenza dei bundler tradizionali, durante lo sviluppo Vite serve il vostro codice direttamente al browser, il che significa avvio istantaneo del server per quanto grande sia il progetto e aggiornamenti fulminei.
Avviare il server di sviluppo
Lanciate il server di sviluppo:
npm run dev
Vedrete:
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Tenete aperto questo terminale mentre sviluppate.
Mentre il dev server è in esecuzione, il plugin scrive un piccolo file di segnalazione
www/assets/.vite/nette.json che contiene il suo URL. Dal lato PHP Nette Assets legge questo file e passa a caricare
dal dev server quando entrambe le condizioni sono vere:
- il dev server di Vite è in esecuzione (il file di segnalazione esiste) e
- la vostra applicazione Nette è in modalità debug.
Il risultato:
{asset 'app.js'}
{* in sviluppo: <script src="http://localhost:5173/@vite/client" type="module"></script>
<script src="http://localhost:5173/app.js" type="module"></script> *}
{* in produzione: <script src="/assets/app-4f3a2b1c.js" type="module" crossorigin></script> *}
Non serve alcuna configurazione, funziona e basta! Per disattivare il rilevamento o impostare a mano l'URL del dev server, vedi l'opzione devServer.
Il file di segnalazione sta in www/assets/.vite/nette.json (proprio accanto al
manifest.json di produzione). Lo potete rinominare con l'opzione infoFile del plugin, che vale
.vite/nette.json per impostazione predefinita. Se Nette non rileva il dev server in esecuzione, controllate che
questo file esista e punti all'URL giusto.
Lavorare su domini diversi
Se il vostro server di sviluppo gira su qualcosa di diverso da localhost (per esempio myapp.local),
potreste incontrare problemi di CORS (Cross-Origin Resource Sharing). Il CORS è una funzione di sicurezza dei browser web che per
impostazione predefinita blocca le richieste tra domini diversi. Quando la vostra applicazione PHP gira su
myapp.local ma Vite gira su localhost:5173, il browser li vede come domini diversi e blocca le
richieste.
Avete due possibilità per risolverlo:
Possibilità 1: configurare il CORS
La soluzione più semplice è consentire le richieste cross-origin dalla vostra applicazione PHP:
export default defineConfig({
// ... altra configurazione ...
server: {
cors: {
origin: 'http://myapp.local', // l'URL della vostra app PHP
},
},
});
Possibilità 2: far girare Vite sul vostro dominio
L'altra soluzione è far girare Vite sullo stesso dominio della vostra applicazione PHP.
export default defineConfig({
// ... altra configurazione ...
server: {
host: 'myapp.local', // uguale alla vostra app PHP
},
});
In realtà, anche in questo caso dovete configurare il CORS, perché il dev server gira sullo stesso hostname ma su una porta diversa. In questo caso però il CORS viene configurato automaticamente dal plugin Vite di Nette.
Sviluppo in HTTPS
Se sviluppate in HTTPS, avete bisogno di certificati per il vostro server di sviluppo Vite. Il modo più semplice è usare un plugin che genera i certificati automaticamente:
npm install -D vite-plugin-mkcert
Ecco come configurarlo in vite.config.ts:
import mkcert from 'vite-plugin-mkcert';
export default defineConfig({
// ... altra configurazione ...
plugins: [
mkcert(), // genera automaticamente i certificati e attiva https
nette(),
],
});
Notate che, se usate la configurazione CORS (Possibilità 1 di sopra), dovete aggiornare l'URL di origine perché usi
https:// invece di http://.
Sviluppo in Docker
Quando fate girare Vite dentro un container Docker, due cose richiedono attenzione: il browser sulla vostra macchina deve poter raggiungere il dev server e Vite deve rilevare le modifiche ai file attraverso il confine del container.
Per prima cosa pubblicate la porta di Vite dal container e legate il dev server a tutte le interfacce, così è raggiungibile da fuori:
export default defineConfig({
// ... altra configurazione ...
plugins: [
nette(),
],
server: {
host: '0.0.0.0', // ascolta su tutte le interfacce (necessario in un container)
port: 5173, // deve corrispondere alla porta pubblicata
strictPort: true, // meglio fallire che scegliere un'altra porta
watch: {
usePolling: true, // attivate se le modifiche ai file non vengono rilevate sui volumi montati
},
},
});
Il plugin scrive l'URL del dev server in nette.json per il lato PHP. Poiché host: '0.0.0.0' non è
utilizzabile da un browser (per ogni asset reindirizza a localhost), il plugin lo riscrive automaticamente in
localhost dentro quell'URL, così gli asset si caricano correttamente.
Se aprite l'applicazione su un dominio personalizzato invece che su localhost, impostate l'opzione
host del plugin su quel dominio:
plugins: [
nette({ host: 'myapp.local' }), // lo stesso dominio della vostra app PHP
],
Il plugin usa allora questo host per l'URL del dev server, lo aggiunge alle origini CORS e lo inserisce nella whitelist
allowedHosts di Vite, così funziona senza alcuna configurazione CORS manuale.
Per configurazioni più complesse, per esempio quando Vite gira dietro a un reverse proxy dove host pubblico, porta e
protocollo differiscono tutti dall'indirizzo interno, impostate server.origin di Vite sull'URL pubblico completo. Il
plugin lo rispetta e lo scrive in nette.json così com'è, invece di ricavare l'URL dal socket locale:
server: {
origin: 'https://myapp.local:8443', // l'URL pubblico da cui il browser raggiunge Vite
},
Build di produzione
Create i file di produzione ottimizzati:
npm run build
Vite:
- minimizzerà tutto il JavaScript e il CSS
- dividerà il codice in chunk ottimali
- genererà nomi di file con hash per invalidare la cache
- creerà un file manifest per Nette Assets
Esempio di output:
www/assets/
├── app-4f3a2b1c.js # il vostro JavaScript principale (minimizzato)
├── app-7d8e9f2a.css # CSS estratto (minimizzato)
├── vendor-8c4b5e6d.js # dipendenze condivise
└── .vite/
└── manifest.json # mappatura per Nette Assets
I nomi di file con hash garantiscono che i browser carichino sempre la versione più recente.
Cartella public
I file della directory assets/public/ vengono copiati nell'output senza essere elaborati:
assets/
├── public/
│ ├── favicon.ico
│ ├── robots.txt
│ └── images/
│ └── og-image.jpg
├── app.js
└── style.css
Referenziateli normalmente:
{* questi file vengono copiati così come sono *}
<link rel="icon" href={asset 'favicon.ico'}>
<meta property="og:image" content={asset 'images/og-image.jpg'}>
Per i file public potete usare le funzionalità di FilesystemMapper. L'opzione extension si applica ai
riferimenti senza estensione (per esempio {asset 'images/og-image'} trova prima og-image.webp):
assets:
mapping:
default:
type: vite
path: assets
extension: [webp, jpg, png] # per i riferimenti senza estensione
versioning: true # aggiunge l'invalidazione della cache
Nella configurazione di vite.config.ts potete cambiare la cartella public con l'opzione
publicDir.
Import dinamici
Vite divide automaticamente il codice per un caricamento ottimale. Gli import dinamici vi permettono di caricare il codice solo quando serve davvero, riducendo la dimensione del bundle iniziale:
// carica i componenti pesanti su richiesta
button.addEventListener('click', async () => {
let { Chart } = await import('./components/chart.js')
new Chart(data)
})
Gli import dinamici creano chunk separati che vengono caricati solo quando servono. Questo si chiama “code splitting” ed è una delle funzionalità più potenti di Vite. Quando usate gli import dinamici, Vite crea automaticamente file JavaScript separati per ogni modulo importato dinamicamente.
Il tag {asset 'app.js'} non esegue automaticamente il preload di questi chunk dinamici. È un comportamento
voluto: non vogliamo scaricare codice che potrebbe non essere mai usato. I chunk vengono scaricati solo quando l'import dinamico
viene eseguito.
Se però sapete che certi import dinamici sono critici e serviranno presto, potete farne il preload:
{* punto di ingresso principale *}
{asset 'app.js'}
{* preload degli import dinamici critici *}
{preload 'components/chart.js'}
Questo dice al browser di scaricare il componente chart in background, così è pronto subito quando serve.
Supporto per TypeScript
TypeScript funziona subito:
// assets/main.ts
interface User {
name: string
email: string
}
export function greetUser(user: User): void {
console.log(`Hello, ${user.name}!`)
}
Referenziate i file TypeScript normalmente (come per qualsiasi file, main.ts deve essere un punto di
ingresso):
{asset 'main.ts'}
Per il pieno supporto a TypeScript, installatelo:
npm install -D typescript
Altra configurazione di Vite
Ecco alcune utili opzioni di configurazione di Vite con spiegazioni dettagliate:
export default defineConfig({
// directory radice che contiene gli asset sorgente
root: 'assets',
// cartella il cui contenuto viene copiato nella directory di output così com'è
// predefinito: 'public' (relativo a 'root')
publicDir: 'public',
build: {
// dove mettere i file compilati (relativo a 'root')
outDir: '../www/assets',
// svuotare la directory di output prima della build?
// utile per rimuovere i vecchi file delle build precedenti
emptyOutDir: true,
// sottodirectory dentro outDir per i chunk e gli asset generati
// aiuta a organizzare la struttura dell'output
assetsDir: 'static',
rollupOptions: {
// punto o punti di ingresso: può essere un singolo file o un array di file
// ogni punto di ingresso diventa un bundle separato
input: [
'app.js', // applicazione principale
'admin.js', // pannello di amministrazione
],
},
},
server: {
// host a cui legare il dev server
// usate '0.0.0.0' per esporlo in rete
host: 'localhost',
// porta del dev server
port: 5173,
// configurazione CORS per le richieste cross-origin
cors: {
origin: 'http://myapp.local',
},
},
css: {
// attiva le source map CSS in sviluppo
devSourcemap: true,
},
plugins: [
nette(),
],
});
Attenzione ai percorsi dei punti di ingresso in rollupOptions.input qui sopra: Rollup risolve
i percorsi relativi rispetto alla directory di lavoro corrente (la radice del progetto), non rispetto a
root: 'assets'. Un semplice 'app.js' non esisterà quindi al momento della build. Usate l'opzione
entry del plugin (che risolve i percorsi dei punti di ingresso relativamente a root), oppure scrivete
i percorsi relativi alla radice del progetto, per esempio 'assets/app.js'.
Ecco fatto! Ora avete un sistema di build moderno integrato con Nette Assets.