Používame Gulp.js (2) - Inštalácia a prvý task

Publikoval Michal Kočí dňa 6.8.2015 o 11:00 v kategórii Gulp.js

V minulom príspevku sme si pripravili stránku, ktorú chceme vylepšovať a budeme na to používať Gulp.js. Bez väčšieho zdržiavania poďme teda na to.

Inštalácia Gulp.js

Predpokladom na nainštalovanie Gulp.js je, že máte na svojom počítači nainštalovaný Node.js a s ním aj jeho balíckovací manažér npm. Že nemáte? Potom si ho bežte stiahnuť a nainštalovať, inštalácia je jednoduchá a nestojí za to ju tu popisovať.

Dobre, takže už máte. Gulp.js sa musí nainštalovať ako globálne (práve raz) tak aj lokálne (toľko krát, v koľkých rôznych projektoch ho chcete používať). Inštaluje sa cez spomínané npm. Globálne, ak ho ešte nemáte, si ho nainštalujte takto:

npm install -g gulp

Dôležitý je práve parameter -g, ktorý zabezpečí jeho globálnu inštaláciu. Potom si ho chcete nainštalovať aj lokálne, t.j. do vášho projektu respektíve jeho adresára.

Uistite sa, že ste si v lokálnom adresári inicializovali npm, t.j. máte v ňom súbor package.json. Ak nie, vytvorte si ho cez npm:

npm init

Budete musieť zodpovedať niekoľko otázok sprievodcu ale u každej vám ponúkne predvolenú možnosť a tak ak sa vám nad jeho konfiguráciou nechce moc rozmýšlať, len stláčajte Enter kým nezodpoviete všetky - následne budete mať package.json vytvorený.

Dobre, balíčky z npm sa inštalujú jednoducho, ale ešte než si nejaký nainštalujete, musíte sa rozhodnúť, či je určený do produkcie alebo len na vývoj. Viac o tomto píšem v tomto príspevku. Gulp bude v našom prípade použitý iba počas vývoja a tak sa jedná len o vývojový balíček a preto použijeme parameter --save-dev:

npm install --save-dev gulp

Gulp je nainštalovaný, lokálne aj globálne. Vytvorte si súbor gulpfile.js v adresári v ktorom máte aj package.json, t.j. v roote vášho projektu a vložte do neho zatiaľ nasledujúci obsah:

var gulp = require('gulp');
gulp.task('default', function() {
});

A teraz si v príkazovom riadku (alebo terminály) spustite v roote projektu nasledujúci príkaz:

gulp

Ak sa vám zobrazil výstup podobný tomu nasledujúcemu, tak gratulujem, Gulp máte úspešne nainštalovaný:

[17:58:19] Using gulpfile ~/dev/git/weather/gulpfile.js
[17:58:19] Starting 'default'...
[17:58:19] Finished 'default' after 60 μs

Ak sa vám zobrazilo toto, tak ste si nevytvorili súbor gulpfile.js tak ako je popísané vyššie:

[17:58:19] No gulpfile found

Takže už vieme, že gulp sa spúšťa (prekvapivo) príkazom gulp. Za ním môže nasledovať názov úlohy, ktorú chcete spustiť. Ak žiadnu nešpecifikujete, sťandardne sa predpokladá, že chcete spustiť task default, ako v príklade vyššie. Ak budete chcieť spustiť napríklad úlohu pomenovanú lint, spustíte ju takto:

gulp lint

Teraz takú ešte namáte, ale neskôr budete. Podstatné je si pamätať, že ak chcete spustiť inú než defaultnú úlohu, musíte špecifikovať jej názov.

Ako Gulp funguje

Stream

Gulp, nazývaný aj Streaming build system, pracuje so streamami. Predstavte si to ako potrubie, na jednej strane do neho niečo vojde, na druhej to vylezie von. A do tohto potrubia viete zapájať rôzne komponenty či moduly, ktoré to, čo potrubím tečie, nejakým spôsobom menia.

Čo bude na vstupe definujete vy a to použitím metódy gulp.src, ktorá vezme jeden alebo viac súborov a pošle ich ďalej. Ďalej pozapájate (metódou pipe) ďalšie moduly, ktoré budú robiť činnosť, ktorú chcete (napríklad minimalizovať Javascript, spájať súbory a podobne).

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.

No a na konci budete chcieť niečo spraviť s výsledkom. Ak ho budete chcieť zase zapísať na disk, pridáte cez pipe funkciu gulp.dest, ktorá sa postará o zápis súborov na disk. A možno vám bude stačiť výstup do okna terminálu, v tom prípade gulp.dest vôbec nepoužijete, ale použijete len moduly, ktoré budú zapisovať do konzoly.

Tasky či úlohy

Streamy sú super, vezmete nejaké súbory, aplikujete na ne niečo a zapíšete na disk. Ale takýchto úloh môžete mať viacero a vtedy sa hodí mať možnosť ich podeliť do menších blokov - taskov.

Dôvodov na podelenie na rôzne drobné úlohy môžete mať viacero, ale najčastejšie budete mať práve tieto dva:

  • budete chcieť v jednom momente spúšťať iba jednu či len niektoré úlohy
  • budete chcieť spúsťať niektorú úlohu až ked iná skončí (jedna úloha bude závisieč na inej)

Prvá úloha - kontrola Javascriptu

Dnes si naprogramujeme prvú úlohu, ktorá bude síce veľmi jednoduchá ale ukáže nám niektoré základné konštrukcie úloh a navyše bude veľmi praktická.

Môžete so mnou súhlasiť ale nemusíte, ale JavaScript je skvelý programovací jazyk. Veľmi flexibilný. Lenže má niektoré črty, ktoré keď si ich nie ste riadne vedomý alebo ich ignorujete, môžu spôsobiť, že váš kód nebude fungovať správne, čo sa môže prejaviť až za behu (keďže sa kód nekompiluje).

JSLint, JSHint a ESLint

Zrejme aj preto svojho času Douglas Crockford, jedna z najznámejšich Javascriptových osobností naprogramoval JSLint. Je to nástroj, ktorý skontroluje Javascriptový kód a upozorní vás na chyby, ktoré ste spravili. Lenže, tento nástroj je docela prísny a riadi sa pravidlami, ktoré stanovil Douglas a to sa mu dlho vyčítalo až vznikol fork - JSHint.

JSHint je menej prísny a konfigurovateľný, takže viacero ľudí ho logicky preferuje. No a keďže Javascript sa našťastie v čase vyvýja, najmä teda v poslednej dobe, tak vznikol ďalší skvelý nástroj ESLint. Ten je tiež veľmi flexibilný a konfigurovateľný a obsahuje pravidlá aj pre najnovšie verzie Javascriptu (v tomto momente to je ES6 tiež známy ako ES2015).

No a my dnes použijeme práve ESLint, i keď vy sa môžete rozhodnúť pre ktorýkoľvek. Pre všetky tri existuje gulp zapúzdrenie a tak vám nič nebráni použiť ktorýkoľvek.

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.

V prvom rade si ho nainštalujeme, respektíve jeho gulp zapúzdrenie (plugin) - gulp-eslint:

npm install --save-dev gulp-eslint

Aby sme ho mohli v našom gulpfile.js skripte použiť, tak si ho naimportujeme (cez require), najlepšie na začiatku súboru:

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

Prvý task

Tak a poďme na samotný task, toto je on, v celej svojej kráse:

gulp.task('lint', function() {
  return gulp.src('./js/*.js')
    .pipe(eslint())
    .pipe(eslint.format());
});

Definujeme teda task s názvom lint. Ten si cez gulp.src načíta všetky súbory s príponou js z adresára js. Do pipeline zapojíme dve veci - prvá je samotný eslint, ktorý skontroluje jednotlivé súbory. Druhá zase výsledky z predchádzajúceho kroku vypíše do konzoly.

Všimnite si tri veci. Celé spracovanie naozaj funguje ako potrubie. Na začiatku do neho strčíme javascriptové súbory, ďalej zapojíme funkciu ktorá skontroluje súbory a výstup (zase zapojený) si necháme vypísať do konzoly.

Druhá vec, ktorá stojí za všimnutie je, že výstup nejde na disk. Nie preto, že by to nešlo, ono to ide a na disk budeme ešte zapisovať v iných taskoch, nebojte. Ale preto, že to nepotrebujeme, aspoň nie v tomto scenári. Je pre nás dostatočné vidieť výpis v konzole.

