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 caldo
  • npm 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:

  1. 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
  2. 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:

  1. il dev server di Vite è in esecuzione (il file di segnalazione esiste) e
  2. 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.

versione: 1.x