To już ostatnia część dot. gulpa :) Tydzień temu opisałem ogólnie jak działa gulp. W tej zaś przejdziemy przez proces automatyzacji pracy. By nie komplikować sobie życia, wykorzystamy ten sam przykład który był w Co to jest Grunt.js? Część 3.

UWAGA: to jest przykład, nie twierdzę że najlepiej napisany, chodzi głównie o pokazanie co można zrobić.

Krok 0 – przygotowanie środowiska

W aplikacji będziemy wykorzystywać gulp jak i bower, więc warto sobie przypomnieć jak te dwie rzeczy zainstalować :)

npm install -g bower
npm install -g gulp-cli

Krok 1 – informacje na temat aplikacji i struktury katalogów

W tym celu wykorzystamy przykładową aplikację dostępną u mnie na github. Ściągnijmy ją sobie:

git clone https://github.com/Gutek/sample-todo-app.git

Krótka notka na temat struktury katalogów:

  • build – to tutaj będzie dostępna nasza aplikacja po wykonaniu tasków w grunt
  • test – tutaj są dostępne testy naszej aplikacji
  • src – kod źródłowy naszej aplikacji
    • css – stylesheet dla aplikacji
    • images – obrazki wykorzystywane przez aplikację
    • js – miejsce gdzie są wszystkie skrypty aplikacji
      • lib – lokalizacja skryptów ściągniętych przez bower

Krok 2 – przygotowanie workspace

Skoro już sięgnęliśmy wszystko, pora zainstalować już zdefiniowane paczki:

bower install
npm install

Zainstaluje nam to wszystkie wymagane komponenty bowera w katalogu lib (plik .bowerrc to określa) oraz zainstaluje lokalną wersję gulpa (jak i grunta, ale go olejemy w tym przypadku).

Krok 3 – pierwszy plugin do gulpa – jshint

Ze względu na to, że nasza aplikacja posiada kod w JavaScript, warto przepuścić ją przez analizator.

W tym celu, musimy zainstalować paczkę gulp-jshint – prawie wszystkie paczki jakie są dostępne, można znaleźć na stronie Gulp Plugins.

npm install jshint --save-dev
npm install gulp-jshint --save-dev

Z miejsca zainstalujmy dodatkową paczkę która spowoduje, że wyniki analizy będą “piękniejsze” – jshint-stylish :)

npm install jshint-stylish --save-dev 

Jak na razie do tej pory jedyną różnicą w całości (pomiędzy gruntem a gulpem) są nazwy paczek ;)

Jak już mamy te dwie paczki zainstalowane, otwórzmy plik gulpfile.js i nadpiszmy go:

var gulp = require('gulp');
var jshint = require('gulp-jshint');

// configuration
var paths = {
    allScripts: ['gulpfile.js',
        'src/js/*.js',
        'test/**/*.js'
    ]
};

// lint all js code
gulp.task('lint', function() {
    return gulp.src(paths.allScripts)
        .pipe(jshint())
        .pipe(jshint.reporter('jshint-stylish'));
});

Teraz, możemy odpalić nasz nowy task za pomocą:

gulp lint

Tutaj tak naprawdę widzimy różnicę, po pierwsze wykorzystujemy strumienie, po drugie, dla mnie jest to bardziej czytelne :)

Krok 4 – auto-śledzenie zmian

Mamy nasz pierwszy task, ale co z tego, że aby go odpalić za każdym razem musimy odpalać komendę:

gulp lint

Przecież to jest męczące. Kolejną różnicą pomiędzy gruntem a gulpem jest taka, że dla gulpa nie potrzebujemy żadnej dodatkowej paczki, gdyż usługa śledzenia plików jest dostępna by-default. Wystarczy, że zaktualizujemy nasz plik gulpfile.js:

var gulp = require('gulp');
var jshint = require('gulp-jshint');

// configuration
var paths = {
    allScripts: ['gulpfile.js',
        'src/js/*.js',
        'test/**/*.js'
    ]
};

// lint all js code
gulp.task('lint', function() {
    return gulp.src(paths.allScripts)
        .pipe(jshint())
        .pipe(jshint.reporter('jshint-stylish'));
});

