Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik12 min czytania

Vite, serwer deweloperski i bundler w jednym

Vite łączy serwer deweloperski na modułach ES z bundlerem produkcyjnym. Rolldown w wersji 8, konfiguracja, wtyczki i błędy, które kosztują najwięcej.

Vite, serwer deweloperski i bundler w jednym

Vite to narzędzie budujące dla aplikacji przeglądarkowych, które łączy dwie osobne rzeczy: szybki serwer deweloperski oparty o natywne moduły ES oraz bundler produkcyjny. Rozwija je zespół skupiony wokół firmy VoidZero, licencja to MIT, bieżąca wersja to 8.2.1, a repozytorium ma około 82 tysięcy gwiazdek.

Co Vite właściwie robi

Nazwa sugeruje jedno narzędzie, a w środku pracują dwa różne mechanizmy, i to jest pierwsze źródło nieporozumień.

Pierwszy mechanizm to serwer deweloperski. Kiedy uruchamiasz projekt lokalnie, Vite nie buduje paczki z całej aplikacji. Zamiast tego podaje przeglądarce pliki źródłowe jako moduły ES i przekształca je pojedynczo, dokładnie w momencie, w którym przeglądarka o nie poprosi. Plik, którego nikt nie zaimportował, nigdy nie zostaje przetworzony. Dlatego czas startu serwera prawie nie zależy od rozmiaru projektu, co przy dużej aplikacji bywa różnicą liczoną w minutach.

Drugi mechanizm to budowanie produkcyjne, gdzie podejście jest odwrotne. Powstaje klasyczna paczka, z podziałem na fragmenty, usuwaniem nieużywanego kodu i minifikacją. Powód tej asymetrii jest czysto praktyczny: setki nieopakowanych modułów ES działają znakomicie na lokalnym dysku i fatalnie przez sieć, gdzie każdy z nich oznacza osobne żądanie i osobne opóźnienie.

Trzecia rzecz, o której łatwo zapomnieć, to wstępne pakowanie zależności. Biblioteki z katalogu node_modules zmieniają się rzadko, a bywają rozbite na setki plików. Vite pakuje je raz, przy pierwszym uruchomieniu, i wynik trzyma w katalogu node_modules/.vite. Dzięki temu przeglądarka pobiera jeden plik zamiast trzystu, a przy okazji moduły zapisane w starym formacie CommonJS zostają zamienione na moduły ES, których przeglądarka i tak wymaga.

Czego Vite nie robi, jest równie ważne. Nie jest frameworkiem i nie narzuca struktury aplikacji, więc nadaje się pod React, Astro czy SvelteKit tak samo dobrze jak pod projekt bez żadnej biblioteki. Nie uruchamia testów, choć Vitest korzysta z tej samej konfiguracji i tego samego łańcucha wtyczek. Nie zarządza monorepozytorium, od tego są narzędzia w rodzaju Turborepo. Nie dostarcza też gotowego serwera produkcyjnego: tryb renderowania po stronie serwera to zestaw interfejsów programistycznych, a nie zamknięte rozwiązanie.

Dwa tryby, dwa różne mechanizmy

Rozdzielenie trybu deweloperskiego od produkcyjnego jest źródłem zarówno największej zalety narzędzia, jak i najbardziej irytującej klasy błędów.

Zaleta jest oczywista przy pracy. Aktualizacja modułu po zapisaniu pliku dotyczy tylko tego modułu i jego bezpośrednich odbiorców. Nie ma przebudowy całego grafu, więc czas reakcji nie rośnie razem z projektem, a stan aplikacji przy podmianie modułu zwykle zostaje zachowany.

