Přechod z Mixu na Vite s Bootstrapem a Vue

Jak už asi víte, nedávno Taylor Otwell oznámil, že Laravel tým tvrdě pracuje na integraci Vite do Laravelu. Nedlouho poté je již možné začínat nové projekty s Breeze nebo Jetstream rovnou s Vite. Právě nejrychlejší způsobem jak začít nebo si vyzkoušet Vite je vytvořit si nový projekt:

laravel new breeze-test --git
cd breeze-test
composer require laravel/breeze
php artisan breeze:install vue
npm install
npm run dev

V tomto článku se nicméně zaměříme na již existující projekty, který využívá Laravel Mix (respektive webpack), Bootstrap 5 a Vue 2 a chceme přejít na Vite. Pro Vue 3 doporučuji využít oficiální dokumentaci Laravelu. Těžko říct jestli je Vite jen aktuální hype, nicméně pokud používáte Laravel Mix na takovou tu klasiku kdy chcete pouze kompilovat javascript, scss, kopírovat soubory atd., pak přechod určitě doporučuji. Rychlost kompilace ve Vite je nesrovnatelná s Mixem, stejně tak skvěle funguje hot reload, který lze snadno navázat i na blade (jak na to se také dozvíte v článku).

Instalace a konfigurace

Jako první je potřeba si nainstalovat všechny npm balíčky:

npm install vite laravel-vite-plugin @vitejs/plugin-vue2

Balíček laravel-vite-plugin je oficiální balíček Laravelu, díky čemuž lze velmi jednoduše kompilovat javascript do buildu. Balíček vitejs/plugin-vue2 je pak oficiální balíček Vite, díky čemuž můžeme kompilovat Vue komponenty.

Jedna z podmínek Vite je ta, že nemůžeme používat javascriptovou metodu require(), což může být v některých případech nepřekonatelná překážka. Pro tyto případy můžeme využít balíček @originjs/vite-plugin-require-context, který nám tyto problémy vyřeší.

Dalším krokem je vytvořit si v rootu projektu konfigurační soubor vite.config.js s tímto obsahem:

import {defineConfig} from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue2'
import path from 'path'
import ViteRequireContext from '@originjs/vite-plugin-require-context'

export default ({mode}) => {
    return defineConfig({
        plugins: [
            laravel([
                'resources/js/app.js',
            ]),
            vue(),
            ViteRequireContext(),
        ],
        resolve: {
            alias: {
                '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),
            },
            extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
        },
    });
}

Vysvětlím postupně (importy neřeším, ty jsou jasné). Jako základ je vracení funkce defineConfig, ve které definujeme naše nastavení. Poté je pole plugins, kde prakticky dáváme vše, s čím chceme v projektu pracovat a co se bude kompilovat.

V pluginu laravel si definujeme všechny javascriptové soubory (css soubory je potřeba importovat do javascriptu a budou se tak současně také kompilovat), které chceme zkompilovat. V našem případě je to pouze soubor app.js, který po zkompilování najdeme v adresáři public/build/assets. Nicméně si můžeme přidat kolik jen chceme javascriptových souborů a všechny se individuálně zkompilují do vlastního buildu. Následující pluginy vue a ViteRequireContext není potřeba více vysvětlovat.

V sekci resolve máme objekt alias. Ten slouží jako helper/alias pro akce, které chceme provádět v javascriptu. Abychom mohli využívat Bootstrap v projektu, je potřeba přidat alias ~bootstrap, který Bootstrap využívá ve svým zdrojáku a bez kterého se neobejdeme. V objektu extensions je pak pole souborů, které má Vite hlídat a kompilovat.

Použítí

Protože jsme si v konfigu definovali pouze jeden javascriptový soubor, app.js, který se má zkompilovat, tak stačí do <head> přidat @vite(['resources/js/app.js']) a nahradit tak původní z Mixu mix('js/app.js') a mix('css/app.css').

V app.js přidáme vše co potřebujeme v projektu:

import '../sass/app.scss';

import * as bootstrap from 'bootstrap';  
window.bootstrap = bootstrap;

import Vue from 'vue/dist/vue.js'
window.Vue = Vue;

Dále pak v package.json nahradíme skripty:

"scripts": {  
  "dev": "vite",  
  "build": "vite build",  
},

Kde npm run dev spustí "hlídání" nad soubory, takže pokud například upravíte vue komponentu, změna se okamžitě po uložení projeví v browseru (klasický hot který ale nemusíme nijak nastavovat a funguje automaticky). Skript npm run build slouží ke kompilaci a hodí se především na produkci nebo do pipeline nebo kdykoliv potřebujeme zbuildovat všechno. Osobně jsem si přidal ještě další dva skripty:

"build-dev": "vite build -m development",
"watch": "vite build --watch"

kdy build-dev funguje jako klasický build ale je v něm mód development, takže ve vue například vidíme i debugbar v inspectu prohlížeče. Skript watch funguje podobně jako dev, tedy si hlídá změny, ale nedělá hot, což v některých případech, např. při debuggingu, může být výhodnější.

A to je prakticky vše. Samozřejmě že každý projekt se liší a proto je velká pravděpodobnost, že další věci budete ještě dohledávat, nicméně toto může stačí jako základ.

Bonus: jQuery

Pokud máte v projektu ještě stále jQuery, pak ho stačí naimportovat do app.js:

import jQuery from "jquery";
import $ from "jquery";
window.$ = $;

Alias samozřejmě záleží na vás jaký v projektu používáte

Bonus 2: Blade hot reload

Vite si defaultně hlídá pouze ty soubory, které definujeme v konfigu a které lze snadno zbuildovat. Kromě toho také nabízí hot reload také pro Blade, tedy při změně blade šablony se vám rovnou přenačte celá stránky (narozdíl od Vue kde se přenačte pouze jedna komponenta). Protože ne vždy je tato feature žádoucí, vytvořil jsem si v .env boolean proměnnou VITE_HOT_BLADE, která určuje jestli je hot reload blade zapnutý nebo ne. Zde je celý výsledný Vite konfig, kde nás zajímá funkce handleHotUpdate():

import {defineConfig, loadEnv} from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue2'
import path from 'path'
import ViteRequireContext from '@originjs/vite-plugin-require-context'

export default ({mode}) => {
	process.env = {...process.env, ...loadEnv(mode, process.cwd())};

    return defineConfig({
        plugins: [
            laravel([
                'resources/js/app.js',
            ]),
            vue(),
            ViteRequireContext(),
            {
                name: 'blade',
                handleHotUpdate({file, server}) {
                    if (process.env.VITE_HOT_BLADE === 'true' && file.endsWith('.blade.php')) {
                        server.ws.send({
                            type: 'full-reload',
                            path: '*',
                        });
                    }
                },
            }
        ],
        resolve: {
            alias: {
                '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),
            },
            extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
        },
    });
}