Webpack i babel

Wsparcie

Gdy spojrzysz na tabelę ze strony http://kangax.github.io/compat-table/es6/ zobaczysz, że wsparcie dla ES6 jest już całkiem dobre.

Niektóre funkcjonalności nie są jeszcze wspierane - w szczególności implementacja modułów, które są jednym z kluczowych dóbr, które wprowadziła ta wersja. Gdy piszę te słowa wciąż trwa dyskusja jak taka implementacja ma wyglądać w końcowej fazie. Safari już wprowadziło swoją implementację, Chrome i Firefox dają możliwość włączenia takiej funkcjonalności w ukrytych ustawieniach przeglądarki.

Pomijając moduły, część przeglądarek nie nadąża za standardami i zatrzymała się na wersji ES5. Dla przykładu używane przez nas let i const są bardzo dobrze wspierane, ale już interpolacja stringów, czy klasy wymagają naszego wsparcia.

Żeby móc używać dzisiaj takich funkcjonalności musimy - podobnie jak przy SASS zamienić nasz nowy kod na odpowiednio transpilowany kod, który zadziała na starszych przeglądarkach.

W dzisiejszych czasach do takiej transpilacji najczęściej używa się Babel, która zamienia nowy kod na starszy odpowiednik.

Inicjalizacja projektu

Jest wiele sposobów na użycie Babel.

Jednym z najbardziej popularnych sposobów jest użycie jej wraz z webpackiem.

Co to jest webpack? Jest to tak zwany bundler, czyli narzędzie, które czyta wskazany przez nas plik JS, a następnie wykonuje na nim zadane czynności. Jeżeli dla przykładu znajdzie w kodzie importowanie innego modułu (innego pliku JS), dołączy go do naszego kodu. Takich różnych akcji jest cała masa. Webpack może łączyć skrypty, minimalizować je, może zamieniać nasze SCSS na odpowiedni kod JS (tak byśmy mogli takie style potem używać w js), może optymalizować kod html, dołączać do js zakodowane grafiki itp.

W wyniku takich akcji nasze źródłowe skrypty są zamieniane na jeden wynikowy, zminimalizowany i zoptymalizowany plik.

Webpack słynie z tego, że jego konfiguracja do najprostszych nie należy.

Na szczęście jego podstawowa konfiguracja wcale taka ciężka nie jest.

Zaczynamy od zainstalowania NodeJS na swoim komputerze. Wchodzimy na stronę https://nodejs.org/en/ i dokonujemy instalacji. Różni się ona w zależności od stytemu. Na Windowsie nie powinno być żadnego problemu. Na makach i Linuxach przyda się użycie sudo na początku komend.

W przypadku Ubuntu trzeba w terminalu wpisać następujące komendy (zatwierdzając je enterem):


sudo apt-get update
sudo apt-get install nodejs

Po instalacji node przechodzimy do instalacji webpacka.

Tak samo jak resztę pakietów Node, webpacka możemy zainstalować globalnie lub lokalnie per projekt. Obie metody są dobre - my skorzystamy z instalacji per projekt.

Wchodzimy do katalogu z naszym projektem i wpisujemy w konsoli:


npm init

Co spowoduje inicjację projektu. Będziemy musieli odpowiedzieć na kilka pytań odnośnie naszego projektu. Musimy czytać, co pojawia się w konsoli, bo dla przykładu pierwsze pytanie dotyczy nazwy naszego projektu i domyślna nazwa brana jest na bazie nazwy katalogu. Jeżeli nasz katalog zawiera duże litery lub spacje, wtedy nie będziemy mogli wybrać domyślnej wartości, a wpisać własną nazwę. Na szczęście wszystkie komunikaty pojawiają się w terminalu.
Nie musimy dokładnie odpowiadać na pytania. Kilka szybkich enterów powinno załatwić sprawę.

Możemy też zamiast npm init wpisać npm init -y, co spowoduje automatyczne odpowiedzi na wszystkie pytania.

Po inicjacji pojawi się nam w katalogu plik packages.json, który jest plikiem json, zawierającym nie tylko informacje na temat projektu (te które właśnie wpisywaliśmy), ale rzeczy, które zainstalujemy dla naszego projektu (np webpack, gulp itp). Plik ten spokojnie możesz edytować za pomocą dowolnego edytora.

Dzięki powyższemu plikowi, osoba która ściągnie nasz projekt z repozytorium nie bedzie musiała się przebijać przez cały proces szukania i instalacji odpowiednich modułów, a wykona tylko jedno polecenie


npm install

które automatycznie zainstaluje jej wszystkie niezbędne moduły.

Na Linuxach i makach przy takich instalacjach bardzo często będzie trzeba je odpalać z odpowiednimi uprawnieniami poprzedzając je komendą sudo. W przeciwnym razie w terminalu pojawią się błędy wynikające z braku uprawnień. W kolejnych krokach pomijam tą komendę, ale pamiętaj, że jeżeli w twoim terminalu pojawią się czerwone komunikaty błędów (ERR:), wtedy spróbuj użyć sudo np.

    sudo npm install
    
Jeżeli pojawią się żółte komunikaty, nie musimy się nimi martwić - to tylko komunikaty informacyjne.

W nowszych wersjach poza package.json może nam się też pojawić plik package-lock.json, który ma w sobie zapisy dotyczące instalowanych przez nas modułów. Możemy go pominąć.

Instalacja webpacka

Po rozpoczęciu projektu przechodzimy do instalacji webpacka.

Będąc dalej w terminalu w naszym katalogu wpisujemy:


npm install webpack --save-dev

alternatywą do powyższego polecenia jest


npm i webpack -D

co zainstaluje nam webpacka w danym projekcie. Możemy to sprawdzić na dwa sposoby. Po pierwsze powinien pojawić się nam katalog node_modules, w którym pojawi się bardzo dużo katalogów i plików (baaardzo dużo). Wśród nich będzie katalog webpack.
Drugim sposobem jest podglądnięcie pliku package.json.
Powinna się w nim pojawić sekcja devDependencies, która będzie zawierać w sobie wpis o webpacku:


{
  "name": "projekt",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {
    "webpack": "^3.5.5"
  }
}

Właśnie w tej sekcji będą się pojawiać wpisy o instalowanych przez nas rzeczach. Dzięki temu inne osoby ściągając nasz projekt nie będą musiały za każdym razem instalować wszystkich rzeczy z osobna, a zainstalują wszystko naraz za pomocą polecenia npm i.

Odpalamy webpacka

Webpack ogólnie działa tak, że wskazujemy mu plik wejścia (plik w którym my piszemy skrypty), a następnie wskazujemy mu plik, do którego zostanie zapisany transpilowany kod.

Przypuśćmy, że nasza struktura katalogów wygląda tak:

Webpack struktura projektu

Mamy więc katalog src, w którym mamy wszystkie pliki na których pracujemy. Mamy też katalog dist, w którym będzie strona wynikowa, a w nim katalog js, w którym pojawi się transpilowany plik script.js, który dołączymy w naszym index.html.

Podstawowe odpalenie webpacka polega na odwołaniu się do webpacka, a następnie podanie 2 parametrów: pliku wejściowego i pliku wyjściowego. Webpack został zainstalowany w node_modules, a jego skrypt mieści się dokładnie w node_modules/webpack/bin. Aby więc odpalić ręcznie webpacka musimy użyć polecenia:


node node_modules/webpack/bin/webpack.js src/js/app.js js/bundle.js

Po odpaleniu powyższej komendy w terminalu projektu powinna nam się pojawić informacja o zakończonej kompilacji.

Jak widzisz, powyższe polecenie do najprostszych nie należy. Spróbujmy to usprawnić.

Jednym ze sposobów jest wykorzystanie skryptów npm. Jeżeli otworzysz plik package.json, znajdziesz w nim sekcję script:


...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
},
...

Jest to przykładowy skrypt o nazwie test. Zamieńmy jego treść na:


...
"scripts": {
    "test": "echo 'To nasz testowy skrypt'"
},
...

i odpalmy poleceniem:


npm run test

