Nette Assets
Zmęczony ręcznym zarządzaniem plikami statycznymi w swoich aplikacjach webowych? Zapomnij o zapisywaniu ścieżek na sztywno, o walce z inwalidacją cache i o martwieniu się wersjonowaniem plików. Nette Assets zmienia sposób, w jaki pracujesz z obrazkami, arkuszami stylów, skryptami i innymi zasobami statycznymi.
- Sprytne wersjonowanie zapewnia, że przeglądarki zawsze wczytują najnowsze pliki
- Automatyczne wykrywanie typów plików i wymiarów
- Płynna integracja z Latte dzięki intuicyjnym tagom
- Elastyczna architektura wspierająca systemy plików, CDN-y i Vite
- Leniwe wczytywanie dla optymalnej wydajności
Dlaczego Nette Assets?
Praca z plikami statycznymi często oznacza powtarzalny, podatny na błędy kod. Ręcznie składasz URL-e, dodajesz parametry wersji na potrzeby cache bustingu i obsługujesz różne typy plików w różny sposób. Prowadzi to do kodu w rodzaju:
<img src="/images/logo.png?v=1699123456" width="200" height="100" alt="Logo">
<link rel="stylesheet" href="/css/style.css?v=2">
Z Nette Assets cała ta złożoność znika:
{* Wszystko zautomatyzowane - URL, wersjonowanie, wymiary *}
<img n:asset="images/logo.png">
<link n:asset="css/style.css">
{* Albo po prostu *}
{asset 'css/style.css'}
I to wszystko! Biblioteka automatycznie:
- Dodaje parametry wersji na podstawie czasu modyfikacji pliku
- Wykrywa wymiary obrazków i umieszcza je w HTML-u
- Generuje właściwy element HTML dla każdego typu pliku
- Obsługuje zarówno środowisko deweloperskie, jak i produkcyjne
Instalacja
Zainstaluj Nette Assets za pomocą Composera:
composer require nette/assets
Wymaga PHP 8.1 albo wyższego i doskonale współpracuje z Nette Framework, ale można go używać także samodzielnie.
Pierwsze kroki
Nette Assets działa od razu, bez żadnej konfiguracji. Umieść swoje pliki statyczne w katalogu www/assets/
i zacznij ich używać:
{* Wyświetlenie obrazka z automatycznymi wymiarami *}
{asset 'logo.png'}
{* Dołączenie arkusza stylów z wersjonowaniem *}
{asset 'style.css'}
{* Wczytanie skryptu *}
{asset 'app.js'}
Dla większej kontroli nad generowanym HTML-em użyj atrybutu n:asset albo funkcji asset().
Jak to działa
Nette Assets zbudowane jest wokół trzech kluczowych pojęć, które czynią je potężnym, a zarazem prostym w użyciu:
Zasoby – Twoje pliki stają się sprytne
Zasób reprezentuje dowolny plik statyczny w Twojej aplikacji. Każdy plik staje się obiektem z przydatnymi właściwościami readonly:
$image = $assets->getAsset('photo.jpg');
echo $image->url; // '/assets/photo.jpg?v=1699123456'
echo $image->file; // '/var/www/assets/photo.jpg' (ścieżka lokalna albo null)
echo $image->width; // 1920
echo $image->height; // 1080
echo $image->mimeType; // 'image/jpeg'
Różne typy plików udostępniają różne właściwości:
- Obrazki: szerokość, wysokość, tekst alternatywny, leniwe wczytywanie
- Skrypty: typ modułu, hashe integralności, crossorigin
- Arkusze stylów: zapytania media, integralność
- Audio/wideo: czas trwania, wymiary (tylko wideo)
- Fonty: właściwe preładowanie z CORS
Biblioteka automatycznie wykrywa typy plików i tworzy odpowiednią klasę zasobu.
Mappery – skąd biorą się pliki
Mapper wie, jak znaleźć pliki i utworzyć dla nich URL-e. Możesz mieć wiele mapperów do różnych celów: pliki
lokalne, CDN, magazyn w chmurze albo narzędzia budujące (każdy z nich ma nazwę). Wbudowany FilesystemMapper
obsługuje pliki lokalne, a ViteMapper integruje się z nowoczesnymi narzędziami budującymi.
Mappery definiuje się w konfiguracji.
Rejestr – Twój główny interfejs
Rejestr zarządza wszystkimi mapperami i udostępnia główne API:
// Wstrzykujemy rejestr do swojej usługi
public function __construct(
private Nette\Assets\Registry $assets
) {}
// Pobieramy zasoby z różnych mapperów
$logo = $this->assets->getAsset('images:logo.png'); // mapper 'images'
$app = $this->assets->getAsset('app:main.js'); // mapper 'app'
$style = $this->assets->getAsset('style.css'); // używa mappera domyślnego
Rejestr automatycznie wybiera właściwy mapper i buforuje wyniki dla wydajności.
Praca z zasobami w PHP
Registry udostępnia dwie metody do pobierania zasobów:
// Rzuca Nette\Assets\AssetNotFoundException, jeśli plik nie istnieje
$logo = $assets->getAsset('logo.png');
// Zwraca null, jeśli plik nie istnieje
$banner = $assets->tryGetAsset('banner.jpg');
if ($banner) {
echo $banner->url;
}
Wskazywanie mapperów
Możesz jawnie wybrać, którego mappera użyć:
// Używamy mappera domyślnego
$file = $assets->getAsset('document.pdf');
// Używamy konkretnego mappera z prefiksem
$image = $assets->getAsset('images:photo.jpg');
// Używamy konkretnego mappera ze składnią tablicową
$script = $assets->getAsset(['scripts', 'app.js']);
Właściwości i typy zasobów
Każdy typ zasobu udostępnia odpowiednie właściwości readonly:
// Właściwości obrazka
$image = $assets->getAsset('photo.jpg');
echo $image->width; // 1920
echo $image->height; // 1080
echo $image->mimeType; // 'image/jpeg'
// Właściwości skryptu
$script = $assets->getAsset('app.js');
echo $script->type; // null ('module' dla punktów wejścia Vite)
// Właściwości audio
$audio = $assets->getAsset('song.mp3');
echo $audio->duration; // czas trwania w sekundach
// Wszystkie zasoby da się rzutować na ciąg (zwraca URL)
$url = (string) $assets->getAsset('document.pdf');
Właściwości takie jak wymiary czy czas trwania wczytywane są leniwie, dopiero przy dostępie, dzięki czemu biblioteka pozostaje szybka.
Dla precyzyjnej analizy statycznej zainstaluj rozszerzenie nette/phpstan-rules. PHPStan zna wtedy konkretny typ każdego
zasobu, więc getAsset('photo.jpg') rozumiane jest jako ImageAsset, a dostęp do ->width
nie zgłasza błędu.
Używanie zasobów w szablonach Latte
Nette Assets daje intuicyjną integrację z Latte przez tagi i funkcje.
{asset}
Tag {asset} renderuje kompletne elementy HTML:
{* Renderuje: <img src="/assets/hero.jpg?v=123" width="1920" height="1080"> *}
{asset 'hero.jpg'}
{* Renderuje: <script src="/assets/app.js?v=456"></script> *}
{asset 'app.js'}
{* Renderuje: <link rel="stylesheet" href="/assets/style.css?v=789"> *}
{asset 'style.css'}
Tag automatycznie:
- Wykrywa typ zasobu i generuje odpowiedni HTML
- Dodaje wersjonowanie na potrzeby cache bustingu
- Dodaje wymiary obrazków
- Ustawia poprawne atrybuty (type, media itd.)
Użyty wewnątrz atrybutów HTML albo wewnątrz elementów <style> i <script> wypisuje
sam URL:
<div style="background-image: url({asset 'bg.jpg'})">
<img srcset="{asset 'logo@2x.png'} 2x">
n:asset
Dla pełnej kontroli nad atrybutami HTML:
{* Atrybut n:asset uzupełnia src, wymiary itd. *}
<img n:asset="product.jpg" alt="Product" class="rounded">
{* Działa z dowolnym odpowiednim elementem *}
<script n:asset="analytics.js" defer></script>
<link n:asset="print.css" media="print">
<audio n:asset="podcast.mp3" controls></audio>
Używaj zmiennych i mapperów:
{* Zmienne działają naturalnie *}
<img n:asset="$product->image">
{* Mapper podajemy w nawiasach klamrowych *}
<img n:asset="images:{$product->image}">
{* Mapper podajemy zapisem tablicowym *}
<img n:asset="[images, $product->image]">
n:asset działa też na <a>, gdzie uzupełnia href, i na
<link>, gdzie tworzy podpowiedź preload:
<a n:asset="hero.jpg">Pobierz obrazek</a>
Zwróć uwagę, że warianty <a> i <link> działają tylko dla zasobów renderowalnych
(obrazek, skrypt itd.), nigdy dla GenericAsset, jak PDF.
Dla obrazków wystarczy ustawić samo width (albo samo height), a drugi wymiar zostanie wyliczony
automatycznie z zachowaniem proporcji:
{* height uzupełniane jest z proporcji *}
<img n:asset="product.jpg" width="200">
asset()
Dla maksymalnej elastyczności użyj funkcji asset():
{var $logo = asset('logo.png')}
<img src={$logo} width={$logo->width} height={$logo->height}>
{* Albo bezpośrednio *}
<img src={asset('logo.png')} alt="Logo">
Zasoby opcjonalne
Brakujące zasoby obsłużysz elegancko za pomocą {asset?}, n:asset? i tryAsset():
{* Tag opcjonalny - nie renderuje nic, jeśli zasobu brak *}
{asset? 'optional-banner.jpg'}
{* Atrybut opcjonalny - pomija, jeśli zasobu brak *}
<img n:asset?="user-avatar.jpg" alt="Avatar" class="avatar">
{* Z wartością zapasową *}
{var $avatar = tryAsset('user-avatar.jpg') ?? asset('default-avatar.jpg')}
<img n:asset=$avatar alt="Avatar">
{preload}
Popraw wydajność wczytywania strony:
{* W sekcji <head> *}
{preload 'critical.css'}
{preload 'important-font.woff2'}
{preload 'hero-image.jpg'}
Generuje odpowiednie odnośniki preload:
<link rel="preload" href="/assets/critical.css?v=123" as="style">
<link rel="preload" href="/assets/important-font.woff2?v=456" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.jpg?v=789" as="image" type="image/jpeg">
Gdy Twoja odpowiedź ustawia nagłówek Content-Security-Policy z nonce, Nette
automatycznie dodaje pasujący atrybut nonce do każdego wygenerowanego elementu <script>,
<link> i <style>, żeby nie zostały zablokowane przez politykę bezpieczeństwa
przeglądarki.
Funkcje zaawansowane
Automatyczne wykrywanie rozszerzenia
Obsługuj wiele formatów automatycznie:
assets:
mapping:
images:
path: img
extension: [webp, jpg, png] # Próbuj po kolei
Teraz możesz żądać bez rozszerzenia:
{* Znajduje automatycznie logo.webp, logo.jpg albo logo.png *}
{asset 'images:logo'}
Idealne do progresywnego ulepszania nowoczesnymi formatami.
Sprytne wersjonowanie
Pliki są automatycznie wersjonowane na podstawie czasu modyfikacji:
{asset 'style.css'}
{* Wyjście: <link rel="stylesheet" href="/assets/style.css?v=1699123456"> *}
Gdy zaktualizujesz plik, timestamp się zmieni, wymuszając odświeżenie cache przeglądarki.
Steruj wersjonowaniem dla poszczególnych zasobów:
// Wyłączamy wersjonowanie dla konkretnego zasobu
$asset = $assets->getAsset('style.css', ['version' => false]);
{* W Latte *}
{asset 'style.css', version: false}
Ta sama składnia referencja, klucz: wartość przekazuje opcje także do n:asset i
{preload}:
<img n:asset="photo.jpg, version: false">
{preload 'style.css', version: false}
Zasoby fontów
Fonty traktowane są specjalnie, z właściwym CORS:
{* Właściwy preload z crossorigin *}
{preload 'fonts:OpenSans-Regular.woff2'}
{* Użycie w CSS *}
<style>
@font-face {
font-family: 'Open Sans';
src: url('{asset 'fonts:OpenSans-Regular.woff2'}') format('woff2');
font-display: swap;
}
</style>
Własne mappery
Twórz własne mappery na specjalne potrzeby, jak magazyn w chmurze albo dynamiczne generowanie:
use Nette\Assets\Mapper;
use Nette\Assets\Asset;
use Nette\Assets\Helpers;
class CloudStorageMapper implements Mapper
{
public function __construct(
private CloudClient $client,
private string $bucket,
) {}
public function getAsset(string $reference, array $options = []): Asset
{
if (!$this->client->exists($this->bucket, $reference)) {
throw new Nette\Assets\AssetNotFoundException("Asset '$reference' not found");
}
$url = $this->client->getPublicUrl($this->bucket, $reference);
return Helpers::createAssetFromUrl($url);
}
}
Rejestracja w konfiguracji:
assets:
mapping:
cloud: CloudStorageMapper(@cloudClient, 'my-bucket')
Używaj jak każdego innego mappera:
{asset 'cloud:user-uploads/photo.jpg'}
Metoda Helpers::createAssetFromUrl() automatycznie tworzy właściwy typ zasobu na podstawie
rozszerzenia pliku.
Typy zasobów implementujące interfejs Nette\Assets\HtmlRenderable (obrazki, skrypty, style itd.) mogą być
renderowane przez {asset} jako kompletny element HTML. Pozostałe typy plików stają się GenericAsset
(na przykład PDF), którego nie da się wyrenderować jako elementu HTML, ale nadal daje URL (i inne metadane). Próba
wyrenderowania takiego zasobu jako elementu HTML rzuca Nette\InvalidArgumentException; jego URL możesz jednak nadal
użyć wewnątrz atrybutu.