Wada wychodzi później. Skoro to dwie różne ścieżki przetwarzania, mogą się różnić wynikiem. Kod, który działa lokalnie i psuje się po zbudowaniu, to najczęściej efekt jednej z trzech rzeczy. Pierwsza to zależność, która deklaruje inny plik wejściowy dla przeglądarki niż dla Node, a warunki eksportu rozstrzygają się w obu trybach inaczej. Druga to kolejność wczytywania arkuszy stylów, która przy nieopakowanych modułach idzie za kolejnością żądań, a po zbudowaniu za kolejnością w grafie zależności. Trzecia to kod, który zakłada, że pliki pozostaną osobne, na przykład ścieżki składane ręcznie z fragmentów tekstu zamiast przez new URL z import.meta.url.

Wniosek praktyczny jest krótki: vite build i vite preview uruchamiane lokalnie, zanim zmiana trafi do repozytorium, wyłapują większość takich przypadków w kilkadziesiąt sekund. Zespoły, które robią to dopiero na serwerze budującym, płacą za tę oszczędność w postaci cyklu poprawek liczonego w godzinach.

Rolldown, czyli co zmieniła wersja 8

Do wersji 7 włącznie Vite opierało się na dwóch osobnych silnikach: esbuild przetwarzał zależności i transformacje w trybie deweloperskim, a Rollup budował paczkę produkcyjną. Ten podział był świadomym kompromisem z czasów pierwszych wydań, ale kosztował rozjazd między tym, co widzisz lokalnie, a tym, co dostajesz po zbudowaniu.

Wersja 8, wydana 12 marca 2026 roku, kończy ten podział. Bundlerem jest teraz Rolldown, napisany w Rust, a transformacje i minifikację przejęły narzędzia z rodziny Oxc. Widać to wprost w zależnościach pakietu: vite w wersji 8.2.1 wymaga rolldown w wersji około 1.2, natomiast rollup zniknął z zależności całkowicie, a esbuild zszedł do roli opcjonalnej zależności równorzędnej, deklarowanej jako peerDependencies z oznaczeniem optional. Minifikacja arkuszy stylów domyślnie idzie przez Lightning CSS.

Zmiana ma bezpośrednie konsekwencje w konfiguracji. Pole build.rollupOptions zostało zastąpione przez build.rolldownOptions, optimizeDeps.esbuildOptions przez optimizeDeps.rolldownOptions, a sekcja esbuild przez sekcję oxc. Stare nazwy nadal są rozpoznawane i tłumaczone wewnętrznie, ale oznaczono je jako przestarzałe. Zmieniły się też domyślne wartości: build.target to teraz 'baseline-widely-available', a build.minify przyjmuje domyślnie 'oxc' dla budowania po stronie klienta i false dla budowania na potrzeby renderowania serwerowego.

Liczby podawane przy wydaniu pochodzą od zespołów, które testowały wersję zapowiadaną. Linear raportuje skrócenie czasu budowania produkcyjnego z 46 sekund do 6 sekund, Ramp około 57 procent, Beehiiv około 64 procent, a Mercedes-Benz.io do 38 procent. Skala zależy od projektu i najmocniej widać ją tam, gdzie budowanie było wcześniej ograniczone czasem pracy procesora, a nie liczbą operacji dyskowych.

Jest też koszt, o którym warto wiedzieć zawczasu. Vite 8 zajmuje po instalacji około 15 megabajtów więcej niż Vite 7, z czego mniej więcej 10 megabajtów to Lightning CSS, a 5 megabajtów sam Rolldown. Przy obrazach kontenerów budowanych od zera na każde zgłoszenie zmian to widoczna pozycja.

Warto rozróżnić dwie rzeczy, które w opisach z 2025 roku często się mieszają. Pakiet rolldown-vite był osobną wersją zapowiadaną, publikowaną w czasie, gdy Rolldown nie był jeszcze domyślny, i jego rozwój zatrzymał się na wersji 7.3.1. Dziś nie ma powodu, żeby po niego sięgać: w wersji 8 Rolldown jest bundlerem domyślnym i jedynym, bez żadnej flagi włączającej.

Instalacja i wymagania

Vite 8 wymaga Node w wersji 20.19 lub nowszej z linii 20, albo 22.12 lub nowszej. Starsze wydania Node nie uruchomią się w ogóle, co przy serwerach budujących z przypiętą wersją bywa pierwszym problemem podczas aktualizacji.

