npm, alebo cesta tam a naspäť

Pôvodne mal vzniknúť článok s názvom Dr.pipe, alebo Ako som sa naučil nerobiť si starosti a mať rád Gulp

2015-12-02
Node

Prečo som teda zmenil vtipný odkaz na úplne odlišnú knihu/film.

Začneme prechodom z Gruntu na Gulp, pričom si vymenujeme základné vlastnosti jednotlivých node.js task runnerov.

Automatizácia procesov je kľúčovým slovom, ktoré umožnilo zaplniť cesty šťastne priaducimi Fordmi Model T, či socialistickým družstvám zvýšiť produkciu mlieka o 175% pre rovnako šťastných pionierov. JavaScriptový task runner bežiaci na node.js zvláda toto všetko a ešte omnoho viac.

Vytvárať a čistiť adresáre, search and replace vo files či definovať kroky zahrnuté v build a post-build-procesoch. Všetko v jednom súbore ktorý je priložený k projektu a môže byť share-ovaný medzi developermi. Všetko napísané v JavaScripte.

Grunt

Pamätníci urbárskych čias Márie Terézie si isto spomenú ako sa aj u nás používali slovné obraty “gruntovať”, “na vlastnom grunte”, či stále populárne “začínať od gruntu”. To je Grunt v skratke.

Všetky príkazy sú zadefinové v lokálnom Gruntfile.js súbore.

grunt.loadNpmTasks() načíta nainštalované npm moduly, grunt.registerTask() zaregistruje príkaz pre spúšťanie taskov, ktoré prebehnú v poradí jeden za druhým.

Aktívnym centrom Gruntfilu je grunt.initConfig({}), ktorý len čaká na dodanie konfiguračných objektov ktoré povedia modulom kde a ako majú vykonať svoje úlohy. Objekty poväčšinou obsahujú atribúty ako source files, destination files či options a bývajú podrobne popísané v príslušnom npm repozitári modulu.

Vďaka zrozumiteľnosti konfigurácie, dodávaniu preddefinovanych attr: value párov a lineárnemu poradiu prebiehania taskov býva Grunt oprávnene first pick nástojom na ceste za automatizáciou development workflowu.

A vie ním zostať až do doby kým sa Gruntfile.js nezačne nekontrolovateľne rozrastať, až sa nestane nekontrolovateľným Behemothom zloženým z objektov v objektoch nestovaných hlboko ako Rímske katakomby.

Pretože jednotlivé tasky vďaka separácii úloh a lineárnemu poradiu spúšťania nepotrebujú medzi sebou komunikovať, pre každý task je potrebné opakovane nastavovať napr. source a destination files, čo pridáva zbytočné riadky kódu a zhoršuje celkový maintenance.

Pre malé a jednoduché projekty ktoré nepotrebujú ďalej rásť a pridávať funkcionalitu do build procesu je Grunt viac nez postačujúci nástroj. V zložitejších prípadoch kedy na výslednom súbore potrebujú prebehnúť viaceré na seba nadväzujúce operácie je potrebné si vizuálne predstaviť, v ktorej fáze procesu a v akom stave vzhľadom k celému procesu sa práve spracovávaný súbor bude nachádzať.

Úspešne sa mi podarilo ponachádzať, povyberať a odtestovať gulp verzie modulov ktoré nahradili grunt tasky pri relatívnom zachovaní workflowu. Avšak počas vyhľadávania v npm repozitári som začal uvažovať nad novým nadpisom.

Potrebujem vlastne build systém so svojim vlastným ekosystémom modulov ktoré sú len abstrakciou už existujúcich node modulov?

Ak v budúcnosti nadobudne výraznú trakciu nový task runner, budem musieť znova prepisovať konfiguráciu a hľadať kto napísal najlepší modul pre tie isté basic tasky?

A musel Evangelion: 3.0 zahodiť sľubný reboot pôvodnej série? (Veru Hideaki Anno, niekedy you can NOT redo…)

Pre málo odpovedí, ale zato ešte viac otázok čítajte ďalej.

Gulp

Medzičasom som sa začal zaujímať čo je to vlastne ten Gulp s ktorým som sa pravidelne stretával v článkoch venovaných developmentu. Čas naskočiť na populárny bandwagon.

Go with the flow, alebo všetko je zrazu v streame.

Kým Grunt sa sústredí na konfigurovanie taskov pomocou dodávania objektov so všetkými potrebnými values k vykonaniu úlohy, v Gulpe programujeme logiku vykonávania v streame vďaka večne mladému UNIXovému konceptu tzv. ‘pipelines’.

Task ktorý v Grunte vykonáva zmeny na súbore, musí zmenený súbor zapísať na disk aby ho mohol otvoriť a pracovať s ním task nasledujúci v poradí.