// watch for changes on js code
gulp.task('watch', function() {
    gulp.watch(paths.allScripts, ['lint']);
});

Teraz możemy na przykład odpalić taks:

gulp watch

jak tylko zapiszemy jeden z śledzonych plików, zostanie wykonany task lint. Dzięki temu, możemy mieć okno cmd otwarte cały czas i widzieć co gdzie źle robimy :)

Do tej pory było parę zmian, trochę mniej paczek, trochę inne podejście. W następnych krokach dopiero zacznie się ta prawdziwa, znacząca różnica.

Krok 5 – pozostałe paczki

Ok, nie ma sensu rozbijać każdej paczki na kolejne punkty ;) to co chcemy uzyskać to aplikację w katalogu build, która będzie z minifikowana (zarówno pliki js, css jak i html) oraz te pliki które możemy niech zostaną połączone w jeden. Dużym plusem także byłaby możliwość wykonania testów.

Zaczynamy więc z instalacją paczek:

  • gulp-contact – umożliwia łączenie wielu źródeł w jedno. Źródło tutaj to będzie plik.
  • gulp-uglify – daje nam możliwość minifikacji plików JS
  • gulp-htmlmin – to samo co uglify ale dla plików HTML
  • gulp-cssmin – jak nazwa wskazuje, tym razem dla CSS
  • gulp-rename – zezwala na różnego rodzaju zmianę nazwy pliku/strumienia. Na przykład po przez dodaje rozszerzenia czy kompletnie inaczej nazwanie pliku
  • gulp-qunit – odapla testy qunitowe z wykorzystaniem PhantomJS
  • gulp-html-replace – zezwala na wstawienie odpowiednich zależności w miesjcach zadklarowanych w pliku HTML – na przykład umożliwia podmianę wielu tagów script w jeden tag
  • run-sequence – umożliwia odpalenie tasków seryjnie a nie równolegle
  • del – kasuje katalogi, podkatalogi, pliki itp

W linii poleceń wpisujemy po kolei:

npm install gulp-concat --save-dev
npm install gulp-qunit --save-dev
npm install gulp-uglify --save-dev
npm install gulp-htmlmin --save-dev
npm install gulp-rename --save-dev
npm install gulp-cssmin --save-dev
npm install gulp-html-replace --save-dev
npm install run-sequence--save-dev
npm install del --save-dev

To, co możemy zaobserwować to nazwy paczek od razu mówią nam do czego służą, i robią one dokładnie tylko i wyłącznie to – nic więcej.

Następnie dodajmy do gulpfile.js załadowanie tych tasków:

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var qunit = require('gulp-qunit');
var htmlmin = require('gulp-htmlmin');
var rename = require('gulp-rename');
var cssmin = require('gulp-cssmin');
var htmlreplace = require('gulp-html-replace');
var runSequence = require('run-sequence');
var del = require('del');

