Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Budowanie wersji produkcyjnej

Przez trzy poprzednie lekcje sprawdzałeś systemy stacji: komponenty, composables, store, wreszcie całe ścieżki operatora w testach E2E. Wszystkie kontrolki świecą na zielono - świecą jednak w laboratorium NOVA LAB, na Twojej maszynie, przy uruchomionym serwerze deweloperskim. Panel telemetrii ma tymczasem trafić na lądownik: do przeglądarki technika, który otwiera go z drugiego końca stacji, przez łącze o opóźnieniu liczonym w minutach. Serwer deweloperski tam z Tobą nie poleci.

Polecenie

npm run dev
, którego używasz od pierwszej lokacji, podaje przeglądarce Twoje pliki źródłowe niemal w oryginale. Każdy plik
.vue
transformuje dopiero wtedy, gdy przeglądarka o niego poprosi, a resztę pracy zostawia natywnemu systemowi modułów przeglądarki. Dzięki temu start jest natychmiastowy, ale ceną są setki osobnych żądań, brak minifikacji i katalog
node_modules
czekający w tle. Do wysyłki potrzebujesz czegoś dokładnie odwrotnego: jednego, policzonego i spakowanego ładunku, który przeglądarka na drugim końcu łącza pobierze w kilku plikach.

Jedno polecenie, które pakuje ładunek

Za spakowanie odpowiada osobny tryb Vite, uruchamiany skryptem

build
. Vite przełącza się wtedy z serwowania plików na budowanie: wczytuje
index.html
jako punkt wejścia, przechodzi po wszystkich importach, kompiluje każdy komponent, a następnie oddaje całość Rollupowi, który skleja moduły w pakiety i minifikuje wynik. To ta sama komenda w każdym projekcie Vue postawionym na Vite, niezależnie od tego, czy budujesz panel telemetrii, czy grę dla załogi.

1npm run build

Po kilkunastu sekundach w katalogu projektu pojawia się folder

dist
. Zwróć uwagę, co się przy tym nie zmieniło: ani jeden Twój plik źródłowy. Build wyłącznie czyta katalog
src
, a zapisuje do
dist
- dlatego możesz go uruchamiać dowolnie często i dlatego
dist
trafia do
.gitignore
, a nie do repozytorium.

Zapamiętaj nazwę tego skryptu dokładnie, @name, bo tu najłatwiej o pomyłkę.

npm run
nie zna żadnych magicznych komend - uruchamia wyłącznie skrypty wpisane do sekcji
scripts
w pliku
package.json
, a szablon Vue z Vite zakłada dokładnie trzy:
dev
,
build
i
preview
. Poleceń
npm run prod
ani
npm run compile
nie znajdziesz w żadnym standardowym szablonie i skończą się komunikatem o brakującym skrypcie.
npm run deploy
bywa dopisywany ręcznie, ale to skrypt wysyłki, a nie budowania - jeśli już go w projekcie zobaczysz, prawie na pewno sam w środku wywołuje
npm run build
, bo najpierw trzeba mieć co wysłać.

Podgląd ładunku przed startem

Zbudowana aplikacja bywa pierwszym miejscem, w którym wychodzą usterki niewidoczne w laboratorium: zła ścieżka bazowa, obrazek doklejany dynamicznie, biblioteka działająca tylko dzięki temu, że serwer deweloperski coś za nią załatwiał. Dlatego Vite ma trzeci skrypt,

preview
. Podnosi on prosty serwer plików statycznych i serwuje z niego zawartość
dist
- dokładnie te pliki, które za chwilę wyślesz. Adres wypisze w konsoli; jest inny niż adres serwera deweloperskiego, więc oba mogą działać jednocześnie. Obie komendy warto uruchamiać jedna po drugiej, spięte podwójnym ampersandem.

1npm run build && npm run preview

Znak