Gulp drží dáta v Node.js streame a pracuje na nich “on the fly”. Dokáže tak na disk zapísať až výsledok spracovania všetkých taskov.

Significance?

Paralelnosť priebehu na sebe nezávislých taskov, rýchlosť a prehľadnosť kódu.

Gulp môže na prvý pohľad vyzerať zložitejšie na konfiguráciu, na druhý pohľad je to stále pravda. Samozrejme aj tu existujú preddefinované konfiguračné objekty ktoré sa dajú dodať tasku a ovplyvňujú tak jeho priebeh.

Pri definovaní úlohy sa však treba zamyslieť nad logikou streamu, kde a v akom stave začíname a kam nás to povedie. Večná otázka vesmírov, ale nič s čím sa nedá po troche času a investovania trpezlivosti oboznámiť.

Problémy sa môžu nastať paradoxne kvôli prítomnosti jednej z hlavných devíz Gulpu - paralelnosti priebehu taskov.

Každému tasku sa dá samozrejme nadefinovať tzv. “dependency task”, od ktorého úspešného dokončenia je závislé spustenie tasku. Pri jednoduchom flowe clean dist folder => build => postprocess => deploy/serve je to trvalo udržateľná metóda.

Osobne (a podľa Stack Oveflowu som nebol jediný) som sa niekoľkokrát ocitol v situácii kedy jednotlivé tasky museli nadväzovať v poradí na iné ktoré mali vlastné dependency tasky (dependency hell). Niekedy sa dependency tasky mohli meniť podľa situácie, env prostredia či argumentov z CLI (k čomu sa dostanem v ďalšom článku).

Najschodnejším riešením pre mňa bolo v gulpfile registrovať iba krátke sekvencie úloh a kombinovať ich z cli cez && operátor do seba nadväzujúcich príkazov.

Ďalej som sa premýšľal či skutočne potrebujem niektoré gulp moduly na mkdir, copy a clen directory tasky, keď mám celý čas pre prácu s filesystemom prístup k shellu a node.js. Určité gulp moduly sa stali vlastne len abstrakciou - mediátorom s lower level prostredím a okrem pohodnejšieho syntaxu integrovateľného do gulpfile neprinášali nič navyše.

Tak prišlo precitnutie, kruh sa uzavrel a stál som na začiatku.

npm

package.json. Tento JSON súbor spočívajúci v roote projektu sa obvykle vytvára príkazom $ npm init a zbesilým stláčaním Enteru. Obsahuje však jeden často prehliadaný attribute - “scripts”.

V tejto chvíli som už navyknutý naskakovať na nové bandwagony ako Sean Connery vo Veľkej vlakovej lúpeži, dal som npm scriptom teda šancu.

Node package manger netreba vnímať len ako nástroj na inštaláciu a spravovanie node modulov, ale aj na ich spúšťanie a konfiguráciu bez toho aby potrebovali byt nainštalované globálne (s -g parametrom). Čím si nezapratávame CLI zbytočnými príkazmi ktoré populujú shell auto-completion tab.

Výhody? Zbavenie sa “dependencies within dependecies” cyklu a potreby vyhľadávať package určené špeciálne pre niektoré z build system prostredí. Vieme zapísať aliasy príkazov, ktoré by sme manualne spúšťali z CLI s príslušnými parametrami. Pipe a redirection sú navyše UNIXové builtiny a máme k dispozícii operátory na paralelné spúšťanie taskov.

Tradeoffom je trochu sparťanské prostredie - package.json je predsa len simple JSON attribute:value pair formát. Poskytuje však pomocný objekt config, ktorého attributes vieme referencovať v scripts príkazoch.

Example

jednoduchý npm watch & livereload bez browser pluginu

1
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"config": {
"port": 8080
},
scripts": {
"serve": "live-server --port=$npm_package_config_port --host=localhost --no-browser --wait=1000 ./dist"
"build-css": "sass ./src/scss/style.scss ./dist/css/style.css",
"build-js": "babel ./src/js/main.js -o ./dist/js/main.js --source-maps",
"build": "npm run build-css & npm run build-js"
"watch-css": "nodemon --ext scss --watch ./src/scss --exec 'npm run build-css'",
"watch-js": "nodemon --ext js --watch ./src/js --exec 'npm run build-js'",
"watch": "npm run watch-css & npm run watch-js & npm run serve",
"start": "npm run build && npm run watch"
}

Task s názvom “start” sa dá spustiť príkazom npm start.

Jednotlivé tasky sa dajú spúšťať samostatne cez npm start {task-name}.

Prehľad všetkých registrovaných scriptov spolu s príslušnými príkazmi sa dajú printnúť spustením npm run bez dodania argumentov.


Komentáre: