chevron-left chevron-right

[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.