&&
to zwykły operator powłoki: druga komenda wystartuje tylko wtedy, gdy pierwsza zakończy się powodzeniem. Jeśli build przerwie się na błędzie importu, podgląd w ogóle się nie podniesie i nie obejrzysz przypadkiem poprzedniej, nieaktualnej zawartości
dist
. I jedno zastrzeżenie, żeby nie było nieporozumień:
preview
jest narzędziem kontrolnym, a nie serwerem produkcyjnym. Nie ma kompresji, nagłówków pamięci podręcznej ani certyfikatu - służy do obejrzenia ładunku, nie do jego udostępniania załodze.

Co naprawdę leży w folderze dist

Skoro

dist
to wszystko, co poleci na lądownik, warto wiedzieć, co dokładnie w nim wyląduje. Struktura jest za każdym razem taka sama: jeden plik
index.html
w korzeniu i katalog
assets
z całą resztą. Poniżej typowa zawartość po zbudowaniu panelu z kilkoma widokami.

1dist/
2  index.html
3  assets/
4    index-4f3a9b1c.js
5    index-9c2e7d84.css
6    vendor-1b7e0a52.js
7    TelemetryView-6d41f0ab.js

Ciąg znaków w nazwie pliku to skrót policzony z jego zawartości. Zmienisz linijkę w kodzie - zmieni się skrót, a więc i nazwa pliku, więc przeglądarka technika nie ma jak podać starej wersji z pamięci podręcznej. Nie musisz przy tym niczego poprawiać ręcznie:

index.html
w
dist
ma już przepisane odwołania do nowych nazw. To dlatego build generuje własny
index.html
, zamiast kopiować Twój bez zmian.

W

dist
znajdziesz wyłącznie zminifikowane pliki JS, CSS i HTML gotowe do wdrożenia. Nie ma tam ani jednego pliku
.vue
- komponenty zostały skompilowane do funkcji renderujących i wtopione w pakiety. Nie ma
node_modules
: te fragmenty bibliotek, z których faktycznie korzystasz, siedzą wewnątrz pakietów, a reszta została odrzucona. Nie ma też
vite.config.js
ani plików konfiguracyjnych laboratorium - to wejście dla procesu budowania, a nie jego wynik.

Jest jeden folder, który łatwo pomylić z wynikiem:

public
. On również jest wejściem - wrzucasz do niego pliki, które mają trafić do
dist
bez żadnej obróbki, na przykład
robots.txt
czy ikonę stacji. Nazwy
build
i
out
też krążą po dokumentacjach, ale należą do innych narzędzi:
build
to konwencja projektów spod Create React App i webpacka,
out
to katalog eksportu statycznego w Next.js. W Vite domyślnym katalogiem wyjściowym jest
dist
- i tam szukaj wyniku po każdym buildzie.

Sekcja build w pliku vite.config.js

Domyślne ustawienia są rozsądne, ale prędzej czy później misja wymusi zmiany: hosting oczekuje innej nazwy katalogu, ktoś prosi o mapy źródeł, ktoś inny o mocniejszą minifikację. Wszystko to opisujesz w pliku

vite.config.js
, w obiekcie
build
. Cztery opcje, od których zaczniesz, brzmią tak:
outDir
to nazwa katalogu wyjściowego,
assetsDir
to podkatalog na pakiety i style wewnątrz niego,
sourcemap
decyduje, czy obok pakietów powstaną mapy pozwalające debugować zminifikowany kod w kategoriach Twoich plików źródłowych, a
minify
wskazuje narzędzie, które ten kod skraca.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6  plugins: [vue()],
7
8  build: {
9    outDir: 'dist',
10    assetsDir: 'assets',
11    sourcemap: false,
12    minify: 'esbuild'
13  }
14})

Ten plik nie zmienia niczego - i to jest w nim najciekawsze. Wpisałeś dokładnie te wartości, które Vite przyjmuje sam, więc build zachowa się identycznie jak przed chwilą. Traktuj go jak listę kontrolną: teraz wiesz, gdzie sięgnąć, gdy hosting zażąda katalogu o innej nazwie. Funkcja