Jak widać, tutaj tego się przyspieszyć w żaden sposób nie da, będzie 100 pluginów, 100 razy trzeba będzie coś napisać. Jest to trochę męczące niestety :(

Mając tak przygotowane środowisko, możemy napisać kolejne brakujące taski do naszego projektu, dzięki czemu będziemy mieli następująco wyglądający plik gulpfile.js:

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var qunit = require('gulp-qunit');
var htmlmin = require('gulp-htmlmin');
var rename = require('gulp-rename');
var cssmin = require('gulp-cssmin');
var htmlreplace = require('gulp-html-replace');
var runSequence = require('run-sequence');
var del = require('del');

// configuration
var paths = {
    allScripts: ['gulpfile.js',
        'src/js/*.js',
        'test/**/*.js'
    ],
    // other matters
    vendors: [
        'src/js/lib/jquery/dist/jquery.min.js',
        'src/js/lib/jquery-ui/ui/jquery-ui.js',
        'src/js/lib/knockout/dist/knockout.debug.js',
        'src/js/lib/knockout-sortable/build/knockout-sortable.js'
    ],
    htmls: 'src/index.html',
    css: 'src/css/*.css',
    // other matters
    scripts: [
        'src/js/todos.js',
        'src/js/app.js',
    ]
};

// cleaning everything in build including
// removing build folder
gulp.task('clean', function() {
    return del(['build']);
});

// minify, and rename css file
gulp.task('styles', function() {
    return gulp.src(paths.css)
        .pipe(cssmin())
        .pipe(rename('site.min.css'))
        .pipe(gulp.dest('build/css'));
});

// replace minified scripts/css and minify html
gulp.task('html', function() {
    return gulp.src(paths.htmls)
        // all <!-- build:css|app|lib --> will be replaced
        // with values below
        .pipe(htmlreplace({
            'css': 'css/site.min.css',
            'app': 'js/app.min.js',
            'lib': 'js/lib/vendor.min.js',
        }))
        .pipe(htmlmin({
            collapseWhitespace: true,
            removeComments: true
        }))
        .pipe(gulp.dest('build'));
});

// just copy images
gulp.task('images', function() {
    return gulp.src('src/images/**/*')
        .pipe(gulp.dest('build/images'));
})

// minify vendors
gulp.task('vendors', function() {
    return gulp.src(paths.vendors)
        .pipe(uglify())
        .pipe(concat('vendor.min.js'))
        .pipe(gulp.dest('build/js/lib'));
});

// minify all scripts
gulp.task('scripts', function() {
    return gulp.src(paths.scripts)
        .pipe(uglify())
        .pipe(concat('app.min.js'))
        .pipe(gulp.dest('build/js'));
});

// run tests
gulp.task('test', function() {
    return gulp.src('test/**/*.html')
        .pipe(qunit());
});

// lint all js code
gulp.task('lint', function() {
    return gulp.src(paths.allScripts)
        .pipe(jshint())
        .pipe(jshint.reporter('jshint-stylish'));
});

// watch for changes on js code
gulp.task('watch', function() {
    gulp.watch(paths.allScripts, ['lint']);
});

// execute build with all tasks after clean
// is finished
gulp.task('build', function(callback) {
    runSequence('clean', ['test', 'styles', 'scripts', 'vendors', 'html', 'images'],
        callback
    )
})

gulp.task('default', function() {
    // empty
});

Teraz wystarczy, że command line wykonamy polecenie:

gulp build

i automatycznie zostanie dla nas wykonane:

  • Testowanie kodu – jeżeli wystąpi błąd, proces jest przerywany
  • Kopiowanie niezbędnych plików do katalogu docelowego
  • Łączenie plików w jeden i generowanie ich wersji zminifikowanych
  • Podmiana linków w HTML
  • Minifikacja pliku HTML

Jak dla mnie :) wyczes :)

Podsumowanie

To co ja lubię w gulipe, to jego prostota. Daje on nam kilka metod do których możemy wiele rzeczy podpiąć. To jak skonstruujemy nasza plik i zadania, zależy od nas, czy chcemy być drobiazgowi czy robimy wszystko w jednym. Każdy ma swoje przyzwyczajenia, każdy zespół trochę inaczej pracuje/działa.

Oprócz prostoty, szanuje w gulpie to, że naprawdę ludzie trzymają się zasady – jeden plugin, jedna rzecz (choć są wyjątki). Dzięlki czemu dość prosto można wszystko łączyć w jedną całość. Chcemy sourcemap ? Nie ma problemu, wstrzykujemy to pomiędzy komendę minifikacji itp. Przy bardziej skomplikowanych problemach to bardzo fajnie daje się komponować.

Jednak, przez to, że plugin to jeden task, to tego jest duuużo. Aż za dużo, czasami pierwsze 50 linii kodu to ładowanie pluginów. Ale coś za coś.

A wy jak odbieracie gulpa? Pasuje wam? Wolicie go od grunta? Co wam się w nim bardziej albo mniej podoba od grunta?

A może powinienem jednak jeszcze coś o gulpie napisać? Jak tak to dajcie znać :)

PS.: a wy korzystanie z gulpa? Jakie są wasze odczucia?

7 KOMENTARZE

  1. Czemu nie lubię gulpa. Milion pluginów, chcę usunąć katalogi? Potrzebuję do tego pakiet – gulp-clean, del, gulp-del, rimraf, jeszcze vinyl paths, slack? slack, gulp-slack a potem i tak się nie instaluje na 3 maszynach bo tak…. co pierdoła to npm install….. a jak coś nie zabangla to tak dziwny komunikat że nie wiadomo co się dzieje. Nie dałeś return w tasku do usuwania katalogów? No i wszystko ok, działa ale co drugi raz i teraz szukaj, że brakuje tego cholernego return. Chcesz odpalić gulpa na nowej maszynie (build server?) instaluj noda, gulpa, a niee…. gulp -g brrr……..

    Wolę cake.build bo c#, bo kompiluje się, bo po błędach kompilacji dużo wiem, bo ma wbudowane podstawowe taski jakie się przy buildowaniu przydają. Mogę całość zainstalować z cmd nawet na czystej maszynie

    Czego nie lubę w cake.build? Jeszcze dosyć niszowe i mało popularne to i mało tasków i mało materiałów i za to lubię gulpa wystarczy pogooglać i się wszystko znajdzie i będzie fajnie działało (jak się uda poskładać i nie trafisz pechowo w jakieś stare pakiety)

  2. @arek

    No akurat dla mnie to zaleta ze jest kilka pluginow do jednego :) a nie ze jest tylko jedna wlasciwa droga :) Co do komunikatow to masz na mysli npm ? no to tak :) ale co zrobic.

    Ale z cake wyskoczyles :) jak to sie ma do web dev i pure na przyklad HTML? W sensie widze zastosowanie cake.build w procesie wytarzania oprogramowania i automatyzacji pracy z aplikacja .NET. ale nie widze jak to ma sie do bibliotek typu SASS, lass, coffe-script itp :) ale moze o czyms nie wiem? jak tak to super :) dawaj poczytam i sie dowiem :)

  3. Ja chciałbym wiedzieć, który użyć, bo jak potrzebuje zrobić clean katalogów to googluje który teraz del działa – z autopsji. To samo ze slackiem, ileś pakietów ale nie wszystkie działają. Tutaj ta mnogość się czkawką odbija :( Co do komunikatów, kilka razy mi gulp nie napisał, że jest błąd. Wyglądało, że jest okej a tak na prawdę była kiszka – vide brak return-a.

    Co do cake-a to lubię, że się kompiluje i jakby bardziej komunikatywny jest. Niestety pure web dev nie zazna tam szczęścia. No chyba że przez cmd noda będzie wałkował ale to przecież bez sensu. Osobiście używam cake-a do builda stuffu .net-owego publishowania, pobrania i wrzucenia na sieć. Slack właśnie i inne pierdoły około publishowe. Obawiam, się że cake nie będzie miał sassa lessa coffe czy ts-a bo wcześniej czy później targał by to przez node-a i wtedy cały ten js-shit będzie wyciekał. Szkoda, że nie mogłem Cię pozytywnie zaskoczyć :)

  4. @arek

    Ok to tutaj warto wspomnieć o tym, że gulp to gulp, i bardzo dużo pluginow nie jest gulopowych ale nodeowych. to znaczy na przyklad del jest czystym nodowym pluginem, ktory moze byc wykorzystany w gulpie ze wzgledu na to jak gulp zostal zaprojektowany/napisany.

    Oczywiscie npm ma problemy i liczba paczek jest przytlaczajaca. z drugiej strony, wole to, niz miec cos co wymaga ode mnie duzego nakladu pracy na napisanie – ie. wkurzyl bym sie jakbym musial pisac plugin usuwajacy katalogi :)

    co do cake.build – no własnie takie mialem odczucia, ze to do server side .NET bardziej. tak czy siak :) sprobuje go wykorzystac w kolejnym projekcie. Zamiast psake cake :) przynajmniej glowa mnie bolec nie bedzie :)

  5. A ja na zakończenie cyklu o gulpie składam ogromne podziękowania za sporą dawkę wartościowej wiedzy. Pozdrawiam serdecznie i życzę dalszych, równie istotnych (dla mnie) tutoriali. :)

Comments are closed.