Jak widzisz skrypt ten tylko wypisał w konsoli powyższy tekst. Skrypty więc służą do odpalania jakiś komend w terminalu. Wykorzystajmy je do odpalania webpacka. Skrypt test nie będzie nam już potrzebny, dlatego zamieńmy go poniższym:


...
"scripts": {
    "build": "webpack"
},
...

Powyższy skrypt odpala webpacka. Odpalając moduły za pomocą skryptów z pliku package.json nie musisz podawać im dokładnej ścieżki do moduły (czyli nie musimy wskazywać dokładnie gdzie leży webpack), ponieważ npm wie gdzie, że ma ich szukać w node_modules.

Uruchamiamy więc nasz skrypt poleceniem:


npm run build

Po odpaleniu powyższego skryptu, w konsoli pojawi się komunikat:


No configuration file found and no output filename configured via CLI option.
A configuration file could be named 'webpack.config.js' in the current directory.

Webpack wyraźnie daje nam znać, że brakuje pliku konfiguracji. Gdy pierwszy raz odpaliliśmy webpacka, podawaliśmy mu 2 atrybuty - plik wejściowy i wyjściowy. Odpalając webpacka w powyższy sposób, musimy mu dostarczyć plik konfiguracyjny.

Konfiguracja webpacka

Podobnie do Gulpa, webpack też wymaga pliku konfiguracyjnego, który powie mu dokładnie co ma robić. W gulpie plik taki nazywa się gulpfile.js, natomiast dla webpacka plik konfiguracyjny nosi nazwę webpack.config.js

Tworzymy więc za pomocą dowolnego edytora w głównym katalogu naszego projektu (tam gdzie mamy package.json) plik webpack.config.js

W jego wnętrzu wpisujemy poniższy kod:


module.exports = {
    entry: "./src/js/app.js",
    output: {
        path: `${__dirname}/js`,
        filename: "bundle.js"
    },
    watch: true,
    devtool: "source-map",
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["es2015"]
                    }
                }

            }
        ]
    }
}

Konfiguracja webpacka zaczyna się od wskazania pliku wejściowego, od którego webpack zacznie czytać nasz kod. Służy do tego klucz entry.
Kolejną rzeczą jest wskazanie pliku wyjściowego czyli output. Można tutaj podawać w filename konkretną ścieżkę do pliku, a można i tak jak powyżej - podać katalog i nazwę pliku.

Właściwość watch włącza nam obserwowanie zmian w plikach. Po odpaleniu webpacka będzie on działał w tle i czekał na zmiany w plikach źródłowych. Gdy takie zauważy, odpali transpilację. Dzięki temu wystarczy, że raz go odpalimy i skupimy się na dalszej pracy. Aby zakończyć tak odpalonego webpacka, wystarczy w terminalu nacisnąć Ctrl + C kilka razy.

Kolejną właściwością jest source-map. Wyobraź sobie, że w naszym kodzie źródłowym zrobiliśmy błąd. Po dołączeniu do index.html wynikowego pliku nie bylibyśmy w stanie stwierdzić, w którym pliku znajduje się błędny kod, bo przecież w przeglądarce mamy dołączony kod skompilowany. Dzięki source-maps webpack dołącza plik z mapą, która mapuje skompilowany kod na pliki źródłowe. Dzięki temu badając JS w przeglądarce debuger będzie nam wskazywał ścieżki do plików źródłowych.

Kolejna duża sekcja to module.
Webpack czyta nasz kod. Jeżeli trafi na instrukcję inport, dołączy do danego pliku importowany kod. Jeżeli trafi np. na importowany plik scss, który tutaj może być importowany do js, wtedy musi wykonać odpowiednie akcje. Właśnie w tej sekcji konfiguracji podajemy mu jak webpack ma reagować na odpowiednie formaty plików które są dołączane w kodzie.

W naszym przypadku interesują nas pliki JS, które domyślnie webpack po prostu by złączył z sobą. My chcemy jednak by webpack dodatkowo taki kod transpilował za pomocą Babel. Używamy do tego celu tak zwanego loadera. Loadery to właśnie "dodatki" do webpacka, które mówią mu co ma robić z danymi formatami plików. Pliki JS loader babel transpiluje na ES5, ale np. loader sass dołączane do JS pliki z sasem zamienił by na odpowiedni kod.