Code
Bash
# nowy projekt z szablonem wybieranym interaktywnie
npm create vite@latest moja-aplikacja

# szablon podany wprost, bez pytań
npm create vite@latest moja-aplikacja -- --template react-ts

# dodanie do istniejącego projektu
npm install --save-dev vite

# uruchomienie serwera deweloperskiego, budowanie i podgląd wyniku
npx vite
npx vite build
npx vite preview

Serwer deweloperski nasłuchuje domyślnie na porcie 5173. Jeśli port jest zajęty, Vite przechodzi na kolejny wolny, chyba że ustawisz server.strictPort, które w takiej sytuacji przerywa uruchomienie zamiast po cichu zmieniać adres.

Konfiguracja i nazwy, które trzeba znać

Konfiguracja mieszka w pliku vite.config.ts w katalogu głównym projektu. Funkcja defineConfig nie robi nic w czasie działania, służy wyłącznie do podpowiedzi typów, więc korzystanie z niej w projekcie na TypeScript opłaca się od pierwszego dnia.

Code
TypeScript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': '/src' },
    dedupe: ['react', 'react-dom']
  },
  server: {
    port: 5173,
    strictPort: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    },
    warmup: {
      clientFiles: ['./src/main.tsx', './src/components/**/*.tsx']
    }
  },
  optimizeDeps: {
    include: ['lodash-es'],
    exclude: ['@moja-firma/pakiet-lokalny']
  },
  build: {
    target: 'baseline-widely-available',
    sourcemap: true,
    rolldownOptions: {
      output: {
        codeSplitting: true
      }
    }
  }
})

Trzy pola z tego przykładu wracają najczęściej w zgłoszeniach problemów. resolve.dedupe rozwiązuje sytuację, w której dwie kopie tej samej biblioteki trafiają do paczki, co przy bibliotekach trzymających stan globalny kończy się awarią bez sensownego komunikatu. optimizeDeps.exclude jest potrzebne dla pakietów z tego samego monorepozytorium, bo wstępne pakowanie zamraża ich stan i zmiany w kodzie źródłowym przestają być widoczne. server.warmup przetwarza wskazane pliki z wyprzedzeniem, zamiast czekać na pierwsze żądanie z przeglądarki.

Podział na fragmenty konfiguruje się przez build.rolldownOptions.output.codeSplitting, a szczegółowe opcje opisuje dokumentacja Rolldown, nie Vite. To ważne przy przenoszeniu konfiguracji z wersji 7, bo dawne ustawienia manualChunks z Rollupa nie przenoszą się jeden do jednego.

Zmienne środowiskowe i dane wstrzykiwane do kodu

Do kodu przeglądarkowego trafiają wyłącznie zmienne z przedrostkiem VITE_. To zabezpieczenie, nie utrudnienie: bez niego wystarczyłby jeden nieostrożny odczyt, żeby klucz z pliku .env wylądował w paczce wysyłanej użytkownikom.

Code
TypeScript
// pliki wczytywane w kolejności: .env, .env.local, .env.[mode], .env.[mode].local
const apiUrl = import.meta.env.VITE_API_URL

// wbudowane pola dostępne zawsze
console.log(import.meta.env.MODE)      // 'development' albo 'production'
console.log(import.meta.env.BASE_URL)  // wartość opcji base
console.log(import.meta.env.PROD)      // wartość logiczna
console.log(import.meta.env.DEV)
console.log(import.meta.env.SSR)

// import wielu plików naraz, rozwijany w czasie budowania
const strony = import.meta.glob('./strony/*.tsx')
const ikony = import.meta.glob('./ikony/*.svg', { eager: true })

// poprawne budowanie ścieżki do zasobu
const dzwiek = new URL('./assets/klik.mp3', import.meta.url).href

