Za nami już dwie części mini-serii na temat konfigurowania nowej wersji webpacka. Dotychczas dowiedzieliśmy się co nieco na temat podstawowej konfiguracji plików wejściowych oraz wyjściowych. Z ostatniego wpisu natomiast, wiemy już czym są loadery i jak możemy je wykorzystać. Dziś przyszła pora na pokazanie trzeciej, istotnej przy konfiguracji webpacka rzeczy, którą są pluginy. Myślę, że nie ma co przedłużać - zapraszam do lektury!

Czym są pluginy w webpacku?

Pluginy, czyli po naszemu wtyczki, możemy spotkać w wielu systemach i aplikacjach. Zwykle jest to zewnętrzny kod, nie koniecznie stworzony przez twórców danego rozwiązania, który dodaje jakieś nowe funkcjonalności lub modyfikuje działanie danej aplikacji. Podobnie jest w przypadku webpacka - pluginy pozwalają “customizować” proces budowania “bundli” na wiele różnych sposobów.

Webpack posiada wiele wbudowanych, przydatnych pluginów, których kilka przykładów pokażę za moment. Istnieją też oczywiście wtyczki stworzone przez innych, nie związanych z webpackiem programistów, które dostępne są w repozytorium npm - niektóre z nich również za chwilę przedstawię.

Jeśli chodzi o konfigurację, to sprowadza się ona do dodania danego pluginu do tablicy, którą przypisujemy do właściwości plugins obiektu konfiguracyjnego webpacka. Zresztą spójrz na poniższy przykład:

const webpack = require('webpack');

module.exports = {
  entry: {
    // ...
  },
  output: {
    // ...
  },
  module: {
    rules: [
      // ...
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin()
  ]
};

W powyższym, przykładowym kodzie widać, że tablica plugins zawiera wbudowany w webpacka plugin UglifyJsPlugin. Zwróć uwagę na użycie słowa kluczowego new - w ten sposób przekazujemy do webpacka instancję obiektu wtyczki. Konstruktory pluginów często przyjmują też jako parametr obiekt, w którym przekazujemy do niego jakąś dodatkową konfigurację. Warto więc dobrze przeczytać dokumentację pluginu aby wiedzieć jak w pełni wykorzystać jego możliwości!

Pod tym adresem znajdziesz listę wszystkich wbudowanych w webpack pluginów, wraz z ich opisem i konfiguracją. A tutaj kilka zewnętrzych wtyczek. Poniżej przedstawię kilka najczęściej stosowanych pluginów: zarówno tych wbudowanych jak i zewnętrznych.

Przykłady

Tak jak obiecałem - teraz czas na przykłady najbardziej przydatnych pluginów jakie często wykorzystuje się w projektach! Przykłady te będę przedstawiać “przyrostowo”, tzn. kolejne listingi będą zawierały kod z poprzedniego przykładu. Dzięki temu na koniec wpisu będziesz mógł zobaczyć jak wygląda pełna konfiguracja wszystkich wtyczek na raz.

CommonsChunkPlugin

Jest to bardzo przydatna wtyczka, która potrafi spośród wszystkich modułów używanych przez poszczególne punkty wejściowe aplikacji, wybrać te, które są przez nie współdzielone. Następnie wszystkie te współdzielone moduły potrafi “wypluć” do dodatkowego, osobnego “bundla”, który może zostać załączony na każdej podstronie aplikacji jako wymagany przez wszystkie “bundle”.

Konfiguracja tego pluginu jest dość prosta:

const webpack = require('webpack');

module.exports = {

  // ...

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.bundle.js'
    })
  ]
};

Wtyczka CommonsChunkPlugin jest, jak widać, pluginem wbudowanym w webpacka. Powyższy przykład przedstawia jego prostą konfigurację, która będzie skutkować tym, że ten dodatkowy, wynikowy “bundle” ze współdzielonymi modułami będzie miał nazwę vendor.bundle.js. Warto wiedzieć, że ciąg znaków przypisanych do właściwości filename może zawierać te same “placeholdery” co w przypadku właściwości filename obiektu output, o której pisałem w pierwszym wpisie tej serii.

