JavaScriptové MVC frameworky

Subjektivní pohled Ondřeje Žáry na současný stav MVC frameworků v JavaScriptu. Jsou opravdu tou správnou cestou?

0 Varování

Tento text odráží osobní názory a je pravděpodobné, že s ním nebudete souhlasit. Když ale dočtete až ke třetí kapitole, budete moci osobní názory objektivně konfrontovat přímo s konkrétní implementací.

1 Historický exkurz

MVC jako paradigma psaní webových aplikací existuje od nepaměti. Studentům každý rok do hlavy vtloukáme, že dokud bude výsledkem jejich práce spaghetti code, nelze mluvit o udržovatelnosti, čitelnosti a podobně.

Na MVC přitom nic objevného není – je to jen konkrétní aplikace SRP, SoC a podobně v prostředí interaktivního uživatelského rozhraní, syceného daty z nějakého (DB) úložiště. Právě proto se při tradičním psaní webů žádný MVC framework nepoužívá (a přesto to MVC je): prostě se výsledné HTML renderuje šablonou (view), data se papají z nějak abstrahované databáze (model) a zbytek – což může být párřádkový skript i všechoschopný moloch – se stará o logiku spolupráce těchto dvou (controller).

Veškeré serverové logice se samosebou občas říka framework (Nette, Zend, …); to je ale proto, že tím autor dává najevo, že jeho kód se umí postarat o všechny aspekty zpracovávání požadavků (jinak by to byl toolkit, knihovna, nebo něco obdobného). A zrovna náhodou při implementaci použil MVC, proč ne.

Během boomu JavaScriptových aplikací (čti: webových stránek, ve kterých logiku zpracovávání interakce a zobrazování dat realizuje klientský JavaScript) však kdosi přichází s myšlenkou, že “serverové MVC” by z jakéhosi důvodu mělo mít svůj klientský komplement – když to dobře fungovalo v místě A, jistě to bude skvěle fungovat i v místě B. (“Polsko jsme zabrali bez problémů. Co takhle to zkusit ještě s Ruskem?”)

Jaké argumenty v takových chvílích slýcháme?

  1. “Vyrábět větší kusy HTML JavaScriptem je opruz. Proto potřebujeme JS šablony.”
  2. “Můj web už obsahuje více než N řádků JS, z čehož je patrné, že se JavaScript stal hlavní komponentou stránky. Je na čase použít nějaký nástroj, který mi usnadní jeho psaní a zařídí, abych toho kódu nemusel psát tolik.”
  3. “Programátoři na backendu používají MVC. Jaká ostuda, že já se svým JavaScriptem také nemám něco tak prďáckého!”

Za těchto okolností proto světlo světa spatřují JavaScriptové šablonovací systémy: klientské skripty, které na vstupu dostávají strukturovaná data a pseudo-HTML šablonu; provedou substituci a vrátí výsledný fragment HTML dokumentu. Nikdo pořádně neví, jak to dělat – kolik logiky v šablonách nabízet (mustache: logic-less templates), jestli duplikovat serverovou syntaxi (ejs, underscore), jak napasovat nutnost šablonovacích direktiv na fragmenty HTML (plates, PURE). Ve finále je rozhodně z čeho vybírat.

javascript template

2 Šablony a jejich zásadní nedostatek

Použití šablonovacího systému na serverové straně bylo posledním krokem před dokončením zpracování požadavku: typický controller měl na svém posledním řádku něco jako template.render(data). Ve světě JavaScriptových šablon ale nekončíme vykreslením; jedná se o interaktivní aplikaci, a proto je nutné na vzniklou DOM strukturu ještě navěsit relevantní posluchače. Tím se dostáváme k zásadnímu problému všech šablonovacích systémů, jejichž výstupem je řetězec: není možné přímočaře pracovat s posluchači. Z výsledku je prve nutno postavit strom (innerHTML), ten procházet (querySelector) a přidávat události (addEventListener). A právě procházení je pracné a vyžaduje jasně a jednoznačně definované API mezi stránkou a JS (například pomocí atributů ID či class). Tato skutečnost je hlavním koncepčním nedostatkem samotných JavaScriptových šablon.