Wywołanie import.meta.glob jest rozwijane w czasie budowania, więc wzorzec musi być zapisany dosłownie. Przekazanie tam zmiennej nie zadziała i jest to jedna z częstszych pomyłek przy przenoszeniu kodu z webpacka, gdzie require.context zachowywał się nieco swobodniej.

Wtyczki i punkty zaczepienia

System wtyczek Vite rozszerza interfejs wtyczek Rollupa o kilka własnych zaczepień. Dzięki temu duża część istniejących wtyczek działa bez zmian, a te specyficzne dla Vite dokładają obsługę serwera deweloperskiego.

Code
TypeScript
import type { Plugin } from 'vite'

export function wtyczkaZnacznikaCzasu(): Plugin {
  const wirtualnyModul = 'virtual:czas-budowania'

  return {
    name: 'znacznik-czasu',
    enforce: 'pre',        // 'pre' albo 'post'
    apply: 'build',        // 'build' albo 'serve'

    config(uzytkownika, { command }) {
      return { define: { __KOMENDA__: JSON.stringify(command) } }
    },
    configResolved(gotowa) {
      console.log('tryb:', gotowa.mode)
    },
    resolveId(id) {
      return id === wirtualnyModul ? '\0' + wirtualnyModul : null
    },
    load(id) {
      if (id !== '\0' + wirtualnyModul) return null
      return `export const czas = ${Date.now()}`
    },
    transformIndexHtml(html) {
      return html.replace('</head>', '<meta name="build" content="ok"></head>')
    },
    configureServer(server) {
      server.middlewares.use('/health', (_req, res) => res.end('ok'))
    }
  }
}

Pole enforce decyduje o kolejności względem wtyczek wbudowanych, a apply pozwala ograniczyć wtyczkę do jednego trybu. Ta druga opcja bywa niedoceniana: wtyczka analizująca rozmiar paczki nie ma czego robić przy serwerze deweloperskim, a jej obecność tam potrafi wyraźnie spowolnić pierwsze wczytanie strony. Nazwa name nie jest ozdobnikiem, pojawia się w komunikatach błędów i w wynikach profilowania.

Vite a alternatywy

CechaVitewebpackRspackParcelTurbopack
SilnikRolldown i Oxc, RustJavaScriptRustRust i SWCRust
Tryb deweloperskinatywne moduły ESpełne pakowaniepełne pakowaniepełne pakowaniepakowanie przyrostowe
Konfiguracjamała, jeden plikrozbudowanazgodna z webpackiembez konfiguracjipowiązana z Next.js
Ekosystem wtyczekRollup i Vitenajwiększywtyczki webpackawłasnywewnętrzny
Typowe zastosowanieaplikacje i bibliotekistarsze projektymigracja z webpackamałe projektyNext.js
LicencjaMITMITMITMITMPL 2.0

Wybór rozstrzyga się zwykle na dwóch pytaniach. Jeśli masz działającą konfigurację webpacka i setki linii reguł ładowania, Rspack pozwala zachować je niemal w całości, a Vite wymaga przepisania od nowa. Jeśli zaczynasz projekt albo używasz frameworka, który i tak stoi na Vite, na przykład Nuxt, pytanie o alternatywy przestaje mieć praktyczne znaczenie.

Typowe błędy

Pierwszy to mylenie serwera deweloperskiego z serwerem produkcyjnym. Polecenie vite preview służy do sprawdzenia zbudowanej paczki na własnej maszynie i nie ma zabezpieczeń wymaganych od usługi wystawionej na świat. Statyczne pliki z katalogu dist powinien podawać właściwy serwer HTTP albo sieć dostarczania treści.

Drugi to zmienne środowiskowe odczytywane przez process.env w kodzie przeglądarkowym. W przeglądarce ten obiekt nie istnieje, a Vite nie podstawia go automatycznie. Jedyną poprawną drogą jest import.meta.env z przedrostkiem VITE_, ewentualnie pole define dla wartości ustalanych w czasie budowania.