Plugin ten ma trochę więcej opcji konfiguracji, warto więc się z nimi zaznajomić aby poznać pełnię możliwości tego pluginu.

UglifyJsPlugin

Kolejny plugin będący częścią webpacka - posiada też jednak wersję dostępną poprzez repozytorium npm. Myślę, że jego nazwa od razu przywodzi na myśl do czego może on służyć… Jest to bowiem wtyczka, która pozwala dokonać optymalizacji i minifikacji wynikowego kodu “wypluwanego” przez webpack, a która do tego celu wykorzystuje popularne narzędzie UglifyJS.

Spójrz na przykładową konfigurację webpacka z omawianym pluginem:

const webpack = require('webpack');

module.exports = {

  // ...

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.bundle.js'
    }),
    new webpack.optimize.UglifyJsPlugin({
      beautify: true,
      comments: false
    })
  ]
};

Obiekt konfiguracyjny przekazywany do konstruktora wtyczki UglifyJsPlugin pozwala na konfigurację wszystkich opcji jakie dostępne są w narzędziu UglifyJS.

Uwaga 1: Plugin ten wymaga do działania osobnej instalacji narzędzia UglifyJS, dostępnego standardowo w npm.

Uwaga 2: W momencie pisania tego tekstu, istnieją znane problemy z minifikacją kodu wynikowego w ES6. Jest na to rozwiązanie wymagające użycia pluginu w wersji dostępnej jako osobna paczka npm. Więcej znajdziesz w dokumentacji.

ExtractTextPlugin

O wtyczce tej wspominałem już przy okazji poprzednich części tej serii. Pozwala ona na wycięcie tekstu z “bundla” i wrzucenie do osobnego pliku wynikowego. Najczęściej wykorzystuje się ją do wyciągania kodu CSS do osobnych “bundli”.

Aby skonfigurować ją do tego właśnie celu nie wystarczy dodać wywołania konstruktora do tablicy plugins. Należy jeszcze odpowiednio zmodyfikować “loader” odpowiedzialny za obsługę plików CSS - ale o tym za moment…

…ponieważ najpierw plugin ten trzeba zainstalować (nie jest on wbudowany w webpacka):

yarn add --dev extract-text-webpack-plugin

Kiedy wtyczka jest już zainstalowana, można już jej użyć do odpowiedniego wydzielania kodu CSS do osobnych plików:

const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {

  // ...

  module: {
    rules: [

      // ...

      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader', 'sass-loader']
        })
      }
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.bundle.js'
    }),
    new webpack.optimize.UglifyJsPlugin({
      beautify: true,
      comments: false
    }),
    new ExtractTextPlugin({
      filename: '[name].bundle.css'
    })
  ]
};

Zanim przejdę do omówienia konfiguracji, zwróć uwagę na import obiektu ExtractTextPlugin z pakietu, który przed momentem zainstalowaliśmy.

Omówienie konfiguracji

W przykładzie pokazałem użycie pluginu ExtractTextPlugin zarówno dla plików CSS jak i Sass. Zwróć uwagę jak, w stosunku do tego co pokazałem w poprzednim wpisie, zmodyfikowany został kod konfiguracji loaderów. Do właściwości use przypisano wywołanie funkcji ExtractTextPlugin.extract(). Przekazano do niej obiekt z opcjami, który zawiera dwie właściwości: fallback oraz use.

Pierwsza z tych właściwości służy do skonfigurowania dodatkowego loadera dla kodu CSS, który nie został wyekstrahowany. To dlatego, że standardowo wtyczka wycina kod CSS tylko z głównych “bundli”. Pomija natomiast dodatkowe, stworzone na przykład przez plugin CommonsChunkPlugin). Zwykle jest to porządane zachowanie, ponieważ “bundle” generowane przez tę wtyczkę to zwykle moduły zaimportowane z node_modules.

Druga opcja konfiguracyjna funkcji ExtractTextPlugin.extract to już konfiguracja odpowiednich loaderów, mających odpowiednio zająć się kodem CSS zaimportowanym jako moduły JavaScript.

Na koniec spójrz jeszcze na tablicę plugins. Do konstruktora ExtractTextPlugin przekazuję obiekt zawierający jedną właściwość: filename. Służy ona do sterowania nazwą generowanego pliku CSS. Jak widzisz, użyłem w niej “placeholdera” [name]. Dzięki temu dla każdego z “bundli” będzie wyekstrahowany osobny plik CSS (np. dla home.bundle.js powstanie też plik home.bundle.css itd.).

Wtyczka ma dość spore możliwości konfiguracji, warto więc samemu przeanalizować dostępne opcje, o których więcej przeczytasz w dokumentacji.

HtmlWebpackPlugin

Mamy już omówione trzy popularne pluginy. Nadszedł więc czas na ostatni, jaki dziś omówię… a jest nim wtyczka HtmlWebpackPlugin. Służy ona do generowania pliku index.html, w którym automatycznie załączane są odpowiednie “bundle” (zarówno JS jak i CSS). Jest to narzędzie przydatne szczególnie przy tworzeniu aplikacji typu SPA.

Wtyczka ta również nie jest wbudowana w webpacka, wymagana jest więc najpierw jej instalacja:

yarn add --dev html-webpack-plugin

Gdy mamy ją już zainstalowaną, można przejść do jej konfiguracji - w najprostszym przypadku nie ma tutaj wielkiej filozofii:

const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {

  // ...

  module: {
    rules: [

      // ...

      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader', 'sass-loader']
        })
      }
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.bundle.js'
    }),
    new webpack.optimize.UglifyJsPlugin({
      beautify: true,
      comments: false
    }),
    new ExtractTextPlugin({
      filename: '[name].bundle.css'
    }),
    new HtmlWebpackPlugin()
  ]
};

Oczywiście, z racji tego, że jest to osobny pakiet, musimy go najpierw zaimportować do zmiennej.

Jedyne co musimy zrobić później, to dodanie wywołania konstruktora obiektu HtmlWebpackPlugin. Jeśli nie przekażemy mu obiektu konfiguracyjnego, to po uruchomieniu komendy webpack w konsoli, w katalogu docelowym pojawi się plik index.html, wyglądający mniej więcej tak:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
    <link href="home.bundle.css" rel="stylesheet"></head>
  <body>
    <script type="text/javascript" src="vendor.bundle.js"></script>
    <script type="text/javascript" src="home.bundle.js"></script>
    <script type="text/javascript" src="post.bundle.js"></script>
    <script type="text/javascript" src="contanct.bundle.js"></script>
  </body>
</html>

Jak widzisz, na końcu sekcji body załączone zostały wszystkie wynikowe “bundle” (łącznie z vendor.bundle.js wygenerowanym przez plugin CommonsChunkPlugin). Oprócz tego, w sekcji head załączony został plik home.bundle.css wygenerowany przez wtyczkę ExtractTextPlugin.

Oczywiście możliwe jest też wygenerowanie wielu wynikowych plików HTML, na przykład osobnych dla każdego z “bundli”. Po więcej informacji odsyłam do dokumentacji tej wtyczki!

Pluginy webpack - podsumowanie

W ten sposób dobrnęliśmy do końca tego wpisu - mam nadzieję, że pluginy webpacka nie będą już dla Ciebie tajemnicą… Ogólnie można powiedzieć, że wraz z loaderami stanowią one trzon każdej konfiguracji. Warto więc wiedzieć jak z nich korzystać.

Tak sobie myślę, że pojawi się jeszcze co najmniej jedna część serii na temat podstaw konfiguracji webpacka! Postaram się opisać w niej strategie dotyczące konfiguracji deweloperskiej i produkcyjnej. Wydaje mi się, że warto by również było omówić wbudowany w webpacka lokalny serwer deweloperski. Nie wspominając juz o takim temacie jak “hot realoading”! Pytanie tylko czy zmieszczę wszystko jednym wpisie czy rozbiję temat na więcej artykułów?

P.S. Myślę, że czas już udostępnić link do repozytorium GitHub [klik], w którym znajdziesz omówione dotychczas przykłady.


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+: