Integracja z Vite
Nowoczesne aplikacje JavaScriptowe wymagają zaawansowanych narzędzi budujących. Nette Assets daje pierwszorzędną integrację z Vite, narzędziem budującym frontend nowej generacji. Uzyskaj błyskawiczne tworzenie z Hot Module Replacement (HMR) i zoptymalizowane buildy produkcyjne bez cierpienia z konfiguracją.
- Zero konfiguracji – automatyczny most między Vite a szablonami PHP
- Kompletne zarządzanie zależnościami – jeden tag obsługuje wszystkie zasoby
- Hot Module Replacement – natychmiastowe aktualizacje JavaScriptu i CSS
- Zoptymalizowane buildy produkcyjne – code splitting i tree shaking
Nette Assets integruje się z Vite płynnie, więc dostajesz wszystkie te korzyści, pisząc szablony jak zwykle.
Ustawienie Vite
Ustawmy Vite krok po kroku. Nie martw się, jeśli narzędzia budujące są dla Ciebie nowe, wszystko wyjaśnimy!
Krok 1: instalacja Vite
Najpierw zainstaluj w swoim projekcie Vite i plugin Nette:
npm install -D vite @nette/vite-plugin
Instaluje to Vite i specjalny plugin, który pomaga Vite doskonale współpracować z Nette.
Krok 2: struktura projektu
Standardowe podejście to umieszczenie źródłowych plików zasobów w folderze assets/ w katalogu głównym
projektu, a wersji skompilowanych w www/assets/:
web-project/ ├── assets/ ← pliki źródłowe (SCSS, TypeScript, obrazki źródłowe) │ ├── public/ ← pliki statyczne (kopiowane bez zmian) │ │ └── favicon.ico │ ├── images/ │ │ └── logo.png │ ├── app.js ← główny punkt wejścia │ └── style.css ← Twoje style └── www/ ← katalog publiczny (document root) ├── assets/ ← tutaj trafią pliki skompilowane └── index.php
Folder assets/ zawiera Twoje pliki źródłowe, czyli kod, który piszesz. Vite przetworzy te pliki i umieści
wersje skompilowane w www/assets/.
Krok 3: konfiguracja Vite
Utwórz w katalogu głównym projektu plik vite.config.ts. Plik ten mówi Vite, gdzie znaleźć Twoje pliki
źródłowe i gdzie umieścić skompilowane.
Plugin Vite dla Nette przychodzi ze sprytnymi wartościami domyślnymi, które upraszczają konfigurację. Zakłada, że Twoje
źródłowe pliki front-endowe są w katalogu assets/ (opcja root), a pliki skompilowane trafiają do
www/assets/ (opcja outDir). Musisz podać tylko punkt wejścia:
import { defineConfig } from 'vite';
import nette from '@nette/vite-plugin';
export default defineConfig({
plugins: [
nette({
entry: 'app.js',
}),
],
});
Pod maską, oprócz root i outDir, plugin ustawia jeszcze kilka opcji Vite, żeby wszystko do siebie
pasowało: base na '' (zasoby serwowane są prosto z document rootu), build.manifest na
true (żeby Nette Assets mogło zmapować zahashowane nazwy plików) i build.assetsDir na
'' (pliki skompilowane lądują bezpośrednio w outDir, bez podfolderu static/). Każdą
z nich możesz nadpisać.
Domyślny outDir (www/assets) wymaga, żeby katalog www/ już istniał.
Jeśli nie istnieje, plugin zatrzymuje się z błędem “The output directory … does not exist”.
Jeśli chcesz podać inną nazwę katalogu do budowania swoich zasobów, musisz zmienić kilka opcji:
export default defineConfig({
root: 'assets', // katalog główny zasobów źródłowych
build: {
outDir: '../www/assets', // gdzie trafiają pliki skompilowane
},
// ... pozostała konfiguracja ...
});
Ścieżka outDir traktowana jest jako względna wobec root, dlatego na początku jest
../.
Krok 4: konfiguracja Nette
Powiedz Nette Assets o Vite w swoim common.neon:
assets:
mapping:
default:
type: vite # mówi Nette, żeby użyło ViteMappera
path: assets
Krok 5: dodanie skryptów
Dodaj te skrypty do swojego package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
Teraz możesz:
npm run dev– uruchomić serwer deweloperski z hot reloadingiemnpm run build– utworzyć zoptymalizowane pliki produkcyjne
Punkty wejścia
Punkt wejścia to główny plik, w którym zaczyna się Twoja aplikacja. Z tego pliku importujesz inne pliki (CSS, moduły JavaScriptu, obrazki), tworząc drzewo zależności. Vite podąża za tymi importami i pakuje wszystko razem.
Przykładowy punkt wejścia assets/app.js:
// Importujemy style
import './style.css'
// Importujemy moduły JavaScriptu
import netteForms from 'nette-forms';
import naja from 'naja';
// Inicjalizujemy swoją aplikację
netteForms.initOnLoad();
naja.initialize();
W szablonie punkt wejścia możesz wstawić tak:
{asset 'app.js'}
Nette Assets automatycznie generuje wszystkie potrzebne tagi HTML: JavaScript, CSS i wszelkie inne zależności.
Wiele punktów wejścia
Większe aplikacje często potrzebują osobnych punktów wejścia:
export default defineConfig({
plugins: [
nette({
entry: [
'app.js', // strony publiczne
'admin.js', // panel administracyjny
],
}),
],
});
Używaj ich w różnych szablonach:
{* Na stronach publicznych *}
{asset 'app.js'}
{* W panelu administracyjnym *}
{asset 'admin.js'}
Ważne: pliki źródłowe kontra skompilowane
Kluczowe jest zrozumienie, że w produkcji możesz wczytywać tylko te pliki, które Vite udostępnia: albo przez swój manifest, albo kopiując je bez zmian z folderu public:
- Punkty wejścia zdefiniowane w
entry(wraz z modułami, które dynamicznie importują) i zasoby, do których odwołuje się JavaScript albo CSS (obrazki, fonty, …) – wszystko to zapisane jest w manifeście - Pliki z katalogu
assets/public/– tych nie ma w manifeście; kopiowane są bez zmian, a{asset}znajduje je przez fallback do systemu plików
Nie możesz wczytywać przez {asset} dowolnych plików z assets/: jeśli plik nie jest nigdzie
referencjonowany, nie zostanie skompilowany. Jeśli chcesz, żeby Vite wiedziało o innych zasobach, możesz przenieść je do folderu public.
Zwróć uwagę, że domyślnie Vite osadza inline wszystkie zasoby mniejsze niż 4 kB, więc do tych plików nie będziesz mógł odwołać się bezpośrednio. (Patrz dokumentacja Vite).
{* ✓ To działa - to punkt wejścia *}
{asset 'app.js'}
{* ✓ To działa - jest w assets/public/ *}
{asset 'favicon.ico'}
{* ✗ To nie zadziała - przypadkowy plik w assets/ *}
{asset 'components/button.js'}
Tryb deweloperski
Tryb deweloperski jest całkowicie opcjonalny, ale po włączeniu daje znaczne korzyści. Główną zaletą jest Hot Module Replacement (HMR): widzisz zmiany natychmiast, bez utraty stanu aplikacji, przez co tworzenie jest znacznie płynniejsze i szybsze.
Vite to nowoczesne narzędzie budujące, które czyni tworzenie niesamowicie szybkim. W przeciwieństwie do tradycyjnych bundlerów Vite serwuje Twój kod przeglądarce bezpośrednio podczas tworzenia, co oznacza natychmiastowy start serwera niezależnie od wielkości projektu i błyskawiczne aktualizacje.
Uruchomienie serwera deweloperskiego
Uruchom serwer deweloperski:
npm run dev
Zobaczysz:
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Zostaw ten terminal otwarty podczas tworzenia.
Gdy serwer deweloperski działa, plugin zapisuje mały plik sygnalizacyjny www/assets/.vite/nette.json
zawierający jego URL. Nette Assets po stronie PHP czyta ten plik i przełącza się na wczytywanie z serwera deweloperskiego,
gdy jednocześnie:
- serwer deweloperski Vite działa (plik sygnalizacyjny istnieje) oraz
- Twoja aplikacja Nette jest w trybie debug.
Wynik:
{asset 'app.js'}
{* Przy tworzeniu: <script src="http://localhost:5173/@vite/client" type="module"></script>
<script src="http://localhost:5173/app.js" type="module"></script> *}
{* W produkcji: <script src="/assets/app-4f3a2b1c.js" type="module" crossorigin></script> *}
Żadna konfiguracja nie jest potrzebna, po prostu działa! Żeby wyłączyć wykrywanie albo ustawić URL serwera deweloperskiego ręcznie, patrz opcja devServer.
Plik sygnalizacyjny leży w www/assets/.vite/nette.json (zaraz obok produkcyjnego
manifest.json). Możesz go przemianować opcją pluginu infoFile, która domyślnie ma wartość
.vite/nette.json. Jeśli Nette nie wykrywa działającego serwera deweloperskiego, sprawdź, czy plik ten istnieje
i wskazuje właściwy URL.
Praca na różnych domenach
Jeśli Twój serwer deweloperski działa na czymś innym niż localhost (jak myapp.local), możesz
natrafić na problemy z CORS (Cross-Origin Resource Sharing). CORS to funkcja bezpieczeństwa przeglądarek, która domyślnie
blokuje żądania między różnymi domenami. Gdy Twoja aplikacja PHP działa na myapp.local, a Vite na
localhost:5173, przeglądarka widzi je jako różne domeny i blokuje żądania.
Masz dwie możliwości, żeby to rozwiązać:
Opcja 1: skonfiguruj CORS
Najprostszym rozwiązaniem jest dopuszczenie żądań cross-origin z Twojej aplikacji PHP:
export default defineConfig({
// ... pozostała konfiguracja ...
server: {
cors: {
origin: 'http://myapp.local', // URL Twojej aplikacji PHP
},
},
});
Opcja 2: uruchom Vite na swojej domenie
Drugim rozwiązaniem jest sprawienie, żeby Vite działało na tej samej domenie co Twoja aplikacja PHP.
export default defineConfig({
// ... pozostała konfiguracja ...
server: {
host: 'myapp.local', // tak samo jak Twoja aplikacja PHP
},
});
Właściwie nawet w tym przypadku musisz skonfigurować CORS, bo serwer deweloperski działa na tej samej nazwie hosta, ale na innym porcie. W tym przypadku CORS konfiguruje jednak automatycznie plugin Vite dla Nette.
Tworzenie na HTTPS
Jeśli tworzysz na HTTPS, potrzebujesz certyfikatów dla swojego serwera deweloperskiego Vite. Najprościej użyć pluginu generującego certyfikaty automatycznie:
npm install -D vite-plugin-mkcert
Oto jak skonfigurować go w vite.config.ts:
import mkcert from 'vite-plugin-mkcert';
export default defineConfig({
// ... pozostała konfiguracja ...
plugins: [
mkcert(), // generuje certyfikaty automatycznie i włącza https
nette(),
],
});
Zwróć uwagę, że jeśli używasz konfiguracji CORS (opcja 1 powyżej), musisz zaktualizować URL origin, żeby używał
https:// zamiast http://.
Tworzenie w Dockerze
Gdy uruchamiasz Vite wewnątrz kontenera Dockera, dwie rzeczy wymagają uwagi: przeglądarka na Twojej maszynie musi móc dosięgnąć serwera deweloperskiego, a Vite musi wykrywać zmiany plików przez granicę kontenera.
Najpierw opublikuj port Vite z kontenera i przypnij serwer deweloperski do wszystkich interfejsów, żeby był osiągalny spoza kontenera:
export default defineConfig({
// ... pozostała konfiguracja ...
plugins: [
nette(),
],
server: {
host: '0.0.0.0', // nasłuchuj na wszystkich interfejsach (wymagane w kontenerze)
port: 5173, // musi odpowiadać opublikowanemu portowi
strictPort: true, // raczej zawiedź, niż wybieraj inny port
watch: {
usePolling: true, // włącz, jeśli zmiany plików nie są wykrywane na zamontowanych wolumenach
},
},
});
Plugin zapisuje URL serwera deweloperskiego do nette.json dla strony PHP. Ponieważ host: '0.0.0.0'
nie nadaje się do użycia przez przeglądarkę (dla każdego zasobu przekierowuje na localhost), plugin
automatycznie przepisuje go w tym URL na localhost, żeby zasoby wczytywały się poprawnie.
Jeśli otwierasz aplikację na własnej domenie zamiast na localhost, ustaw opcję pluginu host na
tę domenę:
plugins: [
nette({ host: 'myapp.local' }), // ta sama domena co Twoja aplikacja PHP
],
Plugin używa wtedy tego hosta w URL serwera deweloperskiego, dodaje go do origins CORS i wpisuje na białą listę
allowedHosts Vite, więc działa bez żadnej ręcznej konfiguracji CORS.
Dla bardziej złożonych konfiguracji, na przykład gdy Vite działa za reverse proxy, gdzie publiczny host, port i protokół
różnią się od adresu wewnętrznego, ustaw server.origin Vite na pełny publiczny URL. Plugin respektuje go
i zapisuje do nette.json bez zmian, zamiast wyprowadzać URL z lokalnego gniazda:
server: {
origin: 'https://myapp.local:8443', // publiczny URL, pod którym przeglądarka sięga po Vite
},
Buildy produkcyjne
Utwórz zoptymalizowane pliki produkcyjne:
npm run build
Vite:
- Zminifikuje cały JavaScript i CSS
- Podzieli kod na optymalne chunki
- Wygeneruje zahashowane nazwy plików na potrzeby cache bustingu
- Utworzy plik manifestu dla Nette Assets
Przykładowe wyjście:
www/assets/
├── app-4f3a2b1c.js # Twój główny JavaScript (zminifikowany)
├── app-7d8e9f2a.css # Wyodrębniony CSS (zminifikowany)
├── vendor-8c4b5e6d.js # Wspólne zależności
└── .vite/
└── manifest.json # Mapowanie dla Nette Assets
Zahashowane nazwy plików zapewniają, że przeglądarki zawsze wczytują najnowszą wersję.
Folder public
Pliki w katalogu assets/public/ kopiowane są na wyjście bez przetwarzania:
assets/
├── public/
│ ├── favicon.ico
│ ├── robots.txt
│ └── images/
│ └── og-image.jpg
├── app.js
└── style.css
Odwołuj się do nich normalnie:
{* Te pliki kopiowane są bez zmian *}
<link rel="icon" href={asset 'favicon.ico'}>
<meta property="og:image" content={asset 'images/og-image.jpg'}>
Dla plików publicznych możesz używać funkcji FilesystemMappera. Opcja extension dotyczy odwołań bez
rozszerzenia (np. {asset 'images/og-image'} znajdzie najpierw og-image.webp):
assets:
mapping:
default:
type: vite
path: assets
extension: [webp, jpg, png] # dla odwołań bez rozszerzenia
versioning: true # Dodaje cache busting
W konfiguracji vite.config.ts możesz zmienić folder public opcją publicDir.
Importy dynamiczne
Vite automatycznie dzieli kod na potrzeby optymalnego wczytywania. Importy dynamiczne pozwalają wczytywać kod dopiero wtedy, gdy jest faktycznie potrzebny, zmniejszając początkowy rozmiar bundle'a:
// Wczytujemy ciężkie komponenty na żądanie
button.addEventListener('click', async () => {
let { Chart } = await import('./components/chart.js')
new Chart(data)
})
Importy dynamiczne tworzą osobne chunki wczytywane tylko wtedy, gdy są potrzebne. Nazywa się to “code splitting” i jest jedną z najpotężniejszych funkcji Vite. Gdy używasz importów dynamicznych, Vite automatycznie tworzy osobne pliki JavaScript dla każdego dynamicznie importowanego modułu.
Tag {asset 'app.js'} nie preładowuje tych dynamicznych chunków automatycznie. To zachowanie zamierzone:
nie chcemy pobierać kodu, który może nigdy nie zostać użyty. Chunki pobierane są dopiero wtedy, gdy import dynamiczny
zostanie wykonany.
Jeśli jednak wiesz, że pewne importy dynamiczne są krytyczne i będą wkrótce potrzebne, możesz je preładować:
{* Główny punkt wejścia *}
{asset 'app.js'}
{* Preładowanie krytycznych importów dynamicznych *}
{preload 'components/chart.js'}
Mówi to przeglądarce, żeby pobrała komponent wykresu w tle, dzięki czemu jest gotowy natychmiast, gdy będzie potrzebny.
Wsparcie dla TypeScriptu
TypeScript działa od razu:
// assets/main.ts
interface User {
name: string
email: string
}
export function greetUser(user: User): void {
console.log(`Hello, ${user.name}!`)
}
Odwołuj się do plików TypeScript normalnie (jak przy każdym pliku, main.ts musi być punktem wejścia):
{asset 'main.ts'}
Dla pełnego wsparcia TypeScriptu zainstaluj go:
npm install -D typescript
Dodatkowa konfiguracja Vite
Oto kilka przydatnych opcji konfiguracyjnych Vite ze szczegółowymi wyjaśnieniami:
export default defineConfig({
// Katalog główny zawierający zasoby źródłowe
root: 'assets',
// Folder, którego zawartość kopiowana jest do katalogu wyjściowego bez zmian
// Domyślnie: 'public' (względem 'root')
publicDir: 'public',
build: {
// Gdzie umieszczać pliki skompilowane (względem 'root')
outDir: '../www/assets',
// Opróżnić katalog wyjściowy przed budowaniem?
// Przydatne do usunięcia starych plików z poprzednich buildów
emptyOutDir: true,
// Podkatalog w outDir na generowane chunki i zasoby
// Pomaga to uporządkować strukturę wyjścia
assetsDir: 'static',
rollupOptions: {
// Punkt(y) wejścia - może być pojedynczy plik albo tablica plików
// Każdy punkt wejścia staje się osobnym bundlem
input: [
'app.js', // główna aplikacja
'admin.js', // panel administracyjny
],
},
},
server: {
// Host, do którego przypinany jest serwer deweloperski
// Użyj '0.0.0.0', żeby udostępnić go w sieci
host: 'localhost',
// Port serwera deweloperskiego
port: 5173,
// Konfiguracja CORS dla żądań cross-origin
cors: {
origin: 'http://myapp.local',
},
},
css: {
// Włącza source mapy CSS przy tworzeniu
devSourcemap: true,
},
plugins: [
nette(),
],
});
Uważaj na ścieżki punktów wejścia w powyższym rollupOptions.input: Rollup rozwiązuje
ścieżki względne wobec bieżącego katalogu roboczego (katalogu głównego Twojego projektu), a nie wobec
root: 'assets'. Samo 'app.js' nie będzie więc istnieć w czasie budowania. Albo użyj opcji pluginu
entry (która rozwiązuje ścieżki punktów wejścia względem root), albo zapisz ścieżki względem
katalogu głównego projektu, np. 'assets/app.js'.
I to wszystko! Masz teraz nowoczesny system budowania zintegrowany z Nette Assets.