Bundler to narzędzie, które łączy wiele plików projektu (moduły JS, style, obrazy) w kilka zoptymalizowanych plików gotowych dla przeglądarki. Webpack to dojrzały, bardzo konfigurowalny bundler, który wciąż działa w wielu starszych projektach, a Vite to nowsze narzędzie z dev serverem opartym na natywnych modułach ES, które w nowych projektach jest dziś domyślnym wyborem. Jeśli zaczynasz nowy projekt, wybierz Vite; webpacka poznasz głównie przy utrzymywaniu istniejącego kodu.
Kiedy projekt frontendowy rośnie z jednego pliku do kilkudziesięciu, ktoś musi je ze sobą połączyć, przetworzyć i przygotować do wdrożenia. Robi to bundler. W tym artykule porównam dwa narzędzia, na które trafisz najczęściej: webpack i Vite. Pokażę, do czego służą, jak wygląda ich minimalna konfiguracja i gdzie każde z nich spotkasz w praktyce.
Czym jest bundler JavaScript i po co go używać?
Bundler (czyli „pakowacz”) czyta Twój kod od punktu wejścia (zwykle index.js), śledzi wszystkie instrukcje import i buduje z nich graf zależności. Następnie zapisuje wynik do kilku plików w katalogu dist. Po drodze potrafi też:
- zminimalizować kod (usunąć białe znaki, skrócić nazwy zmiennych), żeby ważył mniej,
- usunąć nieużywany kod (tzw. tree shaking),
- przetworzyć inne pliki: CSS, Sass, obrazy, czcionki, TypeScript, JSX,
- dodać hash do nazwy pliku (
main.46b9f0.js), dzięki czemu przeglądarka pobierze nową wersję dopiero po zmianie kodu, - podzielić kod na części (code splitting), by użytkownik pobierał tylko to, czego potrzebuje na danej podstronie,
- uruchomić serwer deweloperski z automatycznym odświeżaniem strony po zapisaniu pliku.
Czy bundler jest zawsze potrzebny? Nie. Pojedyncza strona z jednym skryptem działa bez niego. Przestaje to wystarczać, gdy używasz paczek z npm, pisasz w TypeScripcie albo Reakcie, albo chcesz mieć zoptymalizowany build produkcyjny.
Jak działa webpack i do czego służy?
Webpack powstał w 2012 roku i przez lata był standardem. Jego idea: wszystko jest modułem. Plik JS, CSS czy obraz da się zaimportować, o ile masz do niego odpowiedni loader (zamienia plik na moduł) lub plugin (rozszerza sam proces budowania).
Webpack konfiguruje się w pliku webpack.config.js. Minimalna konfiguracja z obsługą CSS i generowaniem index.html wygląda tak:
import path from "node:path";
import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
export default {
entry: "./src/index.js",
output: {
path: path.resolve(import.meta.dirname, "dist"),
filename: "[name].[contenthash].js",
clean: true,
},
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
],
},
plugins: [
new HtmlWebpackPlugin({ title: "App" }),
new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }),
],
};
Co tu się dzieje?
| Klucz | Za co odpowiada |
|---|---|
entry | Punkt startowy, od którego webpack buduje graf zależności |
output | Gdzie i pod jaką nazwą zapisać wynik ([contenthash] = hash w nazwie, clean czyści stary build) |
module.rules | Loadery: który typ pliku przetworzyć jakim narzędziem |
plugins | Rozszerzenia całego procesu, np. generowanie index.html albo wydzielenie CSS do osobnego pliku |
Budowanie uruchamiasz poleceniem webpack --mode production. Użyty w tym przykładzie plik z import.meta.dirname wymaga "type": "module" w package.json (w starszych projektach zobaczysz zamiast tego require i __dirname).
Mocną stroną webpacka jest ogromny ekosystem i pełna kontrola nad każdym etapem. Słabszą: konfiguracja szybko puchnie, a dev server przy dużym projekcie startuje powoli, bo najpierw musi zbudować całą aplikację.
Jak działa Vite i czym różni się od webpacka?
Vite (po francusku „szybki”) działa inaczej w trybie deweloperskim i produkcyjnym:
- W trybie deweloperskim nie buduje całej aplikacji z góry. Przeglądarka sama prosi o moduły ES (
import), a Vite przetwarza tylko te pliki, o które poprosiła. Dzięki temu serwer startuje niemal natychmiast, niezależnie od wielkości projektu. - W trybie produkcyjnym (
vite build) tworzy zoptymalizowany bundle. Od wersji 8 robi to za pomocą Rolldown, bundlera napisanego w Rust (sprawdziłem to w drzewie zależności Vite 8.3.5), wcześniej używał do tego Rollupa.
Największa różnica na co dzień: Vite działa bez pliku konfiguracyjnego. Wystarczy index.html z tagiem <script type="module">:
<!doctype html>
<html>
<head><title>App</title></head>
<body>
<script type="module" src="/src/index.js"></script>
</body>
</html>
W package.json dodajesz skrypty:
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
Trzy skrypty robią różne rzeczy. vite (npm run dev) uruchamia serwer deweloperski, który serwuje Twój kod źródłowy i odświeża stronę po zapisie pliku. vite build tworzy katalog dist z gotowym buildem. vite preview nie buduje niczego, tylko serwuje to, co już leży w dist, żeby sprawdzić wersję produkcyjną lokalnie. Dlatego vite preview zadziała dopiero po vite build.
CSS importujesz zwykłym import "./style.css" i nie potrzebujesz żadnego loadera. Ten sam projekt, który w webpacku wymagał trzech paczek pomocniczych i kilkunastu linii konfiguracji, w Vite zbudował się samym vite build (w moich testach po stronie Vite zostały: index.html, plik JS i plik CSS z hashami w nazwach).
Plik vite.config.js pojawia się dopiero wtedy, gdy czegoś potrzebujesz, np. wtyczki dla Reacta albo proxy do API:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: { "/api": "http://localhost:8000" },
},
});
Webpack czy Vite: jakie są najważniejsze różnice?
| Cecha | webpack | Vite |
|---|---|---|
| Konfiguracja | Wymagana, rozbudowana (webpack.config.js) | Opcjonalna, sensowne ustawienia domyślne |
| Start serwera deweloperskiego | Najpierw buduje cały bundle | Serwuje moduły ES na żądanie |
| Build produkcyjny | Własny silnik webpacka | Rolldown (od Vite 8), wcześniej Rollup |
| CSS, obrazy, TypeScript | Przez loadery i pluginy | Obsługa w standardzie (TS i JSX transpilowane bez dodatków) |
| Dojrzałość i ekosystem | Bardzo duży, obecny od 2012 r. | Mniejszy, ale szybko rosnący |
| Elastyczność | Pełna kontrola nad każdym etapem | Mniej opcji „pod maską”, więcej gotowych rozwiązań |
| Typowe zastosowanie | Starsze i bardzo rozbudowane projekty | Nowe aplikacje frontendowe |
| Wymagania | Node ≥ 10.13 (webpack 5) | Node ≥ 20.19 lub ≥ 22.12 (Vite 8) |
Gdzie najczęściej używa się webpacka, a gdzie Vite?
Webpack spotkasz głównie:
- w starszych projektach: stare aplikacje Reacta, Angulara, Vue, które powstały przed erą Vite,
- w projektach stworzonych przez Create React App: CRA ukrywał konfigurację webpacka, a to na tyle częste pytanie, że „gdzie jest konfiguracja webpacka w CRA?” ma na Stack Overflow ponad 400 tysięcy wyświetleń,
- w dużych aplikacjach firmowych z rozbudowanym buildem (np. Module Federation, czyli współdzielenie kodu między aplikacjami),
- jako silnik pod spodem: część frameworków i narzędzi używa go wewnętrznie, więc możesz z niego korzystać, nie pisząc ani jednej linii konfiguracji.
Vite wybierzesz:
- w nowych aplikacjach w Reakcie, Vue, Svelte czy czystym JS i TypeScript (
npm create vite@latestgeneruje gotowy szablon), - do szybkich prototypów i projektów edukacyjnych, bo nie wymaga żmudnej konfiguracji,
- do bibliotek (tryb
libraryw buildzie) i narzędzi, na których zbudowane są inne projekty (np. Vitest do testów).
Zauważ, że frameworki typu Next.js dostarczają własny system budowania, więc przy nich zwykle w ogóle nie wybierasz bundlera. Do wersji 15 Next.js korzystał domyślnie z webpacka, a od wersji 16 domyślnie używa Turbopacka (opisanego niżej). Webpack nadal działa w tych projektach pod flagą --webpack, więc jeśli trafisz na starszy projekt Next.js z własną sekcją webpack w next.config.js, to właśnie na to patrzysz. Uwaga: w Next.js 16 next build z taką konfiguracją kończy się błędem, dopóki nie dodasz --webpack albo nie przeniesiesz ustawień do Turbopacka.
Jak w obu narzędziach używać zmiennych środowiskowych?
To częsta pułapka. W Vite zmienne z pliku .env trafiają do kodu przeglądarki tylko wtedy, gdy ich nazwa zaczyna się od VITE_:
VITE_API_URL=https://api.example.com
SECRET_KEY=tajne123
console.log(import.meta.env.VITE_API_URL); // "https://api.example.com"
console.log(import.meta.env.SECRET_KEY); // undefined
Sprawdziłem to w buildzie: wartość VITE_API_URL została wstawiona do pliku wynikowego, a SECRET_KEY w ogóle się w nim nie znalazł. To zabezpieczenie, a nie błąd. Wszystko, co trafia do bundla, zobaczy każdy użytkownik strony, więc klucza prywatnego nie wolno umieszczać w zmiennych VITE_* (ani w odpowiednikach w webpacku).
W webpacku nic takiego nie dzieje się automatycznie. Wartości trzeba wstrzyknąć ręcznie przez DefinePlugin lub EnvironmentPlugin, a plik .env wczytać paczką dotenv.
Jakie błędy najczęściej pojawiają się przy webpacku i Vite?
| Objaw | Przyczyna | Co zrobić |
|---|---|---|
ERR_OSSL_EVP_UNSUPPORTED przy buildzie webpacka | Stary webpack 4 + Node 17 lub nowszy (zmiana algorytmu haszowania w OpenSSL) | Zaktualizuj do webpacka 5 (najlepsze rozwiązanie), a tymczasowo użyj starszego Node |
Module not found: Can't resolve 'path' lub 'buffer' w webpacku 5 | Webpack 5 nie dodaje już automatycznie polifilli modułów Node | Nie używaj modułów Node w kodzie przeglądarkowym, albo dodaj resolve.fallback z odpowiednią paczką |
Invalid Host header w webpack-dev-server | Dev server odrzuca żądania z innej nazwy hosta niż localhost | Ustaw opcję allowedHosts w devServer |
import.meta.env.MOJA_ZMIENNA jest undefined w Vite | Brak prefiksu VITE_ | Zmień nazwę na VITE_MOJA_ZMIENNA i zrestartuj serwer |
| Vite nie startuje, błąd o wersji Node | Vite 8 wymaga Node 20.19+ lub 22.12+ | Zaktualizuj Node |
vite: command not found (w Windows: 'vite' is not recognized) przy npm run dev lub npm run build | Brak zainstalowanych zależności: nie ma katalogu node_modules, więc skrypt nie ma czego uruchomić | Wykonaj npm install w katalogu projektu i upewnij się, że vite jest w devDependencies |
Rolldown failed to resolve import "@/greet.js" | Alias @ nie istnieje domyślnie, trzeba go zdefiniować | Dodaj resolve.alias w vite.config.js (przykład niżej) |
| Po deployu na GitHub Pages strona jest pusta | Pliki są szukane pod złą ścieżką (/assets/... zamiast /repo/assets/...) | W Vite ustaw base: "/nazwa-repo/", w webpacku output.publicPath |
Alias @ wskazujący na katalog src ustawisz tak:
import { defineConfig } from "vite";
import path from "node:path";
export default defineConfig({
resolve: {
alias: { "@": path.resolve(import.meta.dirname, "src") },
},
});
Po tej zmianie import { greet } from "@/greet.js" działa w builcie. Bez niej budowanie kończy się błędem, który sprawdziłem w Vite 8.3.5.
Ostatni wiersz tabeli (pusta strona po deployu) dotyczy Ciebie, jeśli publikujesz projekt na GitHub Pages, a szczegóły wdrożenia opisuję w poradniku o podglądzie projektu na GitHubie.
Czy na rozmowie rekrutacyjnej pytają o webpacka i Vite?
Na rozmowach rzadko pada pytanie o składnię webpack.config.js. Częściej chodzi o to, co bundler robi i czym różni się build od trybu deweloperskiego. Jeśli potrafisz to wyjaśnić na własnym projekcie, jesteś do przodu.
Co wybrać na start nauki: webpack czy Vite?
Jeśli uczysz się teraz frontendu i zaczynasz własny projekt: zacznij od Vite. Nie spędzisz pierwszego wieczoru na konfiguracji, szybciej zobaczysz efekt i bez problemu przejdziesz do Reacta czy TypeScripta. Dobrze zrozum jednak sam mechanizm: co to moduł, jak działa import, czym jest build i czym różni się od trybu deweloperskiego. Te pojęcia są identyczne w obu narzędziach, a jeśli kiedyś trafisz do firmy z projektem na webpacku, dużo łatwiej odnajdziesz się w jego konfiguracji.
Zanim sięgniesz po bundler, upewnij się, że znasz moduły ES i podstawy npm. Tego uczymy w ramach kursu JavaScript, a bardziej zaawansowane tematy budowania aplikacji znajdziesz w kursie Zaawansowany JS.
Czy istnieją inne bundlery oprócz webpacka i Vite?
Tak. Najczęściej wymieniane to:
- Rollup: bundler nastawiony na biblioteki, wykorzystywany przez wiele narzędzi (w Vite do wersji 7),
- esbuild: bardzo szybki bundler napisany w Go, używany często jako element innych narzędzi,
- Parcel: bundler „zero config”, który sam wykrywa potrzebne przetwarzanie,
- Rspack: bundler w Rust z konfiguracją zbliżoną do webpacka, więc ułatwia migrację istniejących projektów,
- Turbopack: bundler w Rust zintegrowany z Next.js. Od Next.js 16 jest domyślnym bundlerem zarówno w
next dev, jak i wnext build, a webpacka włączysz tylko flagą--webpack. Starsze wersje Next.js domyślnie używały webpacka. Turbopacka nie wybierasz więc osobno jak Vite, bo jest częścią frameworka.
Nie testowałem ich w tym artykule, więc traktuj ten wykaz jako mapę, a nie rekomendację. W praktyce, zanim zaczniesz porównywać kolejne narzędzia, przećwicz podstawy na jednym z dwóch omówionych tu.