Trzeci to nieaktualne wstępne pakowanie zależności. Po zmianie wersji biblioteki albo po ręcznej edycji czegoś w node_modules zawartość katalogu node_modules/.vite przestaje odpowiadać rzeczywistości, a objawem są importy wskazujące na kod, którego już nie ma. Uruchomienie z flagą --force odtwarza ten katalog od zera i rozwiązuje sprawę w kilkanaście sekund.

Czwarty to zapominanie o opcji base przy wdrożeniu pod podkatalogiem. Domyślna wartość zakłada, że aplikacja stoi w katalogu głównym domeny. Jeśli trafia pod adres z dodatkowym segmentem ścieżki, wszystkie odwołania do zasobów wskazują w próżnię, a strona ładuje się jako sam dokument HTML bez stylów i skryptów.

Piąty dotyczy arkuszy stylów przetwarzanych przez narzędzia zewnętrzne. Konfiguracja Tailwind CSS czy innego preprocesora jest osobną warstwą, a Vite tylko ją uruchamia. Wolne przebudowy stylów prawie nigdy nie wynikają z samego Vite, tylko ze zbyt szeroko określonych wzorców plików w konfiguracji tej warstwy.

Szósty to instalowanie wtyczek dla samego uruchomienia menedżera pakietów innego niż domyślny. Vite działa na Node, ale uruchamia się też przez Bun czy pnpm bez dodatkowych wtyczek, a różnice sprowadzają się do czasu instalacji zależności, nie do sposobu budowania.

FAQ - najczęściej zadawane pytania

Czy w wersji 8 trzeba włączać Rolldown osobno?

Nie. W Vite 8 Rolldown jest bundlerem domyślnym i jedynym, bez żadnej flagi. Pakiet rolldown-vite, publikowany w czasie fazy zapowiadanej, nie jest już potrzebny i zatrzymał się na wersji 7.3.1.

Czy migracja z wersji 7 wymaga przepisania konfiguracji?

W typowym projekcie nie. Nazwy build.rollupOptions i optimizeDeps.esbuildOptions są nadal rozpoznawane i tłumaczone wewnętrznie na nowe odpowiedniki. Przepisania wymagają szczegółowe ustawienia podziału na fragmenty oraz wtyczki sięgające bezpośrednio do interfejsów Rollupa.

Dlaczego aplikacja działa lokalnie, a po zbudowaniu jest pusta?

Najczęściej z powodu opcji base niedopasowanej do adresu wdrożenia albo błędu w kodzie, który ujawnia się dopiero po minifikacji. Uruchom vite build i vite preview lokalnie, a potem sprawdź zakładkę sieci w narzędziach przeglądarki: żądania kończące się kodem 404 wskazują na base, a błąd w konsoli na drugą przyczynę.

Czy Vite nadaje się do budowania bibliotek?

Tak, służy do tego tryb biblioteki konfigurowany przez build.lib z polami entry, name, fileName i formats. Domyślne formaty zależą od liczby punktów wejścia: przy jednym powstają es i umd, przy wielu es i cjs.

Jak przyspieszyć start serwera w dużym projekcie?

Zacznij od server.warmup dla plików wczytywanych zawsze, a następnie sprawdź, czy któraś zależność nie jest niepotrzebnie wyłączona z pakowania wstępnego. Odwrotny ruch, czyli dopisanie ciężkich pakietów do optimizeDeps.include, pomaga wtedy, gdy Vite odkrywa je dopiero w trakcie pracy i przeładowuje stronę.

Czy Vite obsługuje renderowanie po stronie serwera?

Obsługuje, ale w formie interfejsów programistycznych, na których buduje się rozwiązanie docelowe. Gotową obsługę dostarczają frameworki stojące na Vite, więc przy projekcie produkcyjnym rozsądniej jest sięgnąć po jeden z nich niż składać własny serwer od zera.

Dokumentację znajdziesz na stronie Vite, zapowiedź wersji 8 w blogu projektu, a kod źródłowy w repozytorium na GitHubie.