Jedną z podstawowych zasad w pracy każdego programisty jest reguła DRY (don’t repeat yourself – nie powtarzaj się) Ścisłe jej respektowanie pozwala nie tylko tworzyć lepsze oprogramowanie, ale również zaoszczędzić czas, który możemy przeznaczyć na ciekawsze rzeczy, niż wykonywanie nudnych, powtarzalnych czynności. W dzisiejszym artykule pokażę wam, jak ten czas zaoszczędzić z pomocą Grunt.
Słowem wstępu – czym jest Grunt?
Grunt jest systemem automatyzacji pracy, w którym możemy stworzyć listę (własnych lub stworzonych przez kogoś) zadań, które zostaną wykonane po odpaleniu odpowiedniego polecenia. Pozwala nam to oszczędzić sporo czasu, zautomatyzować pracę i pozbyć się czasem nudnego i żmudnego wykonywania ciągle tych samych zadań. Przykładowymi zadaniami, jakie możemy zlecić Gruntowi to: komplikacja Sass bądź Less do CSS, minifikacja CSS, generowanie tzw. sprite’ów, sprawdzenie błędów w plikach JavaScript i wiele więcej.
Sama instalacja i konfiguracja jest dziecinnie prosta, choć wymaga trochę czasu. Na początek przygotujmy nasze środowisko. Potrzebujemy:
- Node.js – instalujemy najnowszą wersję, w systemie Windows instalator automatycznie doda nam do
PATH
ścieżkę instalacyjną do Node.js, co z pozwoli używać w konsoli polecenianpm
. - Grunt – po instalacji Node.js, w konsoli wykonujemy polecenie
npm install -g grunt-cli
, co z kolei pozwolić używać w konsoli polecenia grunt.
- Zadań, które możemy sobie zautomatyzować.
Zaczynamy zabawę
Podzielimy zadania na dwa środowiska – developerskie i produkcyjne. W pierwszym znajdą się zadania wykonywane przy każdej zmianie w pliku, w produkcyjnym natomiast zadbamy o generowanie plików dla serwera produkcyjnego.
Środowisko developerskie
Na początek ułatwmy sobie prace z Sass. Przyjrzyjmy się strukturze pliku Gruntfile.js
, w którym umieszczamy zadania. Może przerażać, ale bez obawy – całość jest dziecinnie prosta!
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
taskName: { //nazwa zadania
options: { //opcje
},
operations: { //operacje
}
}
});
grunt.loadNpmTasks('grunt-module'); // w taki sposób ładujemy nasze moduły
grunt.registerTask('default', ['taskName']); // określany jakie zadania wykonują się po wpisaniu w konsoli grunt
};
W tym miejscu wypada od razu wyjaśnić, z czego jest zbudowany plik Gruntfile.js
. Struktura pliku zazwyczaj jest taka sama, różni się zadaniami. Polecenie grunt.file.readJSON('package.
odnosi się do pliku package.json – zawiera on informację o tym, z jakich modułów korzystamy. Na razie się nim nie przejmujmy.
Zajmijmy się kompilacją pliku z kodem Sass do pliku z kodem CSS. Należy w tym celu pobrać zadanie o nazwie grunt-libsass
, które się tym zajmie. Wydajemy w konsoli polecenie: npm install grunt-libsass --save-dev
. Gdy zadanie zostanie pobrane i zainstalowane, możemy przejść do jego konfiguracji:
libsass: { //zadanie główne
files: {
expand: true,
src: ['css/style.scss'], // pliki na których domyślnie ma być wykonane zadanie
dest: '', // gdzie mają się zapisać skompilowane pliki CSS (w tym przypadku zapisze się w lokalizacji pliku Sass)
ext: '.css' // rozszerzenie po skompilowaniu
}
}
Powyższy listing wyjaśnia cel użycia poszczególnych opcji w zadaniu. Pełna lista opcji zazwyczaj znajduje się na stronie pakietu na stronie npmjs.com bądź na stronie jego autora.
Teraz należy załadować zadanie, dopisując do pliku Gruntfile.js
linię: grunt.loadNpmTasks('grunt-libsass');
Jeśli wszystko zrobiliśmy dobrze, plik Gruntfile.js
powinien wyglądać tak, jak ten poniżej, a po wpisaniu polecenia grunt libsass
w konsolę, powinna rozpocząć się kompilacja pliku.
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
libsass: {
files: {
expand: true,
src: ['css/style.scss'],
dest: '',
ext: '.css'
}
}
});
grunt.loadNpmTasks('grunt-libsass');
};
Jednak wpisywanie polecenia za każdym razem, kiedy dokonamy zmian w pliku Sass, to sporo monotonnej pracy. Dlaczego nie nakazać Gruntowi kompilacji pliku natychmiast po wykryciu dokonanych w nim zmian? Z pomocą przychodzi nam obowiązkowe zadanie watch
. Instalujemy je wpisując w konsolę npm install grunt-contrib-watch --save-dev
i konfigurujemy następująco:
watch: {
scss: { // możemy określić dowolną nazwę
files: ['css/*.scss'], // określamy na jakich plikach pracujemy
tasks: ['libsass'] // wybieramy zadania jakie na nich wykonujemy, po kolei
}
}
Ładujemy zadanie za pomocą kodu grunt.loadNpmTasks('grunt-contrib-watch');
i dodajemy watch
do domyślnych zadań (wykonywanych po odpaleniu polecenia grunt
: grunt.registerTask('default', ['watch']);
. Zapisujemy plik Gruntfile.js
i odpalamy w konsoli polecenie grunt
Od teraz każda modyfikacja pliku Sass w folderze css
będzie powodować automatyczne kompilowanie. Należy jednak pamiętać, że jeżeli zamkniemy konsolę, zamkniemy również zadanie i kompilacja automatyczna zatrzyma się.
Sprite – wszystkie ikony w jednym obrazku
Aby nieco zmniejszyć liczbę zapytań do serwera i przyspieszyć działanie strony, możemy łatwo scalić ikony w jeden obrazek i dla każdej ikony zmieniać background-position
. Bez obaw – nic nie musimy pisać sami! Wykorzystamy zadanie grunt-spritesmith
. Instalujemy je za pomocą polecenia: npm install grunt-spritesmith --save-dev
.
Pozostało jeszcze skonfigurować to zadanie:
sprite: {
all: {
src: 'images/icons/*.png', // folder z naszymi ikonami, każda zmiana w nim będzie podować wygenerowanie sprita
dest: 'images/spritesheet.png', // wynikowy plik z ikonami
destCss: 'css/icons.scss', // plik gdzie wygeneruje nam mixin bądź css
padding: 10, // odstęp pomiędzy ikonami
cssOpts: {
cssClass: function (item) {
return '.icon-' + item.name; // przedrostek klasy
}
}
}
}
Po wykonaniu wyżej wymienionych kroków, rzut okiem na gotowy plik Gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
sprite: {
all: {
src: 'images/icons/*.png',
dest: 'images/spritesheet.png',
destCss: 'css/icons.scss',
padding: 10,
cssOpts: {
cssClass: function (item) {
return '.icon-' + item.name;
}
}
}
},
libsass: {
files: {
expand: true,
src: ['css/style.scss'],
dest: '',
ext: '.css'
}
},
watch: {
libsass: {
files: ['css/*.scss', 'css/**/*.scss'],
tasks: ['libsass']
},
sprite: {
files: ['images/icons/*.png'],
tasks: ['sprite']
}
}
});
// ładowanie wybranych modułów dla Grunt.js
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-libsass');
grunt.loadNpmTasks('grunt-spritesmith');
// rejestrowanie domyślnego zestawu zadań dla Grunt
grunt.registerTask('default', ['watch']);
};
Możemy również definiować własną grupę zadań do uruchomienia korzystając z metody grunt.registerTask('
. Podobnie jest z wykonaniem pojedynczego zadania i podzadania: grunt libsass
wykonana nam wszystko z libsass
, ale już grunt libsass:files
tylko to co znajduje się w files
.
Polecam zadanie grunt-notify, którego zadaniem jest informowanie o błędach podczas wykonywania zadania. Nie zawsze przecież musimy patrzeć na konsolę.
Generowanie środowiska produkcyjnego
Przy generowaniu środowiska produkcyjnego powinno nam zależeć na możliwie największym zmniejszeniu wagi plików – kosztem ich czytelności. Jednym poleceniem rozwiązujemy problem zminifikowania plików CSS, JavaScript i HTML. Służy do tego zadanie grunt-contrib-
(instalacja: npm install grunt-contrib-
). Całość – oczyszczona i odchudzona – będzie generowana do folderu dist
. Konfiguracja tego zadania wygląda następująco:
compressor: {
css:{
files: {
'dist/css/style.css': ['css/style.css']
}
},
js: {
options: {
mangle: true
},
files:grunt.file.expandMapping(['js/*.js','js/*/*.js'], '', {
rename: function(base,file) {
return 'dist/'+file;
}
})
},
html:{
options:{
removeComments: true, // usunie komentarze
collapseWhitespace: true
},
files:{
'dist/index.html': ['index.html']
}
}
}
Nasz Gruntfile.js finalnie powinien wyglądać tak:
module.exports = function (grunt) {
grunt.initConfig({
sprite: {
all: {
src: 'images/icons/*.png',
dest: 'images/spritesheet.png',
destCss: 'css/icons.scss',
padding: 20,
cssOpts: {
cssClass: function (item) {
return '.icon-' + item.name;
}
},
}
},
libsass: {
files: {
expand: true,
src: ['css/style.scss', 'css/icons.scss', 'css/modules/**/*.scss'],
dest: '',
ext: '.css'
}
},
compressor:{
css: {
files: {
'dist/css/style.css': ['css/style.css']
}
},
js: {
options: {
mangle: true
},
files:grunt.file.expandMapping(['js/*.js','js/*/*.js'], '', {
rename: function(base,file) {
return 'dist/'+file;
}
})
},
html:{
options:{
removeComments: true
},
files:{
'dist/index.html': ['index.html']
}
}
},
watch: {
libsass: {
files: ['css/*.scss', 'css/**/*.scss'],
tasks: ['libsass', 'notify']
},
sprite: {
files: ['images/icons/*.png'],
tasks: ['sprite']
}
}
});
// ładowanie wybranych rozszerzeń dla Grunt.js
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-compressor');
grunt.loadNpmTasks('grunt-libsass');
grunt.loadNpmTasks('grunt-spritesmith');
// rejestrowanie domyślnego zestawu zadań dla Grunt.js
grunt.registerTask('default', ['watch', 'notify']);
grunt.registerTask('prod', ['compressor', 'notify']);
};
Na koniec
Mam nadzieję, że ten artykuł zainteresuje was możliwością wykorzystania systemu automatyzacji pracy, jakim jest Grunt, w codziennej pracy. Warto jeszcze wymienić kilka zadań, o których nie wspomniałem w tym artykule, a które mogą okazać się niezwykle pomocne:
grunt-contrib-clean
– usuwa wskazane pliki i folderygrunt-contrib-copy
– pozwala na kopiowanie pliku z miejsca na miejscegrunt-contrib-concat
– pozwala na łączenie wielu plików w jedengrunt-contrib-less
– kompiluje kod Less do CSSgrunt-contrib-imagemin
– zmniejsza wagę obrazków
A może wykorzystujecie już Grunt? Chętnie przeczytam o Waszych zastosowaniach.