Používame Gulp.js (4) - Spúšťanie úloh v stanovenom poradí

Publikoval Michal Kočí dňa 12.8.2015 o 11:00 v kategóriach Gulp.js a Optimalizácia webu

V minulom príspevku sme si ukázali, ako optimalizovať náš web a spravili sme si dva tasky, ktoré dnes rozšírime o tretí a zabezpečíme, aby sa spúšťali v správnom poradí. Poďme na to.

Premazanie distribučného adresára

Ak ste išli s týmto návodom krok za krokom, všimli ste si určite jednu vec. V jednom momente sme do css a js adresárov dali generovať len spojené súbor (weather.css a weather.js) a neskôr sme úlohu upravili a do adresára generujeme spojené, minifikované a verzionované súbory. Avšak v adresároch nám ostali aj neverzionované súbory, ktoré tam nechceme.

Tiež, ak neskôr zmodifikujete niektoré štýly alebo skripty, vznikne vám nový verzionovaný súbor a starý vám v adresári ostane. Záleží na vás, či si ich chcete nechať, ale v našom prípade to nie je potrebné, nakoľko s touto zmenou sa vždy mení aj index.html.

Takže ich poďme zmazať. Ako? Automatizovane, použijeme modul del. Nainštalujte si ho:

npm install --save-dev del

A načítajte si ho:

var del = require('del');

A pripravte si ďalší task:

gulp.task('clean', function() {
  del('./dist/*');
});

Jeho spustením premažete adresár dist presne tak, ako sa špecifikuje prvým parametrom.

Spúšťanie závislých úloh

OK, máme tri tasky - clean, fonts a production. Aby sme pripravili všetko na distribúciu, mali by sme najskôr spustiť premazanie (task clean) a keď skončí, môžeme spustiť tasky fonts a production.

Prvý problém je, že Gulp v tomto momente nevie zistiť, kedy task clean skončí a tak nevieme správne nastaviť závislosti. Dôvodom je, ak si spomeniete na minulé články, že task clean nevracia stream, čo je informácia, ktorú vie Gulp použiť na zistenie dokončenia úlohy.

Chceli by ste sa Gulp naučiť rýchlo, ľahko a v praxi? Je súčasťou mojho Node.js školenia - kurzu na ktorom sa naučíte programovať serverové aplikácie v JavaScripte.

Drobný problém je, že del nie je gulpový modul a preto žiaden stream nevracia. Nič však nie je stratené, Gulp vie využiť ešte dva spôsoby, ako zistiť dokončenie úlohy - promise a callback. No a dobrá správa je, že modul del môže prijímať callback a po dokončení ho aj zavolá.

Potrebujeme teda upraviť task clean. Funkciu, ktorú používame ako callback tasku, teda samotné telo úlohy rozšírime - bude prijímať jeden parameter a tento si pomenujeme cb:

gulp.task('clean', function(cb) {
  del('./dist/*');
});

Tento parameter dostane funckia priamo od Gulpu. A je na nás, aby sme ho zavolali v momente, kedy naša úloha skončila a to buď bez parametra v prípade úspechu - cb() alebo s jedným parametrom, ktorý obsahuje informácie o chybe - cb(err).

A presne takto funguje aj modul del, prvý parameter je informácia o tom, čo sa má mazať (to už používame) a druhý môže byť callback, ktorý del zavolá v momente keď skončí alebo nastane chyba. Takže prijatý callback potrebujeme predať do volania del:

gulp.task('clean', function(cb) {
  del('./dist/*', cb);
});

Tým sme zabezpečili, že Gulp bude vedieť, kedy úloha clean skončí. Teraz ešte potrebujeme nastaviť závislosti.

Štandardné Gulp závislosti

Ak vás napadlo, že stačí task production nastaviť, že závisí na taskoch clean a fonts, tak žiaľ tadiaľto cesta nevedie.

Dôvodom je, že všetky závislosti jednej úlohy sú spustené naraz. To by znamenalo, že sa spustí mazanie adresára dist a zároveň kopírovanie fontov čo nebude dobre fungovať, lebo úloha fonts sa bude snažiť kopírovať fonty a v tom istom momente jej bude úloha clean mazať súbory v danom adresári.

Jedna z možností je dať úlohe production závislosť na úlohe fonts a úlohe fonts na úlohe clean. Funkčné, ale nie úplne pekné. Vyzeralo by to nasledovne:

gulp.task('clean', function(cb) {
  del('./dist/*', cb);
});

gulp.task('fonts', ['clean'], function() {
  return gulp.src('./font/*')
    .pipe(gulp.dest('dist/font'));
});

gulp.task('production', ['fonts'], function() {
  var assetsStream = useref.assets();

  return gulp.src('index.html')
    .pipe(assetsStream)
    .pipe(gulpif('*.js', uglify()))
    .pipe(gulpif('*.css', minify()))
    .pipe(rev()) 
    .pipe(assetsStream.restore())
    .pipe(useref())
    .pipe(revReplace())
    .pipe(gulp.dest('dist'));
});

Aby sme chápali reťaz závislostí, musíme pozrieť všetky tasky a v prípade troch je to ešte celkom ľahké, ale ak ich budeme mať viac, ľahko sa to stane celé neprehľadné.

Sofistikovanejšie spúšťanie závislých úloh

Na krajšie riešenie použijeme modul run-sequence. Nainštalujeme si ho:

npm install --save-dev run-sequence

A naimportujeme:

var runSequence = require('run-sequence');

A viete čo? Poďme si spraviť ešte malý refactoring. Keďže náš task production ani zďaleka nerobí všetko čo má, ale iba optimalizuje veci, premenujeme ho na optimize:

gulp.task('optimize', function() {
  var assetsStream = useref.assets();

  return gulp.src('index.html')
    .pipe(assetsStream)
    .pipe(gulpif('*.js', uglify()))
    .pipe(gulpif('*.css', minify()))
    .pipe(rev()) 
    .pipe(assetsStream.restore())
    .pipe(useref())
    .pipe(revReplace())
    .pipe(gulp.dest('dist'));
});

A vytvoríme si nový task s názvom production, ktorý zavolá všetky tri úlohy potrebné na kompletnú prípravu produkčnej verzie:

gulp.task('production', function(cb) {
  runSequence('clean', 'fonts', 'optimize', cb);
});

Tu vidíte, ako sa run-sequence používa. Prijíma reťazce - názvy taskov, ktoré sa majú spustiť a to v správnom poradí. A postará sa o to, že ďalší sa spustí až v momente, kedy ten predchádzajúci skončil.

Posledný parameter môže byť callback a keďže chceme mať task pripravený na prípadné zapojenie do ďalšej sekvencie, príjmeme ho a predáme ho ďalej. Teraz keď na tomto tasku žiaden ďalší nezávisí to nie je nutné, ale ak si na túto prax zvyknete, tak vám to do budúcnosti ušetrí prácu a prípadne aj nervy v momente, kedy by ste ho dali do sekvencie a neskôr sa čudovali, prečo to nefunguje ako si predstavujete.

Výstup zo spravovania vyzerá nasledovne, vidíme, že spúšťa jednu úlohu za druhou:

[11:38:22] Using gulpfile gulpfile.js
[11:38:22] Starting 'production'...
[11:38:22] Starting 'clean'...
[11:38:22] Finished 'clean' after 17 ms
[11:38:22] Starting 'fonts'...
[11:38:22] Finished 'fonts' after 30 ms
[11:38:22] Starting 'optimize'...
[11:38:27] Finished 'optimize' after 5.1 s
[11:38:27] Finished 'production' after 5.15 s

Paralelné aj sériové spúšťanie úloh

Dá sa to celé ešte zlepšiť? Dá. Ak sa zamyslíme nad celým spracovaním, dospejeme k tomu, že po premazaní (task clean) je bezpečné, aby tasky fonts a optimize boli spustené naraz (paralelne) - oba zapisujú síce do toho istého adresára (dist) ale každý spracováva iné súbory.

Našťastie run-sequence dokáže aj toto. Stačí ak niektorý parameter zmeníme z reťazca na pole reťazcov - v takom prípade tasky z tohto poľa spustí naraz (paralelne) a celú sekvenciu sériovo. A to chceme. Prepíšeme teda task production nasledovne:

gulp.task('production', function(cb) {
  runSequence('clean', ['fonts', 'optimize'], cb);
});

Ako vyzerá výstup? Tak ako čakáme:

[11:41:05] Using gulpfile gulpfile.js
[11:41:05] Starting 'production'...
[11:41:05] Starting 'clean'...
[11:41:05] Finished 'clean' after 16 ms
[11:41:05] Starting 'fonts'...
[11:41:06] Starting 'optimize'...
[11:41:11] Finished 'optimize' after 5.02 s
[11:41:11] Finished 'fonts' after 5.03 s
[11:41:11] Finished 'production' after 5.06 s

Úlohy fonts a optimize sú správne spustené naraz, ale až v momente, kedy úloha clean skončila. Super, nie? Zrýchlili sme tak spracovanie, síce len o pár milisekúnd, ale každé zrýchlenie sa ráta.

Podmienené ukončenie spracovania

Prípravu produkčnej verzie máme, ale celý proces sa dá ešte vylepšiť. Spomínate si, že sme si svojho času pripravili úlohu lint, ktorá kontroluje javascriptový kód? A ak sú v ňom chyby, naozaj chcete pripraviť produkčnú verziu? Aj s tými chybami?

Chceli by ste písať kvalitný JavaScriptový kód? Dojdite na jedno z mojich školení JavaScriptu (Node.js, jQuery, AngularJS, ...), na nich sa okrem iného naučíte, ako písať lepší JavaScriptový kód.

Ak nie, poďme si to celé dokončiť. Máme medzi úlohami aj defaultnú (s názvom default), ktorá zatiaľ nič nerobí. Spravne v nej to, že spustíme najskôr lint a potom production a zase využijeme run-sequence:

gulp.task('default', function(cb) {
  runSequence('lint', 'production', cb);
});

Spustite si ju, všetko by malo bežať ako doteraz s tým, že sa najskôr spustí lint a potom production. A teraz si schválne zaneste do originálneho súboru weather.js nejakú chybu, napríklad odstráňte riadok s use strict. A spustie si úlohu default znova, výsledok bude vyzerať takto:

[11:55:14] Using gulpfile gulpfile.js
[11:55:14] Starting 'default'...
[11:55:14] Starting 'lint'...
[11:55:14] 
weather.js
1:1  error  Missing "use strict" statement  strict
3:1  error  Trailing spaces not allowed     no-trailing-spaces

✖ 2 problems (2 errors, 0 warnings)

[11:55:14] Finished 'lint' after 156 ms
[11:55:14] Starting 'production'...
[11:55:14] Starting 'clean'...
[11:55:14] Finished 'clean' after 15 ms
[11:55:14] Starting 'fonts'...
[11:55:14] Starting 'optimize'...
[11:55:19] Finished 'fonts' after 4.98 s
[11:55:19] Finished 'optimize' after 4.98 s
[11:55:19] Finished 'production' after 5 s
[11:55:19] Finished 'default' after 5.16 s

Ale to nie je to, čo sme chceli, však? Vidíme, že kontrola zbehla, našla chyby, ale spracovanie bežalo ďalej. Úloha lint neskončila chybou. Našťastie je tu jednoduchá pomoc, necháme ju skončiť chybou v prípade problémov s kódom.

ESLint obsahuje funkciu, ktorú keď zapojíme do pipeline a keď nastane chyba, skončí úloha chybou. Upúravte si teda úlohu lint nasledovne:

gulp.task('lint', function() {
  return gulp.src(['./js/*.js', '!./js/jquery*', '!./js/material*'])
    .pipe(eslint({
      env: {
        browser: true,
        jquery: true
      },
      rules: {
        quotes: [2, 'single']
      }
    }))
    .pipe(eslint.format())
    .pipe(eslint.failOnError());
});

A spustite defaultnú úlohu teraz. Výsledok bude hneď iný, spracovanie sa pri výskyte chyby zastaví:

[11:58:47] Using gulpfile gulpfile.js
[11:58:47] Starting 'default'...
[11:58:47] Starting 'lint'...
[11:58:47] 'lint' errored after 135 ms
[11:58:47] ESLintError in plugin 'gulp-eslint'
Message:
  Missing "use strict" statement.
Details:
  fileName: weather.js
  lineNumber: 1
[11:58:47] 'default' errored after 138 ms
[11:58:47] ESLintError in plugin 'gulp-eslint'
Message:
  Missing "use strict" statement.
Details:
  fileName: weather.js
  lineNumber: 1
[11:58:47] 
weather.js
1:1  error  Missing "use strict" statement  strict
3:1  error  Trailing spaces not allowed     no-trailing-spaces

✖ 2 problems (2 errors, 0 warnings)

Funguje presne ako chceme, paráda. Zanesenú chybu si nezabudnite odstrániť. A môžete sa spoľahnúť, že pri zmene skriptu v budúcnosti, ak v ňom spravíte nejakú chybu, sa vám produkčná verzia nevygeneruje, čo je dobre.

Nabudúce

Tak sa nám to pekne vyskladalo, čo poviete? Aktuálnu verziu nájdete v adresári 04 na mojom Githube.

A nabudúce? Pozrieme sa na ďalšie užitočné úlohy - optimalizáciu obrázkov a spracovanie LESS súborov. Takže ako vždy, ostaňte naladení...

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.