defineConfig
niczego nie konfiguruje, tylko podpowiada edytorowi typy, dzięki czemu literówka w nazwie opcji podświetli się od razu. Mapy źródeł polecam trzymać wyłączone w aplikacji wystawionej publicznie, bo razem z nimi publikujesz czytelny kod źródłowy panelu; jeśli potrzebujesz ich do systemu raportowania błędów, ustaw
sourcemap: 'hidden'
- mapy powstaną, ale pakiet nie będzie do nich odsyłał.

Trzy inne opcje z tej sekcji zostaw na razie w spokoju, warto jednak znać je z nazwy.

assetsInlineLimit
wyznacza granicę w bajtach, poniżej której drobny plik, na przykład mała ikona SVG, zostanie wklejony wprost do kodu jako data URI, zamiast lądować osobno - o jedno żądanie mniej.
cssCodeSplit
włącza dzielenie stylów na części odpowiadające pakietom.
chunkSizeWarningLimit
to próg, po przekroczeniu którego Vite wypisze ostrzeżenie o zbyt dużym pakiecie; liczy on rozmiar po minifikacji, ale przed kompresją serwera, więc realny transfer będzie mniejszy niż liczba, którą zobaczysz w konsoli.

Wyciszanie konsoli, czyli minifikator terser

W kodzie panelu na pewno zostało kilka wywołań

console.log
- w laboratorium nieocenionych, na lądowniku zbędnych. Wycinaniem takich wywołań zajmuje się minifikator, ale nie każdy to potrafi. Domyślnym minifikatorem Vite jest
esbuild
: jest bardzo szybki i to on odpowiada za większość skrócenia kodu. Alternatywą jest
terser
, wolniejszy, za to z drobiazgowym zestawem przełączników - między innymi
drop_console
i
drop_debugger
, które usuwają z pakietu odpowiednio wywołania konsoli oraz instrukcje
debugger
. Ustawienia terser podajesz w obiekcie
terserOptions
, w sekcji
compress
.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6  plugins: [vue()],
7
8  build: {
9    minify: 'terser',
10    terserOptions: {
11      compress: {
12        drop_console: true,
13        drop_debugger: true
14      }
15    }
16  }
17})

Jest tu pułapka, o której konfiguracje krążące po sieci zwykle milczą: Vite nie instaluje terser razem ze sobą. Jeśli wpiszesz

minify: 'terser'
bez dodania paczki
terser
do zależności deweloperskich, build przerwie się komunikatem, że minifikator jest niedostępny - wystarczy wtedy
npm install -D terser
. Zwróć też uwagę, czego ta konfiguracja nie robi: nie rusza Twoich plików źródłowych ani trybu deweloperskiego.
console.log
zostaje w kodzie i nadal wypisuje w konsoli przy
npm run dev
, znika wyłącznie z zawartości
dist
.

Moja rekomendacja, @name: zostań przy domyślnym

esbuild
tak długo, jak długo nie potrzebujesz konkretnie wycinania logów - build jest wtedy wyraźnie szybszy, a różnica w rozmiarze pakietu wychodzi niewielka. Po
terser
sięgaj świadomie, dla tej jednej możliwości. Istnieje jeszcze opcja
esbuild.drop
, ale działa ona na poziomie całej transformacji, a nie tylko kroku budowania, więc łatwiej nią przypadkiem uciszyć także laboratorium.

Dzielenie ładunku na pakiety

Domyślnie Vite skleja Twój kod w jeden pakiet, a to ma nieprzyjemny skutek uboczny przy aktualizacjach. Poprawiasz jedną etykietę w panelu, nazwa pliku zmienia się przez nowy skrót, a technik pobiera od nowa całość - razem z Vue, routerem i Pinią, które nie drgnęły od miesięcy. Lekarstwem jest rozdzielenie kodu na pakiety o różnym tempie zmian. Służy do tego opcja

manualChunks
, którą podajesz w
rollupOptions.output
, czyli w ustawieniach przekazywanych wprost Rollupowi. W najprostszej postaci jest to obiekt: klucz staje się nazwą przyszłego pakietu, a wartością jest lista modułów, które mają do niego trafić.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6  plugins: [vue()],
7
8  build: {
9    rollupOptions: {
10      output: {
11        manualChunks: {
12          vendor: ['vue', 'vue-router', 'pinia']
13        }
14      }
15    }
16  }
17})

Po takim buildzie w katalogu

assets
zobaczysz osobny plik zaczynający się od słowa
vendor
. Biblioteki wylądowały w nim razem i dopóki nie zmienisz ich wersji, jego nazwa pozostanie ta sama - przeglądarka pobierze go raz i zatrzyma w pamięci podręcznej mimo kolejnych wydań panelu. Co się przy tym nie zmieniło: nie dotknąłeś ani jednego importu w kodzie. Nadal piszesz
import { ref } from 'vue'
dokładnie tam, gdzie pisałeś, a decyzję o rozdziale plików podejmuje wyłącznie build.

Jedna reguła zamiast wyliczanki

Ręczna lista sprawdza się przy trzech bibliotekach. Gdy

package.json
puchnie, wygodniej podać regułę niż wymieniać nazwy jedna po drugiej.
manualChunks
przyjmuje bowiem także funkcję: Rollup wywoła ją dla każdego modułu, przekazując jego ścieżkę jako argument
id
, a zwrócony napis potraktuje jako nazwę pakietu. Gdy funkcja nie zwróci niczego, moduł trafi tam, gdzie trafiłby domyślnie.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6  plugins: [vue()],
7
8  build: {
9    rollupOptions: {
10      output: {
11        manualChunks(id) {
12          if (id.includes('node_modules')) {
13            return 'vendor'
14          }
15        }
16      }
17    }
18  }
19})

Ta wersja mówi krótko: cokolwiek pochodzi z

node_modules
, ląduje w pakiecie
vendor
, a wszystko inne zostaje bez zmian. Dokładając kolejny warunek, możesz wydzielić pojedynczą, ciężką bibliotekę - na przykład silnik wykresów telemetrii, potrzebny tylko na jednym ekranie. Skoro masz wybór, powiem wprost, co polecam: zacznij od postaci z obiektem, bo jest czytelna i dokładnie widzisz, co gdzie ląduje. Po funkcję sięgaj dopiero wtedy, gdy lista zaczyna rozjeżdżać się z rzeczywistością - zbyt agresywna reguła potrafi rozbić pakiety tak, że przeglądarka pobiera na starcie więcej plików, niż potrzebuje.

Parametry misji, czyli zmienne środowiskowe

Panel telemetrii pyta o dane serwer misji. W laboratorium ten serwer stoi na Twojej maszynie, na lądowniku - pod zupełnie innym adresem. Wpisanie adresu na sztywno w kod oznacza, że przed każdym buildem ktoś musi pamiętać o jego podmianie; prędzej czy później zapomni i produkcyjny panel zacznie pukać do localhosta. Vite rozwiązuje to plikami

.env
, czytanymi w chwili budowania. Plik
.env
obowiązuje zawsze,
.env.production
dokłada się do niego przy buildzie produkcyjnym, a
.env.development
przy serwerze deweloperskim, przy czym wartości z pliku bardziej szczegółowego wygrywają.

1# .env
2VITE_API_URL=http://localhost:3000
3
4# .env.production
5VITE_API_URL=https://telemetry.nova-lab.mars/api

Prefiks

VITE_
nie jest ozdobnikiem, tylko bramką bezpieczeństwa. Do kodu, który trafia do przeglądarki, Vite wpuszcza wyłącznie zmienne z tym prefiksem; wszystkie pozostałe zostają po stronie procesu budowania. Wniosek jest ostrzejszy, niż się na pierwszy rzut oka wydaje: skoro zmienna z prefiksem ląduje w pakiecie, to każdy, kto otworzy
dist
, przeczyta jej wartość. Adresy i flagi funkcji - tak; klucze i hasła - nigdy.

Tu też siedzi klasyczna pomyłka. Prefiks

VUE_APP_
i odczyt przez
process.env
to konwencja Vue CLI, czyli poprzednika opartego na webpacku. W projekcie na Vite obiekt
process
w przeglądarce po prostu nie istnieje i taki odczyt kończy się błędem. Nie pomoże również własny plik
config.js
z dowolnie nazwanymi zmiennymi - to zwykły moduł, który wjedzie do pakietu tak samo jak reszta kodu, tyle że bez jakiegokolwiek mechanizmu przełączania wartości między środowiskami. I na wszelki wypadek: twierdzenie, że Vite w ogóle nie obsługuje zmiennych środowiskowych, jest nieprawdziwe. Obsługuje, tylko na własnych zasadach.

Odczyt zmiennej w kodzie

Po stronie kodu zmienne czytasz z obiektu

import.meta.env
. Samo
import.meta
to standardowy element JavaScriptu - obiekt z metadanymi bieżącego modułu - a Vite dokłada do niego pole
env
. Pełny zapis składa się więc z trzech części w tej właśnie kolejności:
import.meta
, potem
.env
, potem nazwa zmiennej razem z prefiksem, czyli
import.meta.env.VITE_API_URL
.

1// api/telemetry.js
2const apiUrl = import.meta.env.VITE_API_URL
3
4export async function fetchModuleTelemetry(moduleId) {
5  const response = await fetch(`${apiUrl}/modules/${moduleId}`)
6
7  if (!response.ok) {
8    throw new Error('Telemetry request failed')
9  }
10
11  return response.json()
12}

Ważne, żebyś rozumiał, co się tutaj naprawdę dzieje. To nie jest odczyt z jakiegoś słownika w czasie działania aplikacji - Vite podmienia całe wyrażenie

import.meta.env.VITE_API_URL
na literał tekstowy jeszcze przy budowaniu. W pliku w
dist
nie ma śladu po
import.meta
, jest gotowy adres. Ma to jedną praktyczną konsekwencję: nazwę zmiennej trzeba zapisać dosłownie. Sklejanie klucza w locie, z prefiksu i nazwy trzymanej w zmiennej, nie zostanie podmienione i da w wyniku
undefined
.

Tryb, w którym działa aplikacja

Obok Twoich zmiennych Vite wstawia do

import.meta.env
kilka własnych. Pierwsza z nich to
MODE
, czyli nazwa trybu zapisana tekstem: przy zwykłym
npm run dev
będzie to
development
, a po
npm run build
-
production
. Kolejne dwie,
DEV
i
PROD
, niosą tę samą informację w postaci wartości logicznych, przez co wygodnie używa się ich w warunkach.

1if (import.meta.env.DEV) {
2  console.log('Mission control mode:', import.meta.env.MODE)
3}

Ten warunek nie kosztuje ani bajta w wersji produkcyjnej. Skoro

import.meta.env.DEV
jest podmieniane na literał, w pakiecie produkcyjnym powstaje warunek zawsze fałszywy, a minifikator wyrzuca całą martwą gałąź razem z jej zawartością. To bardzo wygodny wzorzec na panele diagnostyczne dla załogi laboratorium: piszesz je normalnie, obok reszty kodu, a technik na lądowniku nawet ich nie pobiera.

Tryby nie kończą się na tych dwóch nazwach. Polecenie

npm run build -- --mode staging
zbuduje aplikację w trybie o nazwie
staging
i wczyta plik
.env.staging
- to standardowy sposób na środowisko testowe, stojące gdzieś pomiędzy laboratorium a lądownikiem. Podwójny dywiz przed
--mode
jest konieczny, bo to on każe menedżerowi pakietów przekazać argument dalej, do Vite.
MODE
będzie wtedy równe
staging
, natomiast
PROD
domyślnie pozostanie prawdziwe, bo o tej fladze decyduje rodzaj polecenia, a nie nazwa trybu.

Co Vite robi bez pytania

Zanim zaczniesz cokolwiek dokręcać, warto wiedzieć, ile pracy build wykonuje za Ciebie i - co ważniejsze - która technika za co odpowiada, bo te cztery nazwy bywają mylone.

Tree shaking to wycinanie nieużywanego kodu. Rollup czyta importy i eksporty modułów ES, buduje z nich graf zależności i usuwa z pakietu każdy eksport, do którego nikt się nie odwołuje. Importujesz jedną funkcję z dużej biblioteki - do

dist
trafi ta jedna funkcja wraz z tym, czego sama potrzebuje. To dlatego statyczne importy na górze pliku są dziś standardem: dopiero one dają narzędziu pewność, co jest naprawdę używane.

Minifikacja to skracanie kodu, który zostaje. Znikają spacje, wcięcia i komentarze, a nazwy zmiennych lokalnych zamieniają się w pojedyncze litery. Zauważ różnicę: minifikator nie zastanawia się, czy dany moduł jest komukolwiek potrzebny, on go wyłącznie ściska. Samo usuwanie komentarzy jest zaledwie drobnym fragmentem tej pracy i z pewnością nie tym mechanizmem, który usuwa nieużywany kod.

Code splitting to podział wyniku na kilka plików - dokładnie to, co robiłeś przed chwilą przy

manualChunks
. Tu nic nie ginie, kod zostaje jedynie przeniesiony do osobnego pakietu.

Lazy loading jest natomiast strategią w czasie działania aplikacji: pakiet powstały z podziału pobierany jest dopiero wtedy, gdy użytkownik faktycznie go potrzebuje. To skutek code splittingu, a nie osobna technika czyszczenia kodu.

Warto jeszcze rozprawić się z dwoma nieporozumieniami. Build niczego nie pakuje do archiwum ZIP - przeglądarka nie umiałaby takiego pliku uruchomić. Kompresja oczywiście istnieje, nazywa się gzip albo brotli, dzieje się na poziomie transportu HTTP, odpowiada za nią serwer i nie ma nic wspólnego z usuwaniem kodu. Nieprawdą jest również, że Vite niczego nie optymalizuje i trzeba do tego zewnętrznego narzędzia:

vite build
to Rollup z kompletem optymalizacji włączonym domyślnie.

Leniwe trasy w praktyce

Code splitting nie musi być ręczny. Najskuteczniejszy podział bierze się z importu dynamicznego, czyli wywołania

import()
w środku funkcji zamiast statycznego
import
na górze pliku. Rollup traktuje takie miejsce jak granicę: wszystko, co za nią, wędruje do osobnego pakietu. W routerze wygląda to tak, że zamiast gotowego komponentu podajesz funkcję, która dopiero go sprowadzi.

1// router/index.js
2import { createRouter, createWebHistory } from 'vue-router'
3import MissionControl from '../views/MissionControl.vue'
4
5const routes = [
6  { path: '/', component: MissionControl },
7  { path: '/telemetry', component: () => import('../views/TelemetryView.vue') },
8  { path: '/crew', component: () => import('../views/CrewView.vue') }
9]
10
11export default createRouter({
12  history: createWebHistory(),
13  routes
14})

Ekran startowy wchodzi do głównego pakietu, bo technik zobaczy go zawsze. Dwa pozostałe widoki dostają własne pliki, pobierane dopiero przy pierwszym wejściu na daną trasę - to właśnie taką nazwę widziałeś w przykładowej zawartości

dist
. Co się nie zmieniło: same komponenty. Plik
TelemetryView.vue
wygląda identycznie jak wcześniej, zmienił się wyłącznie sposób, w jaki router go wskazuje.

Zmierz, zanim zaczniesz optymalizować

Optymalizowanie na wyczucie zwykle kończy się dokręcaniem rzeczy, które ważą tyle co nic. Dlatego zanim ruszysz cokolwiek poza domyślne ustawienia, zmierz. Do pomiarów służy wtyczka

rollup-plugin-visualizer
, dokładana do zależności deweloperskich poleceniem
npm install -D rollup-plugin-visualizer
, a potem dopisywana do listy
plugins
. Opcja
open
otwiera raport w przeglądarce zaraz po zakończeniu builda, natomiast
gzipSize
i
brotliSize
dokładają do niego rozmiary po kompresji.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4import { visualizer } from 'rollup-plugin-visualizer'
5
6export default defineConfig({
7  plugins: [
8    vue(),
9    visualizer({
10      open: true,
11      gzipSize: true,
12      brotliSize: true
13    })
14  ]
15})

Raport jest mapą prostokątów: im większe pole, tym cięższy moduł. Zwykle wystarcza jedno spojrzenie, żeby zobaczyć, że połowę pakietu zjada jedna biblioteka wciągnięta dla jednej funkcji. Patrz przede wszystkim na rozmiary skompresowane - to one odpowiadają temu, co naprawdę przejdzie przez łącze do lądownika, bo rozmiar surowy bywa kilkukrotnie większy i niepotrzebnie przeraża. Sama wtyczka niczego w pakiecie nie zmienia, wyłącznie mierzy, dlatego trzymaj ją na końcu listy

plugins
.

Obrazy ważą więcej niż kod

Ostatni przystanek, bo w typowym panelu to nie kod waży najwięcej. Jedno nieobrobione zdjęcie powierzchni Marsa potrafi przeważyć całą aplikację razem z Vue. Vite nie kompresuje obrazów samodzielnie - przenosi je do

dist
i dokłada skrót do nazwy, ale nie rusza zawartości. Od kompresji jest wtyczka
vite-plugin-image-optimizer
, instalowana przez
npm install -D vite-plugin-image-optimizer
. Potrzebuje ona jeszcze pakietów
sharp
i
svgo
, z których korzysta pod spodem. Dla każdego formatu podajesz
quality
, czyli jakość w skali do stu.

1// vite.config.js
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
5
6export default defineConfig({
7  plugins: [
8    vue(),
9    ViteImageOptimizer({
10      jpg: { quality: 80 },
11      png: { quality: 80 },
12      webp: { quality: 80 }
13    })
14  ]
15})

Wartość 80 oznacza kompresję stratną, przy której różnicy zwykle nie widać gołym okiem, a plik chudnie o kilkadziesiąt procent. Najważniejsze jest jednak to, czego robić nie musisz: nie zmieniasz ani jednego

<img>
w szablonach i nie podmieniasz plików w katalogu
src
. Wtyczka pracuje na kopii wędrującej do
dist
, więc oryginały w repozytorium zostają nietknięte i w każdej chwili możesz zmienić ustawienia oraz zbudować wszystko od nowa.

Zbudowany, obejrzany i zmierzony ładunek to wszystko, czego potrzeba przed startem - zostaje pytanie, na jaką platformę go wysłać, i tym zajmiemy się w następnej lekcji. Do tego czasu wyrób sobie odruch: najpierw

npm run build
, potem
npm run preview
, potem spojrzenie na raport rozmiarów, a dopiero na końcu cokolwiek dalej.

Pamiętaj, @name:

npm run build
nie jest kolejnym sposobem uruchomienia aplikacji - to spakowanie całego laboratorium w jeden ładunek, który poleci bez Ciebie.

Przejdź do CodeWorlds