[JS] Grunt to Grunt(.js) – czyli o tym jak zautomatyzować pracę webdevelopera
Pracując jako twórca stron internetowych i/lub programista aplikacji webowych na pewno zdarzają Ci się sytuacje gdy jesteś zmuszony wykonywać te same żmudne czynności, takie jak: minifikowanie plików, kompresja obrazków, walidacja kodu JS, itd., itp.
Nie da się ukryć, że to nie są najbardziej przyjemne aspekty pracy webdevelopera, ponieważ najczęściej zajmuje to sporo czasu, aby ukończyć wybrane zadanie.
Na szczęście z pomocą przychodzi nam język JavaScript oraz narzędzia stworzone przy jego pomocy, takie jak:
- serwer node.js,
- moduł Grunt.js oraz dodatki do niego,
- inne narzędzia oparte na JS.
Instalacja serwera node.js
By móc rozpocząć pracę z Grunt.js należy najpierw posiadać zainstalowany serwer node.js. Jest to serwer javascriptowy, który w swoim działaniu przypomina serwer Apache, ale jego największą zaletą jest wykorzystanie modelu zarządzania dostępem do serwera opartym na zdarzeniach (eventach) oraz asynchronicznym obsługiwaniu danych wejściowych i wyjściowych, co pozwala na budowanie aplikacji działających w czasie rzeczywistym i aplikacji, których działanie nie jest blokowane przez zakolejkowane zapytania do serwera.
W celu zainstalowania serwera należy się udać na stronę skąd można pobrać node.js na każdy popularny typ systemu. Co oznacza, że taki serwer można zainstalować, między innymi, na komputerach z systemem Windows. W przypadku systemu Windows pobieramy program instalacyjny i instalujemy stosując się do poleceń na ekranie. Po zainstalowaniu, serwer node.js obsługujemy za pomocą linii komend/terminala systemowego oraz odpowiednio przygotowanych plików JS. Na potrzeby tego artykułu ograniczymy się obsługi terminala oraz plików wymaganych przez Grunt.js.
Instalacja obsługi Grunt.js
Gdy mamy już zainstalowany serwer node.js, to możemy przystąpić do instalacji Grunt.js. Za pomocą terminala przejdźmy do folderu gdzie znajduje się projekt. W systemie Windows można odpalić terminal w otwartym folderze po kliknięciu prawym przyciskiem myszy przy jednoczesnym przytrzymaniu klawisza Shift i po wybraniu z listy opcji Otwórz okno polecenia tutaj.
Gdy mamy otwarte okno wiersza poleceń, wpisujemy:
npm install -g grunt-cli
Ta komenda instaluje obsługę modułu Grunt.js, ale jeszcze nie instaluje samego modułu. Aby to zrobić należy przygotować 2 pliki: Gruntfile.js i package.json.
Przygotowanie pliku package.json
Package.json jest to plik odpowiedzialny za przechowywanie danych konfiguracyjnych lub opisowych odnośnie danego projektu. Możemy go utworzyć na 2 sposoby:
- Z wykorzystaniem linii komend:
-
npm init
Kolejno uzupełniając dane o które nas pyta generator. I wtedy powstanie plik bazowy mogący wyglądać w sposób następujący:
1 2 3 4 5 6 7 8 9
{ "name": "blog-grunt", "version": "1.0.0", "description": "Przykładowy wygląd pliku konfiguracyjnego projektu", "author": "Piotr Nalepa", "devDependencies": { "grunt": "~0.4.2" } }
- Ręcznie
- Można tez taki plik utworzyć ręcznie poprzez skopiowanie powyższego kodu. Warto pamiętać, że taki plik konfiguracyjny może zawierać o wiele więcej potrzebnych danych o projekcie. Możemy te dodatkowe dane sami zdefiniować w razie potrzeby.
Instalacja samego Grunt.js
Wcześniej instalowaliśmy obsługę dla Grunt.js a teraz pora zainstalować moduł sam w sobie. Aby tego dokonać, należy w terminalu wpisać komendę:
npm install grunt --save-dev
Powyższa komenda instaluje moduł Grunt.js dla serwera node.js wraz instrukcją, aby zostało zapisane powiązanie modułu Grunt.js z projektem w pliku package.json co jest widoczne w powyższym przykładzie kodu w miejscu - devDependencies. W ten sam sposób można zainstalować zarówno moduły do serwera node.js jak i rozszerzenia dla modułu Grunt.js:
npm install [nazwa-modulu] --save-dev
Za pomocą powyższej komendy łączymy się z Internetem i pobieramy najnowszą wersję modułu/rozszerzenia z repozytorium node.js. To jest ważne, bo za pomocą tej komendy będziemy musieli zainstalować kolejno:
- grunt-contrib-concat
- Rozszerzenie do Grunt.js pozwalające na łączenie wielu plików w jedną całość.
- grunt-contrib-cssmin
- Rozszerzenie do Grunt.js kompresujące kod CSS w celu zmniejszenia wagi pliku.
- grunt-contrib-less
- Rozszerzenie do Grunt.js konwertujące kod napisany w LESS do CSS. Istnieje również rozszerzenie dla SASS.
- grunt-contrib-uglify
- Rozszerzenie do Grunt.js kompresujące kod JavaScript, a także potrafiące odpowiednio zmniejszyć czytelność kodu w celu utrudnienia poznania zastosowanych rozwiązań w kodzie JS (przy okazji jeszcze bardziej zmniejsza wagę pliku końcowego Javascript).
- grunt-imageoptim
- Rozszerzenie do Grunt.js pozwalające na bezstratną kompresję obrazków w celu zmniejszenia ich wagi bez utraty jakości.
- grunt-contrib-watch
- Rozszerzenie do Grunt.js obserwujące zmiany w plikach i automatycznie uruchamiające określone zadania. Ponadto, automatycznie odświeża treść strony lub aplikacji nad którą pracujemy.
Konfigurację powyższych rozszerzeń będę chciał omówić w dalszej części tego artykułu. Poza rozszerzeniami wymienionymi wyżej, biblioteka rozszerzeń do Grunt.js zawiera ich o wiele więcej i warto ją przejrzeć, bo być może znajduje się rozszerzenie, które spełni Twoje oczekiwania.
Konfiguracja zadań w Grunt.js w pliku Gruntfile.js
Po zainstalowaniu wyżej wymienionych rozszerzeń możemy przystąpić do konfiguracji zadań, które będzie realizować za nas Grunt. Wszystkie te ustawienia i zadania będą definiowane w pliku Gruntfile.js. Struktura tego pliku rozpoczyna się od następującego kodu:
1 2 3 | module.exports = function (grunt) { 'use strict'; }; |
Jest to tzw. wrapper dla kodu konfiguracji zadań. To w nim będziemy będziemy definiować zachowanie poszczególnych rozszerzeń. Przykładowa konfiguracja zadań, może wyglądać następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | module.exports = function(grunt) { 'use strict' // inicjalizacja konfiguracji zadań grunt.initConfig({ /* * Kompresja plików JS * * https://github.com/gruntjs/grunt-contrib-uglify */ uglify : { build : { // szukamy wszystkich plików JS, które nie są już skompresowane src : ['**/*.js', '!*.min.js'], // katalog roboczy, tj. katalog gdzie znajdują się pliku nad którymi pracuje // w danej chwili webdeveloper cwd : 'dev/js/', // katalog, gdzie znajdują się pliki w wersji produkcyjnej, po kompresji dest : 'dev/js/min/', expand : true, /* * Domyślnie, uglify tworzy nazwy plików wycinając wykropkowane nazwy * dlatego utworzyłem callback, którego zadaniem jest utworzenie nazwy * poprzez dodanie końcówki - .min.js do pierwotnej nazwy pliku. * Dzięki temu, zostanie zachowana pierwotna nazwa z dodanym słowem - min, * w nazwie pliku. * * [Błędne zachowanie] * Pliki przed kompresją: * dev/js/plugins/jquery.plugin.tabs.js * dev/js/plugins/jquery.plugin.modal.js * * Pliki po kompresji: * prod/js/plugins/jquery.min.js - tego nie chcemy * * [Poprawione zachowanie] * dev/js/plugins/jquery.plugin.tabs.js * dev/js/plugins/jquery.plugin.modal.js * * Pliki po kompresji * prod/js/plugins/jquery.plugin.tabs.min.js * prod/js/plugins/jquery.plugin.modal.min.js */ rename : function (dest, src) { // wyciągnij z ciągu ścieżkę do folderu var folder = src.substring(0, src.lastIndexOf('/')); // wyciągnij z ciągu nazwę pliku var filename = src.substring(src.lastIndexOf('/'), src.length); // z nazwy pliku wyciągnij samą nazwę bez rozszerzenia filename = filename.substring(0, filename.lastIndexOf('.')); // zwróć nową ścieżkę z nową nazwą pliku zminifikowanego return dest + folder + filename + '.min.js'; } } }, /* * Aktywujemy przetwarzanie plików LESS obsługiwanych * przez preprocesor CSS. W wersji developerskiej mamy plik * nie zminifikowany, a w wersji produkcyjnej plik jest odpowiednio * zminifikowany, aby zmniejszyć jego wagę. * * https://github.com/gruntjs/grunt-contrib-less */ less : { dev : { options : { paths : ['dev/css/'] }, files: { 'dev/css/style.css' : 'dev/css/style.less' } }, prod : { options : { paths : ['dev/css/'], cleancss : true }, files : { 'prod/css/style.css' : 'dev/css/style.less' } } }, /* * Łączenie już skompresowanych plików w jedną całość. * Takie podejście ułatwia debuggowanie ewentualnych problemów, * które czasem mogą wystąpić po kompresji plików. * * https://github.com/gruntjs/grunt-contrib-concat */ concat : { /* * Łączymy wszystkie zminifikowane pliki w jeden * dzięki czemu możemy przyspieszyć ładowanie strony poprzez * zmniejszenie liczby wymaganych żądań wysyłanych do serwera. */ js : { options : { separator : ';' }, src : ['dev/js/min/**/*.min.js'], dest : 'prod/js/all.min.js' } }, /* * Optymalizacja obrazków * * https://github.com/JamieMason/grunt-imageoptim */ imageoptim : { options : { quitAfter : true }, images : { files: [{ expand : true, cwd : 'dev/img/', src : ['**/*.{png,jpg,gif}'], dest : 'prod/img/' }] } }, /* * Nasłuchiwanie zmian w plikach i uruchamianie odpowiedniego zadania. * Przy okazji, automatyczne odświeżanie strony w przeglądarce. * * https://github.com/gruntjs/grunt-contrib-watch */ watch : { html : { files : 'dev/index.html', options : { livereload : true } }, less : { files : 'dev/css/*.less', tasks : ['less'], options : { livereload : true } }, js : { files : 'dev/js/**/*.js', tasks : ['uglify', 'concat'], options : { livereload : true } }, img : { files : 'dev/img/**/*.*', tasks : ['imageoptim'], options : { livereload : true } }, } }); // ładowanie wybranych rozszerzeń dla Grunt.js grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-imageoptim'); // rejestrowanie domyślnego zestawu zadań dla Grunt.js grunt.registerTask('default', ['watch']); }; |
Muszę dodać, że jeśli chcemy aby strona WWW nam się automatycznie odświeżała, to należy gdzieś w kodzie HTML zamieścić następujący kawałek kodu:
1 | <script src="//localhost:35729/livereload.js"></script> |
Jest to odwołanie do serwera node.js, który przekazuje do przeglądarki użytkownika informację, że powinna ona odświeżyć swoją zawartość. Tym samym widoczny projekt będzie zawsze aktualny, bez potrzeby ręcznego odświeżania strony.
Należy również pamiętać o tym, aby nie przesadzać z ilością plików, w których mają być wykrywane zmiany, ponieważ występuje ograniczenie w konsoli odnośnie liczby jednocześnie przetwarzanych plików. Na szczęście, jeśli nie zamierzasz pracować na setkach plików jednocześnie, to problem Ciebie nie dotyczy.
Podsumowanie
Jak widać, Grunt.js ma ogromne możliwości i jego wykorzystanie w swoim procesie tworzenia kodu stron WWW lub aplikacji znacząco przyspiesza development jak i odciąża programistę od wykonywania ważnych, ale jednak uciążliwych czynności. Tym samym możesz się skupić na tym co jest najważniejsze, czyli pisaniu dobrego kodu. To wraz z poradami odnośnie narzędzi używanych w codziennej pracy sprawi, że praca stanie się jeszcze większą przyjemnością. Naprawdę zachęcam Ciebie do korzystania z tego narzędzia, bo z własnego doświadczenia wiem, że ma ono same zalety.