Blog programistyczny to jednak fajna sprawa… Kiedy nie masz pomysłu na wpis, zawsze możesz sprawdzić czy coś z Twoich starszych wpisów nie straciło na aktualności. W końcu, szczególnie w świecie front-endu, kolejne wersje frameworków, bibliotek i narzędzi pojawiają się stosunkowo często. Nie inaczej stało się z webpackiem, o którego konfiguracji pisałem w październiku 2016 roku. Z racji tego, że już jakiś czas temu pojawiła się jego nowa wersja, dziś przedstawiam aktualizację tamtego wpisu (jak się w trakcie pisania okazało, jego pierwszą część), w której pokażę na czym polega konfiguracja webpack 2+ (plus, ponieważ wyszła już wersja 3 ale nie wprowadza ona łamiących zmian więc wszystko co opiszę jest dla niej aktualne).

Co to jest Webpack

O tym, czym jest webpack pisałem już w poprzedniej wersji tego wpisu. Jeśli jednak dotarłeś tutaj po raz pierwszy, na przykład z wyszukiwarki google to myślę, że możesz nie wiedzieć do czego służy to narzędzie. To właśnie dla tych wszystkich osób jest te kilka “powtórkowych” akapitów. Ogólnie to mój pomysł na ten wpis to zrobienie dokładnie tego samego co poprzednio tylko używając składni konfiguracyjnej webpacka 2. Skoro jednak piszę od nowa to myślę, że warto rozszerzyć ten zakres o kilka przydatnych elementów. Dzięki temu całość będzie bardziej wartościowa, ponieważ nie ukrywam, że stary wpis jedynie sygnalizował czym jest to narzędzie i nie byłem z niego zadowolony… No ale najpierw spieszę z wyjaśnieniem czym jest webpack!

Webpack jest to tzw. „module bundler” (nie ma chyba dobrego tłumaczenia na polski) co oznacza, że potrafi on spakować zasoby wielu różnych typów do jednego wspólnego, wynikowego zasobu. Dla przykładu: jeśli posiadamy w naszym projekcie wiele modułów JavaScript, ale też pliki Sass, LESS, itp., webpack potrafi połączyć je ze sobą (zachowując odpowiednią kolejność ładowania), na różne sposoby przekształcić, a na koniec zminifikować i zoptymalizować. Następnie umieszcza on to wszystko w jednym pliku *.js, a jeśli jest taka potrzeba to potrafi on także wydzielić kod CSS to osobnego pliku, a następnie wszystko to wrzucić do pliku HTML. Zaletą takiego rozwiązania jest na pewno wydajność: mniej plików do pobrania z serwera to mniej do niego zapytań.

Zapewne mógłbyś powiedzieć, że na rynku jest mnóstwo innych, podobnych narzędzi, po co więc nam, do cholery, jeszcze jedno? No cóż, webpack ma tę przewagę nad innymi tego typu narzędziami, że pozwala na dzielenie wynikowych plików na mniejsze kawałki (ang. chunks). Takie kawałki mogą być ładowane na różnych podstronach naszej aplikacji w zależności od potrzeb. Wydaje mi się, że może to mieć spore znaczenie szczególnie w dużych projektach, gdzie mamy wiele różnych, rozbudowanych funkcjonalności, które nie koniecznie od siebie zależą. Nie jest przecież wówczas konieczne ładowanie wszystkiego na każdej podstronie aplikacji. Zresztą, do tej pory już sporo z webpackiem popracowałem i mogę powiedzieć, że do małych projektów również świetnie się nadaje!

Konfiguracja Webpack 2+

Ok, wiemy już co to jest webpack, przejdźmy teraz do głównego tematu tego posta jakim jest konfiguracja webpack 2. Na początek poświęcimy jednak chwilę na jego instalację. W dalszej części wpisu natomiast, pokażę jak skonfigurować punkty wejściowe oraz wyjściowe.

W kolejnym wpisie przedstawię też czym jest “loader”, dzięki któremu umożliwimy wykorzystanie składni ES6+ w naszym projekcie. Na koniec dodamy też obsługę plików CSS oraz skonfigurujemy webpacka tak, aby trafiały one do osobnej paczki.

Instalacja

Do instalacji najnowszej wersji webpacka wykorzystamy repozytorium npm. Najlepiej jest zainstalować to narzędzie lokalnie, tylko w naszym projekcie:

npm install --save-dev webpack

Do samej instalacji można też wykorzystać, zamiast polecenia npm, narzędzie yarn (do czego namawiam):

yarn add --dev webpack

Jeśli nie znasz jeszcze tego narzędzia to na początek zachęcam do zajrzenia do mojego wpisu na ten temat… Ja w każdym razie, w dalszej części tego wpisu będę już wykorzystywał tylko yarn.

Skoro webpack został już zainstalowany w naszym projekcie, możemy zacząć z niego korzystać. Niestety jest jedna drobna niedogodność - polecenie webpack jest teraz dostępne tylko z poziomu katalogu node_modules (czyli będąc w głównym katalogu projektu musimy wywoływać node_modules/.bin/webpack. Aby temu zaradzić mamy dwie opcje: instalacja webpacka globalnie (co nie jest zalecane z powodu możliwego konfliktu wersji) albo dodanie dedykowanego skryptu do pliku package.json narzędzia npm.

Skorzystajmy z tej drugiej opcji i dodajmy odpowiedni wpis do wspomnianego pliku package.json:

"scripts": {
  "start": "webpack --config webpack.config.js"
}

W powyższym przykładzie utworzyliśmy polecenie start, które uruchamia polecenie webpack z parametrem wskazującym gdzie znajduje się konfiguracja. Akurat w przypadku konfiguracji, która znajduje się w głównym katalogu projektu, w pliku o nazwie webpack.config.js ten parametr można by pominąć. Chciałem jednak pokazać, że jest opcja wskazania dowolnej lokalizacji pliku konfiguracyjnego.

Podstawowa konfiguracja

Nadszedł wreszcie czas, aby pokazać Ci na jak wygląda podstawowa konfiguracja Webpack 2. Najpierw zajmiemy się absolutnym minimum, a więc konfiguracją wejścia/wyjścia. Do tego celu utwórzmy, wspomniany już powyżej, plik konfiguracyjny webpacka. Nazwijmy go standardowo webpack.config.js i umieśćmy w głównym katalogu projektu. Jego najbardziej podstawowa zawartość może wyglądać jak poniżej:

const path = require('path');

module.exports = {
  entry: './src/home.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  }
};

Jeżeli znasz już mój poprzedni wpis o podstawach konfiguracji webpacka to pewnie od razu zauważyłeś, że nic się tutaj od poprzedniej wersji nie zmieniło.

W powyższym pliku konfiguracyjnym najpierw importuję przydatne pakiety (tutaj biblioteka path, która jest standardem w świecie Node.js i nie trzeba jej dodatkowo instalować). Dalej, jest już tylko eksport obiektu zawierającego konfigurację.

Pierwszą właściwością tego obiektu jest entry, która służy do wskazania punktu (lub punktów ale o tym za moment) startowego naszej aplikacji. Jak widzisz jest to plik home.js znajdujący się w katalogu ./src. To od tego pliku webpack rozpocznie analizę drzewa zależności więc jeśli importuje on jakieś inne pliki i pakiety to one również znajdą się w wynikowym “bundlu” (to samo z ich zależnościami itd.).

Drugą właściwością powyższej konfiguracji jest output. Jak sama nazwa wskazuje jest to miejsce, w którym definiujemy plik(i) wynikowe, które wypluje webpack po zakończeniu swoich działań. Do właściwości output przypisujemy obiekt zawierający dwie właściwości. Pierwsza z nich, path, wskazuje do jakiego katalogu wrzucić wynikowe pliki. Właściwość filename z kolei definiuje nazwę wynikowego pliku.

Wystarczy teraz uruchomić zdefiniowany przez nas wcześniej skrypt npm aby zobaczyć czy powyższy kod działa (oczywiście plik ./src/home.js musi w tym momencie istnieć):

yarn start

To spowoduje, że w katalogu ./dist znajdzie się plik bundle.js odpowiednio przetworzony już przez webpacka.

Wiele plików wejściowych

Teraz zajmiemy się jedną z największych zalet webpacka, a więc możliwością definiowania wielu “chunków”. Wyobraź sobie, że tworzysz stronę internetową, która zawiera wiele podstron. Na każdej z nich masz zupełnie inną logikę i inne style. Czy w takim przypadku jest sens posiadać jeden duży “bundle”? Raczej nie… lepiej jest podzielić go sobie na mniejsze kawałki.

Aby utworzyć za pomocą webpacka “bundle” dedykowane dla poszczególnych podstron należy utworzyć wiele punków wejściowych. W poprzednim przykładzie mieliśmy tylko jeden taki punkt: plik home.js. Jeśli w naszym projekcie, oprócz strony głównej, mamy też podstrony takie jak, na przykład, kontakt oraz post, to tworzymy dla nich punkty startowe contact.js oraz post.js.

Następnie musimy też odpowiednio zmienić konfigurację webpacka:

const path = require('path');

module.exports = {
  entry: {
    home: './src/home.js',
    post: './src/post.js',
    contact: './src/contact.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name].bundle.js'
  }
};

Zwróć najpierw uwagę na właściwość entry. Tym razem przypisujemy do niej obiekt. Zawiera on trzy właściwości będące nazwami poszczególnych “chunków”, a do każdej z nich przypisano ścieżkę do odpowiadającego im pliku wejściowego.

Teraz przejdźmy do właściwości output. To co tutaj istotne dzieje się w “stringu” przypisanym do właściwości filename. Na pewno zauważyłeś, że zawiera on teraz wartość name w nawiasach kwadratowych. Jest to “placeholder”, w którym webpack umieści nazwę danego “chunka”(weźmie ją z nazwy właściwości obiektu entry).

W przypadku wielu plików wejściowych, dodanie “placeholdera” [name] jest obowiązkowe, ponieważ inaczej webpack nie wiedziałby jak rozdzielić wyniki swojej pracy. Zamiennie z “placeholderem” [name] można też stosować kilka innych “placeholderów”. Przeczytasz o nich więcej w dalszej części wpisu.

Po ponownym uruchomieniu komendy yarn start, w katalogu ./dist znajdują się teraz trzy pliki: home.bundle.js, post.bundle.js oraz contact.bundle.js. Każdy z nich zawiera wszystkie zależności odpowiadającego im pliku wejściowego. Teraz wystarczy każdy z tych “bundli” załączyć do odpowiednich podstron i mamy pewność, że załadowane zostanie tylko to co jest dla danej podstrony niezbędne.

Więcej opcji “placeholdera”

Jeśli chodzi o “placeholder” [name] to nie jest to jedyna opcja. Webpack pozawala w ten sam sposób wstrzykiwać do nazw wynikowych plików, także takie rzeczy jak na przykład “hash” operacji itp. Zresztą spójrz na kilka przykładów właściwości filename:

// nazwa "bundla"
filename: "[name].bundle.js"

// wewnętrzny identyfikator "chunka"
filename: "[id].bundle.js"

// "hash" całego builda
filename: "[name].[hash].bundle.js"

// "hash" danego "chunka"
filename: "[chunkhash].bundle.js"

Pierwszy z “placeholderów” jest nam już znany dlatego nie będę się więcej nad nim rozwodzić. Przejdę od razu do drugiego z nich czyli [id].

Wcześniej wspomniałem, że kiedy mamy wiele plików wejściowych to za pomocą placeholdera [name] możemy rozróżnić poszczególne pliki wynikowe. Innym “placeholderem” jednoznacznie rozróżniającym “bundle” jest właśnie [id]. Jest to wewnętrzny identyfikator danego “chunka” więc webpack sam wstawi w to miejsce odpowiednią wartość.

Kolejnym przedstawionym przykładem “placeholdera” jest [hash]. Jest to, jak sama nazwa wskazuje, “hash” całego builda. Wartość ta będzie więc taka sama dla wszystkich plików wynikowych i dlatego musi być łączona z jednym z unikalnych “placeholderów”.

Ostatnim przykładem również jest “hash” tyle, że wygenerowany na podstawie kodu znajdującego się w danym pliku wynikowym. On również jest unikalny więc może z powodzeniem zastąpić “placeholder” [name].

Na koniec jeszcze mała uwaga! Zważywszy na to, że “placeholdery” oparte o “hash” mogą być wypełnione inną wartością, przy każdym uruchomieniu “builda”, należy je stosować z odpowiednia wtyczką potrafiącą wkleić odwołania do plików wynikowych do pliku HTML. Napiszę o tym więcej w kolejnej części tej serii wpisów.

P.S. Więcej na ten temat “placeholderów” właściwości filename przeczytasz na tej stronie dokumentacji.

Podsumowanie

W sumie to planowałem opisać wszystko dzisiaj ale do tego momentu post zrobił się już całkiem długi, a ja opisałem dopiero konfigurację wejścia/wyjścia. No niestety, konfiguracja webpack 2 daje wiele możliwości, które ciężko pomieścić w ramach jednego posta. Dlatego też postanowiłem podzielić tego posta na (conajmniej) dwie części. Myślę, że dzięki temu uda mi się znacznie pełniej opisać wszystkie możliwości tego, na prawdę potężnego, narzędzia. Niniejszym zapraszam więc na kolejną część, w której opowiem co nieco o “loaderach”.

P.S. Na koniec tej mini-serii o webpacku podam linka do repozytorium GitHuba, gdzie będziesz mógł/mogła znaleźć kod przedstawianych przeze mnie przykładów!


Jako, że powyższy wpis jest częścią większej całości, poniżej podaję linki do wszystkich odcinków serii na temat podstaw konfiguracji webpacka 2+: