Nette Vite – použití Nette s Vite pro rychlý lokální vývoj

před 8 měsíci od Lubomír Blažek  

Vite je frontendový nástroj nové generace, sloužící k načítání JS a CSS souborů přímo ze zdrojů, bez nutnosti kompilace a za použití moderních standardů.

Kdekoho určitě štve, že když udělá úpravu na frontendu, tak musí kompilovat a zbytečně čekat, a poté spouštět kompilaci stále dokola při každé úpravě. Mnohdy je navíc potřeba systém složitě konfigurovat. S tím je naštěstí díky moderním nástrojům a přístupům konec.

Moduly v JavaScriptu se dají načítat mnoha způsoby, ten nejznámější je CommonJS, s kterým se setkáte v Node.js. Ten však nebude fungovat v prohlížeči, pokud teda nepoužijete knihovnu jako RequireJS.

Moderní a standardizovaný formát, který funguje v Node.js i prohlížeči, jsou ES moduly (ESM). Teoreticky lze psát zdrojový kód s ES moduly a s použitím importmap, což jednou bude fungovat v prohlížečích, aniž byste museli cokoliv kompilovat. Zatím to podporuje pouze Chrome, pro ostatní prohlížeče je nutné použít es-module-shim.

Vite používá obdobný přístup a používá výhradně ES moduly – zatím však bez importmap. Závislosti bundluje pomocí esbuild. Ten je napsán v Go a bundluje 10–100× rychleji než ostatní nástroje. Lokální server pak transformuje cesty k modulům tak, aby jim prohlížeč rozuměl. V současnosti je většina knihoven pořád napsaná v CommonJS, takže ty Vite transformuje do ESM syntaxe, aby šly načíst v prohlížeči. Závislosti pak bundluje přímo za chodu a jen když je to potřeba. Váš vlastní kód není nutné kompilovat, ten je už napsaný v moderním ES formátu, kterému prohlížeč rozumí.

// zdrojový soubor
import { Application, Controller } from "@hotwired/stimulus";

Pro zajímavost, s import-mapami lze načíst zdrojový kód tak, jak je, např. z CDN esm.sh. Jde o alternativní přístup ten však Vite zatím nepodporuje.

// načtený soubor v prohlížeči (přes Vite)
import { Application, Controller } from "/node_modules/.vite/@stimulus_core.js?v=02a6d100";

Takže v projektu načítáte JavaScript přímo ze zdrojových souborů a není potřeba nic kompilovat. Vite to řeší hned na vyžádání a díky Hot Module Replacement (HMR) se kompilují jen změněné soubory. Vite vám však musí běžet na pozadí, ale pokud používáte lokální vývoj, tak to není žádný problém. Lze to dokonce využít i na remote serveru, kdy se CSS a JS načítají z localhostu.

Pro načítání zdrojů si vytvoříme jednoduchou třídu, která bude rozlišovat, jestli se mají načítat soubory ze zdrojů (při vývoji) nebo buildnuté soubory (produkce):

use Nette\Utils\FileSystem;
use Nette\Utils\Html;
use Nette\Utils\Json;


class ViteAssets
{
    public function __construct(
        private string $viteServer,
        private string $manifestFile,
        private bool $productionMode,
    ){}

    public function printTags(string $entrypoint): void
    {
        $scripts = [];
        $styles = [];
        $baseUrl = '/';

        if ($this->productionMode) {
            if (file_exists($this->manifestFile)) {
                $manifest = Json::decode(FileSystem::read($this->manifestFile), Json::FORCE_ARRAY);
                $scripts = [$manifest[$entrypoint]['file']];
                $styles = $manifest[$entrypoint]['css'] ?? [];
            } else {
                trigger_error('Missing manifest file: ' . $this->manifestFile, E_USER_WARNING);
            }

        } else {
            $baseUrl = $this->viteServer . '/';
            $scripts = ['@vite/client', $entrypoint];
        }

        foreach ($styles as $path) {
            echo Html::el('link')->rel('stylesheet')->href($baseUrl . $path);
        }

        foreach ($scripts as $path) {
            echo Html::el('script')->type('module')->src($baseUrl . $path);
        }
    }
}

Jednoduše třídu nakonfigurujeme a předáme do presenteru:

services:
	- ViteAssets(http://localhost:3000, %wwwDir%/manifest.json, not(%debugMode%))
abstract class BasePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ViteAssets $viteAssets,
	) {
	}

    public function beforeRender(): void
    {
		$this->template->viteAssets = $this->viteAssets;
    }
}

A použijeme v šabloně layoutu:

{$viteAssets->printTags('src/main.js')}

Příklad integrace Nette s Vite jsem vytvořil na GitHubu, vychází to z web-project.

Do Tracy jsem přidal rozšíření pro Vite, kde lze pomocí ikonky načítání přes Vite zapnout nebo vypnout na základě uložené cookie v prohlížeči.

Pro lokální vývoj je tam přidaný Docker Image od Josefa Jebavého. Takže stačí mít nainstalovaný Docker a Node.js 14+ a nemusíte mít vlastně v systému ani PHP a Composer. Jako starter pack pro front-end jsem pak přidal následující knihovny.

  • Tailwind – utility třídy na všechno
  • Stimulus – na bindování eventů a controllerů
  • Turbo – automatická navigace mezi stránkami bez refreshe, dál se tím dá řešit live načítání obsahů nebo překreslování snippetů

Nejdříve nainstalujte závislosti Node.js balíčků pomocí npm i. Po instalaci spustíte lokální web server pomocí docker compose up, Vite spustíte pomocí npm run dev.

S Vite jsou rovněž využité Web Sockety, takže při jakékoliv změně v .php, .latte nebo .css, .js souborů se prohlížeč automaticky přenačte a načte aktuální data.

Pro produkci se potom pouští npm run build což buildne zdrojové JS a CSS soubory do www/assets a do www přidá manifest.json kde jsou revize jednotlivých souborů.

Je to vlastně hodně podobný přístup jako má Laravel Vite

Vite se konfiguruje v souboru vite.config.js projektu a základní konfigurace vypadá takto.

export default {
    build: {
        manifest: true,
        outDir: "www",
        emptyOutDir: false,
        rollupOptions: {
            input: '/src/scripts/main.js'
        }
    }
}

Jediná současná nevýhoda Vite je, že se CSS soubory musí includovat v JS (import "/src/styles/main.css"), aby šly buildovat. Ve výsledku se v buildu sice rozdělí zvlášť, ale někomu tenhle zápis může vadit. CSS lze také nalinkovat přímo přes <link rel="stylesheet" href="src/styles/main.css">. Pokud tak uděláte, tak je pořád nutné v JS includovat aby se vytvořil záznam v manifestu. Lze využít např. takovýto hack:

	if (typeof window[9999] !== 'undefined') {
	  (async() => await import('/src/styles/main.css'))()
	}

Komu se ani jedno řešení nelíbí, tak nezbývá než počkat dokud problém nevyřeší v následujícím issue – https://github.com/…/issues/6595. Nebo lze pro build využít alternativně třeba gulp-vite, kde se build může řešit přes Gulp a Esbuild, Vite pak lze použít na možnost načítání věcí ze zdrojů, Web Sockety apod.

Takový přístup jsme využili např. v Newlogic Core, kde Vite je použitý jako lokální server a vše ostatní se řeší Gulpem. Myšlenka tohoto projektu byla mít v jednom balíčku všechny moderní nástroje pro vývoj – jako PostCSS, PurgeCSS, Tailwind apod. Příklad integrace Newlogic Core s Nette na GitHubu

Pokud s Nette použijete kombinaci Vite, Stimulus a Naja tak získáte moderní a lehce spravovatelný frontend. Pro plnohodnotnou ukázku využití Vite a moderních frontend přístupů doporučuji mrknout také na Newlogic UI.

Pokud někoho zajímá srovnání frontendových nástrojů za posledních 10 let a jak jsme vyvíjeli Newlogic Core & UI, tak se může mrknout na článek.

Komentáře (RSS)

  1. nemam node_modules v public slozce projektu, mam ho na rootu projektu

    před 8 měsíci

Chcete-li odeslat komentář, přihlaste se