Tretia vec pýtajúca si vašu pozornosť je, že funkcia predaná do funkcie task, teda samotný obsah tasku vracia ten stream, ktorý sme vytvorili (a zapojili do neho moduly). Nie je to povinné, ale iba za predpokladu, že na tomto tasku nebude závisieť iný. To, že vrátite z funkcie samotný stream umožní Gulpu zistiť, kedy spracovanie skončilo. A ak na tomto taskú závisí iný, ten sa spustí až po skončení tohto. Inak by sa spustili naraz.

Dobre, koniec rečí, spustite si task lint. Dobre, vidíte ten výsledok? Vyše 2000 chýb? Ufff. Ak sa však lepšie pozriete na výsledok spracovania, všimnete si, že drvivá väčšina týchto chýb nepochádza z vašich skriptov, ale z jQuery a z material designu. A neviem ako vy, ale tie ja opravovať nechcem. Takže ich potrebujeme vylúčiť zo spravovania.

Vstupné súbory - gulp.src

Do gulp.src ako prvý parameter posielame reťazec ./js/*.js, ktorý vezme všetky skripty z daného adresára. Chceme však lepšie špecifikovať, ktoré súbory majú byť spracované. Máme tak dve možnosti:

  • špecifikovať iba súbory, ktoré chceme spracovať, napríklad ak chceme iba weather.js, použijeme reťazec ./js/weather.js
  • špecifikovať okrem súborov, ktoré sa majú spracovať, aj tie, ktoré chceme z tejto množiny vynechať

Funkcii gulp.src môžete predať ako prvý parameter reťazec, alebo pole reťazcov. Takže oba vyššie scenáre sú možné. Ak chcete povedať, ktoré súbory nemajú byť spracované, reťazec začnete výkričníkom. Prepíšme teda task tak, aby vynechal dva spomínané súbory:

gulp.task('lint', function() {
  return gulp.src(['./js/*.js', '!./js/jquery*', '!./js/material*'])
    .pipe(eslint())
    .pipe(eslint.format());
});

Vidíte? Vravíme, zober všetky js súbory z daného adresára, ale vynechaj tie, ktorých názov začína na jquery alebo na material. Šikovné, že?

Spustite task znova a pozrime sa, či je výsledok uspokojivejší. Je, našťastie. A našťastie chýb nie je veľa a docela sa opakujú, tak sa na ne pozrime:

  1. Strings must use doublequote
  2. Missing "use strict" statement
  3. "$" is not defined
  4. "console" is not defined
  5. Expected '!==' and instead saw '!='
  6. Unnecessary semicolon

Prvá chyba pramení z toho, že osobne preferujem jednoduché úvodzovky pred dvojitými. Toto sa dá prekonfigurovať. Druhá je chyba, zabudli sme zapnúť strict mód, opravíme. Tretia a štvrtá chyba pramenia z toho, že používame globálne premenné. Vyriešime. Piata je nebezpečná, nakoľko porovnávame bez kontroly typov. Šiesta nás varuje, že máme navyše bodkočiarku za deklaráciou funkcie, opravíme jej odstránením.

Konfigurácia ESLint

ESLint sa dá konfigurovať a to vicerými spôsobmi, my použijeme ten, že pri zapojení eslint do streamu mu predáme konfiguračný objekt. Možností na konfiguráciu je veľa a treba si ich dohľadať v dokumentácii.

Upravme teda task tak, že nakonfigurujeme prostredie v ktorom skript beží (zapneme prehliadač a jQuery) a nastavíme, že chceme používať jednoduché zátvorky (single) ale že ak sa vyskytnú iné, považujeme to za chybu (hodnota 2):

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());
});

Samotný skript sme zatiaľ nemenili, ale znížíli a zmenili sme množinu chýb. Teraz máme tieto:

  1. Missing "use strict" statement
  2. Unexpected console statement
  3. Expected '!==' and instead saw '!='
  4. Unnecessary semicolon

Dobre, druhá chyba je nová, varuje nás že zapisujeme do konzoly. To je fér, v produkcii do nej zapisovať nechceme a tak v skripte riadky kde do konzoly zapisujeme odstráníme. Čo nám prinesie novú chybu:

  1. textStatus is defined but never used

Áno, funkcia, ktorá funguje ako callback pre chybu ajaxového volania (error callback), berie dva parametre (jqXHR a textStatus) a ani jeden z nich teraz nepoužívame, predtým sme druhý zapisovali do konzoly. Teraz ich môžeme, dokonca chceme, odstrániť a tým sa zbavíme tejto chyby.

Tretiu chybu odstráníme nahradením != za !== čím dosiahneme bezpečnejšieho kódu (bezpečnejšieho porovnania). Ak by sme nechali netypové porovnanie (!=), toto nebude kontrolovať typ premenných na ľavej a pravej strane porovnania a môže sa nám to nepekne vypomstiť.

Ako odstrániť prvú chybu? Jednoducho, treba pridať reťazec use strict na začiatok skriptu, to nám zabezpečí prepnutie do striktného módu. Keď ho tam pridáme, tak si však moc nepomôžeme, nakoľko dostaneme chybu:

  1. Use the function form of "use strict"

Máme teda použiť use strict vo funkcii. Tu sa dostávame k ďalšiemu problému nášho skriptu. Funkcie a premenné, ktoré máme v skripte definované sú na globálnom scope. Nepekné. Ale riešenie je veľmi jednoduché - použijeme IIFE.

IIFE - Okamžite zavolaná fukncia

OK, čo je IIFE? Je to skratka pre Immediately-Invoked Function Expression. Krásny názov, ale nie je to nič iné ako deklarácia funkcie a jej okažité zavolanie. V najjednoduchšej podobe vyzerá nasledovne:

(function() {
})(); 

Do takejto metódy presunieme celý obsah skriptu a hneď na začiatok uvedieme use strict:

(function() {
  'use strict';
  // zbytok skriptu
})(); 

Dobre, ale ako to funguje a na čo je to dobré? Na prvom riadku definujeme funkciu, ale celá je zabalená do zátvoriek - to zabezpečí, že sa z toho stane expression. A hneď nasledovaná prázdnymi zátvorkami, ktoré tento výraz (expression), teda funkciu, spustia.

Na prvý pohľad to môže vyzerať nezmyselne, prečo by sme náš kód chceli obaliť do fukcie a tú hneď zavolať? Odpoveď je jednoduchá - scope. Určite viete, že každá premenná či fukcia patrí do scopu svojej fukcie, v ktorej sa nachádza (a nie bloku, ako sa niekedy ľudia mýlia). Ak teda kód zapúzdrime do IIFE, tak všetky premenné a fukcie v ňom definované budú spustené a zvnútra fukcie prístupné, ale nebudú na globálnom scope.

IIFE? Scope? Posvietime si na ne spolu na mojich školeniach JavaScriptu (Node.js, jQuery, AngularJS, ...). Lebo napísať super aplikáciu neznamená len poznať ten ktorý framework, ale aj vedieť napísať kvalitný kód.

Takže ak sme pred touto zmenou mali v našom skripte funkciu refresh, táto bola na globálnom scope, v prehliadači teda na objekte window. Mohli ste ju v prehliadači zavolať z vývojárskej konzoly priamo, ale aj cez window objekt:

refresh();
window.refresh();

A to je veľmi zlé, ľahko by mohlo dojsť ku kolízii názvov (čo ak niektorý z nalinkovaných skriptov tiež má metódu refresh na globálnom scope?), či k zbytočnému zapraseniu globálneho objektu (čo môže mať dopad aj na výkonnosť skriptu). Po zabalení do IIFE už toto volanie zvonku nejde, funkcie sú prístupné len v rámci skriptu, čo nám úplne stačí.

Prvý task dokončený

Ak si spustíte lint po všetkých našich úpravách, nebude už vypisovať žiadnu chybu a náš kód je hneď kvalitnejší a bezpečnejší. Takže ak môžete, vždy si váš kód kontrolujte a ideálne od začiatku projektu, aby ste neskôr nezistili, že v ňom máte desiatky či stovky chýb.

Výstup kontroly by mal u vás teraz vyzerať nasledovne:

[17:58:19] Using gulpfile ~/dev/git/weather/02/gulpfile.js
[17:58:19] Starting 'lint'...
[17:58:19] Finished 'lint' after 148 ms

Výsledok nášho snaženia si viete pozrieť v adresári 02 na mojom Githube.

Nabudúce sa posunieme ďalej a budeme spolu pripravovať ďalšie zaujímavé úlohy, ktoré sa vám bežne zídu pri vývoji vašich webov. Takže, 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.