V tuto chvíli je možná na místě připomenout, že zmiňovaný neduh vůbec nepřipadá v úvahu při tradičním psaní JavaScriptu, známém též jako DOM manipulation. Uzly, které vyrobím pomocí createElement (nebo nějaké nadstavby), mám okamžitě k ruce a ve vhodnou chvíli na nich události pořeším.

A nyní přichází konfrontace s prvním argumentem, zmiňovaným v předchozí kapitole – totiž že DOM manipulation je opruz, je to pracné, je to zdlouhavé a ušetřený čas by se mohl věnovat něčemu užitečnějšímu. Jaké stanovisko k tomuto zaujmout?

Autor tohoto textu píše JavaScriptové aplikace zhruba osm let a zastává názor, že zmiňovaný argument je prázdná fráze. Proč?

  1. Práce s DOMem může být pracná, ale k tomu slouží různá syntaktická zjednodušení, která abstrahují tyto úkony, nabízí zkrácený zápis a řeší kompatibilitu. Týká se to jak tvorby značek (create(“input”, {type:“button”, value:“A”, id:“b”, color:“red”})), tak konstrukce stromu (ul( li(), li( a() ), li()).
  2. Vývojářův čas, strávený psaním DOM manipulation, je minimální v porovnání s ostatními aspekty JS aplikace: logikou zpracování událostí, komunikací s backendy, psaním chytrých algoritmů na zpracování dat. Správní vývojáři jsou líní a mohu potvrdit, že se při práci snažím maximum času přemýšlet (nad algoritmy, nad logikou toku programu, …) a jen čas od času něco málo skutečně napsat. Nikdy mi nepřišlo, že bych měl optimalizovat právě čas věnovaný tvorbě těch pár pitomých odstavců, divů a formulářových prvků.

Ve světle toho, jak málo mne DOM manipulation obtěžuje, nevidím mnoho přínosů pro JavaScriptové šablonovací systémy.

Občas se ještě objevuje myšlenka, že oddělené šablony pak mohou spravovat úplně jiní lidé a cenný čas JS programátora může být pálen psaním hardcore JS logiky. Ani náhodou, říkám: šablona je s přidruženým JavaScriptem symbioticky provázána a jakmile do ní začne sahat někdo cizí, dříve nebo později provede nekompatibilní změnu takovou, že si na takto “opravené” šabloně vyláme kód zuby. Nebo naopak, samosebou.

Pojďme se nyní podívat, jakým způsobem na zmiňovaný nedostatek klientského šablonování zareagovali chytří chlapci a děvčata na Internetu.

3 MVC framework

Vítejte ve světě JS MVC: nástrojů, které vzaly šablonování a doplnily do něj události. Jedná se o bleeding edge technologii; nové rostou jako houby po dešti a opět je z čeho vybírat. Bylo by nespravedlivé tyto frameworky označit jen za vylepšené šablony – zpravidla toho s sebou nesou ještě mnohem více, typicky podporu pro HTTP, práci s URL a data binding. Ve finále však ale všechny sdílí jeden základní společný rys: berou nějaký šablonovací systém a dovolují vzniklé HTML počůrat tak, aby se nad ním dalo komfortně (čti: semi-automaticky) pracovat s událostmi.

JavaScript MVC framework

Pokud to doposud nebylo zcela patrné, tak je nejvyšší čas odhalit hlavní sdělení tohoto článku: Dle mého názoru jsou JS MVC frameworky nafouklá bublina, která vznikla opakovaným záplatováním jednoho konkrétního nádoru. Pojďme se podívat na mé argumenty:

  1. Zmiňované počůrání HTML šablony je nekoncepční a vzniklo jako ctnost z nouze, způsobené nedostatkem JS šablonování. Více o tomto níže, u konkrétních ukázek.
  2. MVC frameworky způsobují fragmentaci vývojářů. Nesdílí společná API ani vývojová paradigmata; každý je postavený dle nejlepšího vědomí a svědomí svého autora. Řadu let se JS komunita těší na konvergenci JS API v prohlížečích, aby se daly tradiční cross-browser problémy (addEventListener, querySelector, XHR, …) řešit bez zastřešujících můstků. A do této skvělé doby, kdy nekompatibilní prohlížeče skutečně mizí z trhu, najednou přichází desítky nových nástrojů se zcela odlišným zevnějškem. Hledáte JS vývojáře? Možná vám dříve stačilo, když měl skvělé povědomí o jazyce, znal prototypovou dědičnost, chápal uzávěry a event loop. Dnes už musí navíc hovořit tím dialektem JS, ve kterém je psaný firemní MVC framework – protože firemní MVC framework nectí operátor new ani klíčové slovo this; používá vlastní abstrakci, která sere na prototypy a věci, které jsou původnímu JavaScriptu blízké.

V rámci férovosti je nyní dobrá chvíle pozorovat některé populární JS MVC frameworky v jejich přirozeném prostředí. Ideální je pro to služba TodoMVC, která ukazuje implementaci triviální aplikace pomocí řady frameworků. Musíme ale opatrně – delší expozice vede k depresím z toho, v jakém marasmu se situace kolem frameworků nyní nachází.

3a Ember.js

Používá šablony Handlebars, což je syntaxe Mustache + další featury (explicitní IF a podobně). Ve vzorovém HTML jsou šablony vychytrale uloženy v uzlu <script>, což jednak není pravda (není to skript, ale hybrid HTML + custom markup), a za druhé to spolehlivě zmate většinu syntax highlighterů.

Události jsou provázány pomocí tagu či atributu action, kterému v šabloně upřesníme DOM událost a sdělíme název metody controlleru, která ji bude zpracovávat. Zatímco dříve bylo fujfujfuj napsat <label ondblclick=”editTodo()”>, nyní je hujhujhuj varianta <label {{action "editTodo" on="doubleClick"}}>.

V JavaScriptové části kódu je zcela zavrhnuto new a namísto toho věci vyrábíme různými variantami extend() a create(). Nomenklatura používá různé automagické konvence; můžeme si o nich přečíst v dokumentaci. Například controller TodosController, který je mj. zodpovědný za přidání nové položky, je sice definován s tímto jménem, ale jinak se jeho název už nikde dále v kódu nepoužívá (což neznamená, že ho můžeme pojmenovat jak chceme – aplikace očekává, že s ohledem na pojmenování okolních věcí bude něco s tímto názvem existovat. Data binding je realizován explicitním voláním set() na controlleru, který následně notifikuje posluchače o změně hodnoty.

Implementační zajímavost: v Ember.js mají funkce prototypovou metodu property, která jim definuje metadata (závislosti), na kterých volání této funkce závisí. Mimo jiné i kvůli tomu není možno používat tradiční prototypový přístup, kdy je jedna funkce sdílena spoustou různých objektů.

3b Backbone.js

Backbone nepoužívá žádné vestavěné šablonování, ale zhusta závisí na low-level knihovně Underscore. K provázání DOM událostí definuje vlastní syntaxi, podobnou CSS selektorům prefixovaným názvem události; zároveň s tím nabízí implementaci vzoru observer a tím pádem i užívání vlastních událostí.

To, čemu se jinde říká Controller, je v Backbone nazýváno View. Vlastní vyrábíme voláním create(), na prototypy můžeme zapomenout. Spolupráce s URL je zcela oddělená součást frameworku; jedná se o prosté mapování vzorů URL na vlastní funkce či události.

Zajímavost #1: ke zdrojovému kódu Backbone existuje anotace téměř ke každému řádku.

Zajímavost #2: Backbone jako jeden z mála nástrojů nenabízí vlastní abstrahovaný super; namísto toho zodpovědně vybízí k používání čistě JavaScriptového volání předka.

3c Angular.js

Tento super-heroic framework používá vlastní šablonovací jazyk, založený na speciálních HTML značkách a atributech (které z nějakého důvodu začínají na ng a připomínají tak, že až se do šablon někdo podívá za deset let, jistě mu budou připadat velmi New Generation). Výsledný mix HTML a NG je špatně čitelná směs. Vzpomínáte, jak se říkalo, že <form onsubmit=”...”> je fuj? Tak dnes frčí <form ng-submit=”...”>.

Hlavním tahounem Angularu je jeho obousměrný data-binding, totiž automatické reagování na změny v datech, se kterými se pracuje (ať už iniciované Angularem, nebo uživatelem). Realizace funguje na bázi opakovaného monitorování dat: funkcí $watch říkáme, která data chceme sledovat; (implicitním) voláním funkcí $apply a $digest dochází k ověření změny hodnot a automatickým aktualizacím. Více o těchto konceptech je k vidění v dokumentaci.

Implementační zajímavost #1: K detekci změn v datech dochází často (vlastně skoro pořád – po každém požadavku, po každé události); pro účely této detekce se musí data jednak porovnávat a druhak kopírovat (nutno si někde pamatovat minulý stav). To může mít samosbou dost neblahý vliv na výkon celé aplikace (mousemove? objemnější datové struktury?).

Implementační zajímavost #2: po změně ve watchovaných datech je volán relevantní posluchač (zpravidla ten, který má nová data vykreslit či jinak zpracovat). Jeho volání může ovšem způsobit změnu v datech někde jinde; proto je celý proces $digest potenciálně rekurzivní. Autoři tuto rekurzi omezili magickou konstantou 10, takže je nutné s daty dokonvergovat do stabilního stavu nejvýše na 10 iterací.

3d CanJS

Tento nástroj je postaven na spolupráci (a tedy i prerekvizitě) s jedním z low-level JS toolkitů: jQuery, Zepto, Dojo, YUI či Mootools. Nativně podporuje šablony EJS (syntaxe ve stylu ASP, v šabloně tedy vidíme např. <% todos.each(function( todo ) { %> či) a k dispozici je též plugin pro Mustache.

Posluchače událostí se definují vlastní CSS-like syntaxí (ať už pro DOM události či realizaci observeru). Pokud v šabloně použijeme výpis hodnoty, která je Observable (typicky .each nebo .attr), dojde v rámci šablony k navěšení posluchače na změnu tohoto Observable; při této se pak automaticky přerenderuje relevantní část šablony.

Dle dokumentace je doporučováno provazování HTML uzlů s datovými objekty – tato pochybná technika je k vidění v ukázkové aplikaci.

Zajímavost: autoři CanJS používají nezvykle obskurní techniku předávání undefined jako (nedefinovaného) parametru do všudepřítomné obalující anonymní funkce (viz první a poslední řádky zdrojových souborů).

3e KnockoutJS

Namísto tradičního šablonování si KnockoutJS počůrává části HTML atributem data-bind, ve kterém je možno specifikovat provázání obsahu prvku s daty i vyšší logiku (cykly, podmínky). Zápis

<a data-bind="css: { selected: showMode() == 'all' }" href="#/all">All</a>

tedy prvku přidá class=”selected”, pokud vykonání fragmentu JS vrátí pravdivou hodnotu. Implementace takového chování s sebou přirozeně nese takové radosti jako vnořené with a Function constructor (techniky, které patří k těm nejméně doporučovaným vůbec).

Automatická aktualizace HTML probíhá na základě vzoru Observer; voláním ko.observable() (implicitně při data-bind) vytvoříme hodnotu, která při své změně notifikuje posluchače. Sympatické je, že Model je zcela v režii uživatele a zpravidla to bývá tradiční JS konstrukční funkce. Jen ta data, která se následně propagují do HTML, jsou obalena jako ko.observable.

KnockoutJS nenabízí žádnou vestavěnou podporu pro práci s URL. Namísto Controller se v něm říká ViewModel, ale to je jen terminologický blázinec.

3z VanillaJS

Bylo by nezodpovědné jen hodnotit cizí práci a nepředvést nic vlastního. V rámci projektu TodoMVC existuje čistokrevná vanilla verze, ale není udržovaná a obyčejnému JavaScriptu dělá spíše medvědí službu. Naimplementoval jsem proto vlastní variantu ukázkové úlohy, na které je vidět, jakým způsobem k této problematice přistupujeme bez nutnosti nějakých MVC nástrojů.

Kód je rozdělen do dvou souborů – todo.js odpovídá jedné položce úkolovníku, udržuje její data i vizuální reprezentaci, zpracovává události. app.js zastřešuje celou aplikaci, aplikuje filtry, přistupuje k localStorage. Jak je vidno, hlavní manipulace s DOMem se odehrává ve funkci Todo.prototype._build – a není to nijak hrozné. Nabízejí se tato další rozšíření kódu:

  1. Helper pro tvorbu DOM prvků (pomocí výčtu vlastností v objektu nebo CSS selektorem),
  2. oddělení vizuální reprezentace do vlastního objektu (např. Todo.Visual), která by se starala výhradně o DOM a události; data by držel stávající objekt Todo.

4 Závěr

Zastánci MVC frameworků argumentují tím, že tyto nástroje dokážou zastřešit a provázat práci s individuálními komponentami a knihovnami aplikace. Dle mého názoru je tato výpomoc vágní, protože aplikační kód (controller) si člověk musí vždy vyrobit sám; to za něj žádný framework neudělá.

Naštěstí ale máme k dispozici návrhové vzory a hotová řešení pro všechny elementární úkony (práce s URL, RPC, Promise, DOM, Storage, Template, události, …) – proto mi přijde, že přidávání dalších úrovní indirekce/abstrakce má jen velmi malou přidanou hodnotu.

Až budete zvažovat použití nějakého JS MVC frameworku, zkuste si zodpovědět tyto otázky:

  1. Rozumím tomu nástroji dobře? Dokážu v implementaci ospravedlnit existenci každé řádky svého kódu a vysvětlit, proč ho používám?
  2. Bude výslednému kódu rozumět i ten, kdo k němu přijde po mně? I za dva roky?
  3. Volím framework proto, že je to nejlepší řešení mého problému? Nebo jen proto, že mi ho někdo doporučil a/nebo je zrovna in něco takového používat?
  4. Ospravedlní mi těch pár uspořených kilobajtů aplikačního kódu přítomnost frameworku, který leckdy sahá na několik set kilobajtů?
  5. Je pro mne práce s DOMem skutečně tolik obtížná a repetitivní, že kvůli ní musím klesnout k užití string-based šablon?

Autor pracuje ve společnosti Seznam na všem, co alespoň trochu souvisí s JavaScriptem. Ve volném čase se mimo jiné zabývá věcmi, které alespoň trochu souvisí s JavaScriptem. O obojím občas tweetuje jako @0ndras.

Komentáře: 31

Přehled komentářů

Radek Moc pěkné
Ondřej Žára Re: Moc pěkné
Petr Taborsky Re: Moc pěkné
Petr Novák Re: Moc pěkné
Ondřej Žára Re: Moc pěkné
Martin Hassman Re: Moc pěkné
Lukáš Vlček Dík za článek
Ondřej Žára Re: Dík za článek
Chleba VanillaJS
Rexxar Re: VanillaJS
Karel Fučík Počůrávání MVC frameworků
Martin Hassman Re: Počůrávání MVC frameworků
jlx Re: Počůrávání MVC frameworků
jlx Re: Počůrávání MVC frameworků
Karel Re: Počůrávání MVC frameworků
Daniel Steigerwald Reakce
lopata Re: Reakce
Martin Hassman Re: Reakce
JoshB Hmm
Ondřej Žára Re: Hmm
maryo
NkD spokojenost
Futrál Re: spokojenost
Martin Hassman Re: spokojenost
Futrál Re: spokojenost
Futrál Re: spokojenost
Vojtech Jasny Fajn zamysleni, ale co praxe?
Richard Šerý Nevylijte s vaničkou i dítě
keff Šablony mi dávají smysl i když na kódu dělám sám
Tomas Hodbod JS DOM
Ondřej Žára Re: JS DOM
Zdroj: https://www.zdrojak.cz/?p=8047