Grunt.js i automatyzacja pracy - początek

Lepiej sformatowany artykuł z podlinkowaniem możecie znaleźć tu:
http://psds.pl/grunt-js-i-automatyzacja-pracy/

Słowem wstępu - czym jest Grunt?

Grunt.js jest task runnerem w którym możemy stworzyć listę zadań (użyjąc gotowych lub napisać swoje) wykonywane pod odpaleniu odpowiedniego polecenia. Pozwala nam to oszczędzić sporo czasu, zautomatyzować naszą prace i pozbyć się czasem nudnej i żmudnej pracy. Dla przykładu: komplipacja scss bądź less, minifikacja css, generowanie sprite'ów, sprawdzenie błedów w plikach JS i wiele więcej.

Sama instalacja i konfiguracja jest dziecinnie prosta, choć wymaga trochę czasu i pomysłów co możemy jeszcze usprawnić (a zawsze można).

Na początek przygotujmy nasze środowisko.

Potrzebujemy:

Node.js - instalujemy najnowszą wersje, automatycznie doda nam PATH do konsoli dzięki czemu będziemy mogli instalować moduły poleceniem npm install
Grunt.js - po instalacji node.js w konsoli wykonujemy polecenie npm install -g grunt-cli
Pomysły co można sobie zautomatyzować ;)

Zaczynamy zabawę

Podzielimy zadania na 2 środowiska - developerskie i produkcyjne.
W pierwszym znajdą się zadania wykonywane przy każdej zmianie w pliku, w produkcyjnym natomiast zadbamy o odchudzenie plików.

Środowisko developerskie

Na początek ułatwmy sobie prace z scss.
Przyjrzyjmy się strukturze tasków w Gruncie. Może przerażać, ale bez obawy - całość jest dziecinnie prosta!


module.exports = function(grunt) {
    // Project configuration.
    grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    task: {
        opcje: {

        },
        operacje: {

        }
    }
  });
   grunt.loadNpmTasks('modul-grunta'); // w taki sposób ładujemy nasze moduły
   grunt.registerTask('default', ['zadanie domyślne']); // określany jakie zadania wykonują się po wpisaniu w konsoli grunt
};

Wypadałoby od razu wyjaśnić z czego jest zbudowany plik Gruntfile.js. Skruktura pliki zazwyczaj jest taka sama, różni się zadaniami (czy zawsze o tym innym razem).

W 4 linii: pkg: grunt.file.readJSON('package.json'), mamy odniesienie do pliku package.json - plik ten zawiera informację o tym z jakich modułów korzystamy. Na razie się nim nie przejmujmy.

Poniżej pierwsze zainicjowanie taska. Zadania możemy znaleźć na Gruntjs.com lub npmjs.com.

Na pierwszy ogień idzie kompilacja plików scss. Użyję do tego libsassa - jest zdecydowanie szybszy niż compass i sass.
Pobierzemy sobie zadanie grunt-libsass poleceniem npm install grunt-libsass --save-dev (dopisek --save-dev dodaje nam pakiet do pliku package.json, więc warto go używać).
Czas na stworzenie taska.

libsass: { //zadanie główne
    files: {
        expand: true,
        src: ['css/style.scss'], // pliki na których domyślnie ma byc wykonane zadanie
        dest: '', // gdzie mają się zapisać skompilowane pliki css (w tym przypadku zapisze się w lokalizacji pliku scss
        ext: '.css' // rozszenie po skimpilowaniu
    }
},

Pełna lista ustawień przeważnie znajduje się na stronie pakietu bądź na githubie autora.

Teraz trzeba powiedzieć gruntowi, żeby załadowal nasze zadanie:
grunt.loadNpmTasks('grunt-libsass'); Teraz wystarczy odpalić polecenie grunt libsass, żeby skompilowało nasze pliki.

Ale to nadal za mało. Czemu nie miałoby kompilować od razu po zapisaniu pliku? I to jakiegokolwiek w folderze z naszymi scss?

I z pomocą przychodzi nam obowiązkowe zadanie: watch!
Instalujemy: npm install grunt-contrib-watch --save-dev, konfigurujemy:

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: grunt.loadNpmTasks('grunt-contrib-watch'); I dodajemy watch do domyślnych zadań (wykonywanych po odpaleniu polecenia grunt.
grunt.registerTask('default', ['watch']); Zapisujemy, odpalamy w konsoli grunt i od teraz każda modyfikacja pliku scss w folderze css będzie powodować automatyczne kompilowanie.

Sprite - wszystkie ikony w jednym obrazku

Żeby nieco zmniejszyć liczbę zapytać i przyspieszyć działanie strony możemy latwo scalić ikony w jeden obrazek i dla każdej ikony zmieniać background-position. Bez obaw - nic nie musimy pisać sami! Wykorzystamy grunt-spritesmith. Instalujemy npm install grunt-spritesmith --save-dev.

Następnie zadanie dla Grunta:

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
            }
          }
    }
}

Zadanie dla Was to stworzenia taska z optymalizacą otrzymanego PNG :) (np. grunt-pngmin).

Oto nasz cały plik gruntfile.js

module.exports = function (grunt) {
    'use strict';
    grunt.initConfig({
        sprite: {
            all: {
                src: 'images/icons/*.png', // folder z naszymi ikonami
                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
                    }
                }
            }
        },
        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.js
    grunt.registerTask('default', ['watch']);
}; 

Możemy również zdefiniować swoje zadania: grunt.registerTask('zadanie', ['inne', 'zadania', 'ktore', 'wykonujemy']);. Podobnie jest z wykonaniem pojedynczego zadania i subzadania:
grunt libsass wykonana nam wszystko z libsass, ale już grunt libsass:files tylko to co znajduje się w files.

Dodajcie sobie również task grunt-notify żeby być powiadamianym o udane bądź nie kompilacji, w końcu nie patrzymy ciągle na konsole.

Generowanie środowiska produkcyjnego

Przy generowaniu środowiska produkcyjnego powinno nam zależeć na możliwie największym zmniejszeniu wagi plików - nie musi to być czytelne.
Jednym poleceniem załatwimy sprawę zminifikowania plików css, js i html. Zakładam, że korzystacie z @import w scss także pomijam łączenie.

Wykorzystamy zadanie grunt-contrib-compressor, idealnie się nada a jest bardzo proste w obsłudze.
Całość oczyszczona i odchudzona będzie trzymana w folderze dist.
Oto task:

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']
        }
    }
},

Jak możecie zauważyć niewiele różni się od przykładu na Githubie. A żeby oduczyć stosowania kopiuj-wklej zostawiam Wam modyfikację do wprowadzenia - minifikację wszystkich plików HTML.

I tu małe zadanie: czasem zdarzy się, że nie działają JSy. Problemem może być brak średników. Jest od tego zadanie - dodaje (skutecznie) średniki dla końcu linii tam gdzie potrzeba. jssemicoloned może być pomocny! Dajcie znać o efektach :)

Nasz Gruntfile.js na koniec powinien wyglądać tak:

module.exports = function (grunt) {
  'use strict';
  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'
          }
      },
      notify: {
          watch: {
              options: {
                  title: 'Wszystko ok!',
                  message: 'Bez błedów, oby tak dalej!'
              }
          }
      },
      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,
                  collapseWhitespace: 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-notify');
  grunt.loadNpmTasks('grunt-spritesmith');

  // rejestrowanie domyślnego zestawu zadań dla Grunt.js
  grunt.registerTask('default', ['watch', 'notify']);
  grunt.registerTask('prod', ['compressor', 'notify']);
};