Takich loaderów jest masa i wszystko zależy czego potrzebujesz. W praktyce wystarczy wpisać w google "webpack loader ..." gdzie kropki to interesujący nas format.

Żeby używać loadera babel, musimy go zainstalować. Wpisujemy więc w google webpack loader bable i przechodzimy na stronę https://github.com/babel/babel-loader. Musimy ten loader zainstalować - tak samo jak webpacka. W terminalu wpisujemy więc:


npm install babel-loader babel-core babel-preset-es2015 -D

Jeżeli spojrzysz na stronę babel-loader zauważysz, że powyższe polecenie nieco się różni od tego, które jest tam podane. Pierwsza różnica to brak webpacka - w końcu już go zainstalowaliśmy. Drugi to zastosowany babel-preset. Presety dla babela przypominają presety dla autoprefixera, czyli mówią nam na jaką wersję ma być transpilowany kod. W naszym przypadku chcemy wersję JS5, a nie najnowszą, dlatego właśnie zmieniliśmy preset z babel-preset-env na babel-preset-es2015. Poza presetem zainstalowaliśmy babel-loader - właśnie ten, który powie webpackowi co ma robić z plikami .js. Babel-core to sam Babel we własnej osobie.

Dodatkowo aby nasz plik wynikowy był zminimalizowany, dodajmy do webpacka plugin do minimalizacji. Plugin taki zwie się uglifyjs-webpack-plugin. Instalujemy go więc poleceniem


npm install uglifyjs-webpack-plugin -D

a następnie dodajemy go do konfiguracji webpacka:


const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    entry: "./src/js/app.js",
    output: {
        path: `${__dirname}/js`,
        filename: "bundle.js"
    },
    watch: true,
    devtool: "source-map",
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["es2015"]
                    }
                }

            }
        ]
    },
    plugins: [
        new UglifyJSPlugin({
            sourceMap: true
        })
    ]
}

Po instalacji powyższych modułów i zakończonej konfiguracji odpalamy webpacka za pomocą wcześniej przygotowanego skryptu:


npm run build

Tutaj możesz ściągnąć powyżej opisany mini projekt. Po ściągnięciu wypakuj go do katalogu z projektem, a następnie zainstaluj wszystkie paczki za pomocą polecenia npm i (lub sudo npm i).

Po instalacji możesz już działać odpalając webpacka poleceniem npm run build

Inne rozwiązania

To co pokazałem ci powyżej, jest jedną z miliona możliwości używania webpacka.

Można też go dla przykładu zainstalować globalnie, stworzyć plik konfiguracyjny (tak jak powyżej), a potem odpalać go bez skryptu bezpośrednio z konsoli poleceniem:


webpack

co dam nam w konsoli:

webpack console

Ja osobiście używam na co dzień Gulpa. Stworzyłem sobie więc podobny do powyższego plik konfiguracyjny webpacka, a następnie zintegrowałem go z Gulpem.


...
gulp.task('es6', function(cb) {
    return webpack(require('./webpack.config.js'), function(err, stats) {
        if (err) throw err;
        console.log(stats.toString());
        cb();
        browserSync.reload();
    })
})

gulp.task('watch', function() {
    gulp.watch('src/scss/**/*.scss', ['sass']);
    gulp.watch('src/js/**/*.js', ['es6']);
    gulp.watch("dist/**/*.html").on('change', browserSync.reload);
});

gulp.task('default', function() {
    console.log(gutil.colors.yellow('------ start ------'));
    gulp.start(['sass', 'es6', 'browseSync', 'watch']);
});

...

Dzięki temu odpalając Gulpa poleceniem gulp odpalam wszystkie taski - w tym ten, który odpala webpacka.
Metoda dla mnie bardzo wygodna, ale czy dla ciebie się sprawdzi? Wszystko zależy od twojego warsztatu webmastera i tego do czego używasz podobnych narzędzi.

Jest wiele bardzo dobrych artykułów na temat webpacka. Jednym z nich jest świetny tekst na stronie https://medium.com/javascript-training/beginner-s-guide-to-webpack-b1f1a3638460