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.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 buildPo 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ć.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 previewZnak
&& 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.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.jsCią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.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.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.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.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.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/apiPrefiks
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.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.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.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.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.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.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.