2024-03-08T00:36:43+01:00https://blog.comandeer.pl/daj-sie-poznac-2017Comandeer’s blogKategoria 'Daj się poznać 2017'KoniecComandeer2017-05-29T22:45:00+02:002017-05-29T22:45:00+02:00https://blog.comandeer.pl/koniec.htmlTo już koniec. Konkurs Daj Się Poznać 2017 zbliża się do końca, więc wypada jakoś podsumować moje osiągnięcia w rozwijaniu jakże ambitnego (i przerażająco nudn…<p>To już koniec. Konkurs Daj Się Poznać 2017 zbliża się do końca, więc wypada jakoś podsumować moje osiągnięcia w rozwijaniu jakże ambitnego (i przerażająco nudnego) projektu ComSemRel. I choć najlepiej całość podsumowuje <a href="https://www.youtube.com/watch?v=oxiJrcFo724">słynna scena ze słynnego filmu</a>, to jednak kilka gorzkich słów przeciwko samemu sobie wypada napisać.</p>
<p>Tak, ComSemRel jak nie działał, tak nie działa. Przybyło może z jakieś 50 linijek sensownego kodu, z czego jakieś 40 nie działa lub działa niezgodnie z moją wolą. <em>Cusz</em>. Przez większą część konkursu, zamiast skupić się na faktycznym rozwoju projektu, skupiałem się na klupaniu narzędzi, które pozwolą mi rozwijać projekt. Oczywiście narzędzia również nie powstały, a moja bliższa znajomość z TypeScriptem okazała się jeszcze bardziej toksyczna niż ta z Rollupem czy Babelem. Ostatecznie doszedłem do wniosku, że nienawidzę progamowania i być może <a href="https://github.com/Comandeer/Lisek">odrzucenie Rusta</a> na rzecz JS-a było głupotą.</p>
<p>Czy zatem mój udział w konkusie był całkowitą porażką? Rzeknę prosto z mostu owiniętego w bawełnę: nie. Tak, pod względem programistycznym jest to wielka porażka, którą zapiję colą, ale za to powstało kilka naprawdę dobrych blogpostów! Najbardziej jestem dumny z <a href="https://blog.comandeer.pl/html-css/javascript/daj-sie-poznac-2017/2017/05/12/bem-jako-architektura.html">moich rozmyślań o BEM</a>, niemniej nie mogę nie wspomnieć także o <a href="https://blog.comandeer.pl/html-css/javascript/daj-sie-poznac-2017/2017/04/09/potrzebujemy-zachowan-nie-dziedziczenia.html">artykule o zachowaniach jako alternatywie dla Custom Elements</a> czy <a href="https://blog.comandeer.pl/refleksje/daj-sie-poznac-2017/2017/04/26/odjulianilem-sie.html">odjulianieniu</a>, które okazało się dość chwytliwym terminem. Być może w tę stronę powinienem podążać: pisanie dla ludzi… Odrzucić okowy kodu i wrócić w świat czystej imaginacji, by tworzyć łąki, po których hasać będą cieniste jednorożce…</p>
<p>Co bym jednak nie postanowił, jedno jest pewne: <a href="https://www.youtube.com/watch?v=dB62xkejDIc">to już jest koniec</a>.</p>
Prymitywna implementacja mitycznej funkcji React.createElementComandeer2017-05-28T23:40:00+02:002017-05-28T23:40:00+02:00https://blog.comandeer.pl/prymitywna-implementacja-mitycznej-funkcji-react-createelement.htmlKażdy, kto choć trochę bawił się Reactem (czy naprawdę jestem jedynym, którego nigdy do tego nie ciągnęło?!), zapewne zauważył, że pod spodem JSX-a znajduje si…<p>Każdy, kto choć trochę bawił się Reactem (czy naprawdę jestem jedynym, którego nigdy do tego nie ciągnęło?!), zapewne zauważył, że pod spodem JSX-a znajduje się <a href="https://facebook.github.io/react/docs/react-api.html#createelement">mityczna funkcja <code class="language-plaintext highlighter-rouge">React.createElement</code></a>. <a href="https://github.com/Matt-Esch/virtual-dom#example---creating-a-vtree-using-the-objects-directly">Składnia tej funkcji szybko została podchwycona przez inne biblioteki</a> i obecnie jest <i>de facto</i> standardem w środowisku zajmującym się Virtual DOM.</p>
<h2 id="virtual-dom--cóż-to">Virtual DOM – cóż to?</h2>
<p>Co jednak robi? Otóż nic wielkiego: tworzy obiekt, który imituje element z drzewka DOM. Jak zapewne już wiesz, przeglądarka, wczytując i interpretując kod HTML, zamienia go na DOM (ang. Document Object Model – Obiektowy Model Dokumentu). Jest to drzewko, które pokazuje zależności pomiędzy wszystkimi elementami na stronie. Każdy element na stronie jest reprezentowany przez obiekt zawierający wszystkie atrybuty, style, referencje do rodzica i dzieci elementu i wiele, wiele innych informacji, których prawdopodobnie nigdy nie użyjemy w praktyce. Działa to wszystko bardzo sprawnie i pozwala na tworzenie interakcji ze stroną (klikalny przycisk, yay!), ale ma dwie poważne wady: zabiera dużo miejsca i nie działa poza przeglądarką.</p>
<p>I tutaj właśnie wkracza koncept Virtual DOM. Jest to nic innego jak… drzewko elementów na stronie. Od DOM odróżnia się tym, że nie przechowujemy miliarda niepotrzebnych informacji, a jedynie te, które są niezbędne do odtworzenia stanu strony. Dodatkowo wszystko opiera się na <a href="https://twitter.com/_ericelliott/status/831965087749533698">POJO</a> (bo jednak węzły DOM to dziwne bestie są…), co sprawia, że tego typu drzewko zbudować możemy także na serwerze. A to otwiera nam bardzo ciekawe możliwości, związane ze składaniem widoków aplikacji na serwerze. Dodatkowo zabawa z obiektami pozwala nam stworzyć mechanizm pozwalający na wykrywanie różnic pomiędzy dwoma drzewkami, a tym samym: na jak najefektywniejsze modyfikowanie prawdziwego DOM (wystarczy wykryć, co się zmieniło i tylko to uaktualnić, zamiast renderować całe drzewko od nowa).</p>
<h2 id="implementujemy">Implementujemy!</h2>
<p>Tyle teorii. Przyjrzyjmy się, jak funkcji <code class="language-plaintext highlighter-rouge">React.createElement</code> się używa:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">props</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">);</span>
</code></pre></div></div>
<p>Jako pierwszy parametr podajemy nazwę tagu HTML, jako drugi – obiekt z atrybutami nowo tworzonego elementu, a jako kolejne – dzieci naszego nowego elementu.</p>
<p>Czyli wywołanie</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test</span><span class="dl">'</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">Test</span><span class="dl">'</span> <span class="p">);</span>
</code></pre></div></div>
<p>powinno nam ostatecznie wygenerować</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"test"</span><span class="nt">></span>Test<span class="nt"></div></span>
</code></pre></div></div>
<p>Analogicznie</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span>
<span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">p</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Test</span><span class="dl">'</span> <span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>powinno nam dać</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div></span>
<span class="nt"><p></span>Test<span class="nt"></p></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>Na chwilę obecną nie będziemy się zajmować generowaniem wirtualnego DOM (może wrócimy do tego zagadnienia kiedy indziej) – po prostu niech nasza funkcja, <code class="language-plaintext highlighter-rouge">createElement</code>, stworzy nam odpowiedni element DOM.</p>
<p>Zacznijmy od zadeklarowania samej funkcji:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Jak widać przyjmuje ona co najmniej trzy parametry: nazwę tworzonego elementu (<code class="language-plaintext highlighter-rouge">tag</code>), obiekt z atrybutami (<code class="language-plaintext highlighter-rouge">attributes</code>) oraz bliżej nieokreśloną liczbę parametrów, które staną się dziećmi naszego elementu.</p>
<p>Oczywiście podstawowym zadaniem naszej funkcji będzie stworzenie nowego elementu:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Z racji tego, że <code class="language-plaintext highlighter-rouge">createElement</code> będzie można zagnieżdżać w sobie, wypada tak stworzony element zwracać (inaczej zamiast dzieci będziemy dostawać piękne <code class="language-plaintext highlighter-rouge">undefined</code>):</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span> <span class="p">);</span>
<span class="k">return</span> <span class="nx">element</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Sprawdźmy, czy na razie działa:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span> <span class="p">)</span> <span class="p">);</span> <span class="c1">// <div></div></span>
</code></pre></div></div>
<p>Działa! Na razie nie ma za dużo pożytku z tego, bo <code class="language-plaintext highlighter-rouge">document.createElement</code> jest raptem o 9 znaków dłuższe. Dodajmy zatem dodawanie atrybutów:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">attributes</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">attributes</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span> <span class="nx">attributes</span> <span class="p">).</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="nx">attribute</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span> <span class="nx">attribute</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">[</span> <span class="nx">attribute</span> <span class="p">]</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">element</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Na początku sprawdzam, czy na pewno w <code class="language-plaintext highlighter-rouge">attributes</code> mamy obiekt, z którego można wyciągnąć atrybuty. I tutaj przypomnienie, dlaczego samo sprawdzenie typu nam nie wystarczy: <code class="language-plaintext highlighter-rouge">null</code> również jest typu <code class="language-plaintext highlighter-rouge">object</code>! Dlatego wypada najpierw sprawdzić, czy <code class="language-plaintext highlighter-rouge">attributes</code> jest tzw. truthy value (czyli czy konwertuje się niejawnie na <code class="language-plaintext highlighter-rouge">true</code>). Jeśli tak i jest to obiekt, to wówczas przy pomocy <code class="language-plaintext highlighter-rouge">Object.keys</code>, które pobiera wszystkie nazwy własności obiektu, iterujemy po każdym atrybucie i przypisujemy każdy z nich do naszego elementu przy użyciu metody <code class="language-plaintext highlighter-rouge">setAttribute</code> (czemu to nie jest najlepszy pomysł, to innym razem).</p>
<p>Warto tutaj zauważyć, że <code class="language-plaintext highlighter-rouge">Object.keys</code> pobiera <strong>tylko nazwy</strong> własności obiektów, więc żeby dobrać się do ich wartości, należy użyć zapisu <code class="language-plaintext highlighter-rouge">obiekt[ pobranaNazwaWłasności]</code>. Można ten kod zapisać inaczej, używając <code class="language-plaintext highlighter-rouge">Object.entries</code>, które dla każdej własności zwraca tablicę klucz-wartość:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span> <span class="nx">attributes</span> <span class="p">).</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="p">[</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span> <span class="p">]</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
</code></pre></div></div>
<p>Dodatkowo wewnątrz callbacku <code class="language-plaintext highlighter-rouge">forEach</code> użyłem <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">destrukturyzacji (dekonstrukcji?) parametrów</a>, żeby nie musieć użerać się z tablicą. Warto tylko zwrócić uwagę, że <code class="language-plaintext highlighter-rouge">Object.entries</code> ma o wiele mniejsze wsparcie niż <code class="language-plaintext highlighter-rouge">Object.keys</code>.</p>
<p>OK, sprawdźmy, czy działa:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log( createElement( 'div', { class: 'test' } ) ); // <span class="nt"><div</span> <span class="na">class=</span><span class="s">"test"</span><span class="nt">></div></span>
</code></pre></div></div>
<p>Uff, czyli zostały nam tylko dzieci do ogarnięcia! Z racji tego, że do pozostałych parametrów użyliśmy <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator">operatora spread</a>, wystarczy po raz kolejny użyć <code class="language-plaintext highlighter-rouge">forEach</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">attributes</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">attributes</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span> <span class="nx">attributes</span> <span class="p">).</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="p">[</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
<span class="p">}</span>
<span class="nx">children</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="nx">child</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span> <span class="nx">child</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
<span class="k">return</span> <span class="nx">element</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Sprawdźmy, czy działa:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">span</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">void</span><span class="dl">'</span> <span class="p">}</span> <span class="p">)</span> <span class="p">)</span> <span class="p">);</span> <span class="c1">// <div><span class="void"></span></div></span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Test</span><span class="dl">'</span> <span class="p">)</span> <span class="p">);</span> <span class="c1">// Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'</span>
</code></pre></div></div>
<p>Uppss… Dla normalnych elementów działa aż miło, niemniej dla tekstu – nie. Cały problem polega na tym, że tekst w DOM musi być reprezentowany przez węzeł typu <code class="language-plaintext highlighter-rouge">Text</code>. Zatem musimy każdy tekst na tego typu obiekt przerobić:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">children</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="nx">child</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="k">typeof</span> <span class="nx">child</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">child</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Text</span><span class="p">(</span> <span class="nx">child</span> <span class="p">);</span>
<span class="p">}</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span> <span class="nx">child</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
</code></pre></div></div>
<p>Sprawdźmy:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">createElement</span><span class="p">(</span> <span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Test</span><span class="dl">'</span> <span class="p">)</span> <span class="p">);</span> <span class="c1">// <div>Test</div></span>
</code></pre></div></div>
<p><i>Voilà</i>! Oto udało nam się stworzyć prymitywną wersję <code class="language-plaintext highlighter-rouge">React.createElement</code>, która operuje na normalnym DOM i ułatwia tworzenie drzewek elementów:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span><span class="p">,</span> <span class="nx">attributes</span><span class="p">,</span> <span class="p">...</span><span class="nx">children</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span> <span class="nx">tag</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">attributes</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">attributes</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span> <span class="nx">attributes</span> <span class="p">).</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="p">[</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span><span class="p">]</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">value</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
<span class="p">}</span>
<span class="nx">children</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span> <span class="p">(</span> <span class="nx">child</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="k">typeof</span> <span class="nx">child</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">child</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Text</span><span class="p">(</span> <span class="nx">child</span> <span class="p">);</span>
<span class="p">}</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span> <span class="nx">child</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">);</span>
<span class="k">return</span> <span class="nx">element</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
ComSemRel – raport wojenny #9Comandeer2017-05-21T20:30:00+02:002017-05-21T20:30:00+02:00https://blog.comandeer.pl/comsemrel-raport-wojenny-9.htmlDzisiaj wieczorem będę walczył z interfejsami do renderera, czyli to, co robię od miesiąca z bardzo, bardzo marnym skutkiem. Niemniej mam już ładnie rozplanowa…<p>Dzisiaj wieczorem będę walczył z interfejsami do renderera, czyli to, co robię od miesiąca z bardzo, bardzo marnym skutkiem. Niemniej mam już ładnie rozplanowane jak to powinno wyglądać: wszystko asynchroniczne, na obiecankach itd. Dodatkowo trza będzie siąść na nowo nad <code class="language-plaintext highlighter-rouge">rollup-plugin-typescript</code>.</p>
<p>To przedostatni raport z konkursu. I mam dziwne wrażenie, że jak w końcu się skończą, to… zacznie iść lepiej.</p>
Reduce i formatowanie tekstuComandeer2017-05-21T20:20:00+02:002017-05-21T20:20:00+02:00https://blog.comandeer.pl/reduce-i-formatowanie-tekstu.htmlZapewne niektórzy się oburzą, co to za obrzydłe herezje ten Comandeer głosi: jak to można używać Array.prototype.reduce do formatowania tekstu?! Ano, można i j…<p>Zapewne niektórzy się oburzą, co to za obrzydłe herezje ten Comandeer głosi: jak to można używać <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> do formatowania tekstu?! Ano, można i już demonstruję jak i po co.</p>
<h2 id="co-to-za-czary">Co to za czary?</h2>
<p>Zacznijmy od ustalenia, do czego przydaje się <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>. Chyba najbardziej znanym przykładem jest ten z dodawaniem:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">10</span> <span class="p">];</span>
<span class="kd">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">sum</span><span class="p">,</span> <span class="nx">number</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">number</span><span class="p">;</span>
<span class="p">}</span> <span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">sum</span> <span class="p">);</span> <span class="c1">// 33</span>
</code></pre></div></div>
<p>Po szybkiej analizie tego kodu można dojść do wniosku, że <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> z całej tablicy produkuje ostatecznie jedną wartość – w tym wypadku sumę wszystkich elementów tablicy. Do każdego wywołania jako 1. parametr przekazywana jest wartość, którą chcemy ostatecznie zwrócić, natomiast jako 2. parametr – aktualny element tablicy. Wszystko, co zwrócimy, zostanie przekazane kolejnemu wywołaniu <code class="language-plaintext highlighter-rouge">reduce</code> jako 1. parametr.</p>
<p>Zmodyfikujmy nieco kod i zobaczmy, co dokładnie jest wywoływane:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">10</span> <span class="p">];</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">sum</span><span class="p">,</span> <span class="nx">number</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="s2">`Wywołanie #</span><span class="p">${</span> <span class="o">++</span><span class="nx">i</span> <span class="p">}</span><span class="s2">\tsum: </span><span class="p">${</span> <span class="nx">sum</span> <span class="p">}</span><span class="s2">\tnumber: </span><span class="p">${</span> <span class="nx">number</span> <span class="p">}</span><span class="s2">`</span> <span class="p">);</span>
<span class="k">return</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">number</span><span class="p">;</span>
<span class="p">}</span> <span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">sum</span> <span class="p">);</span> <span class="c1">// 33</span>
</code></pre></div></div>
<p>Gdy spojrzymy w konsolę przeglądarki, zobaczymy taki wynik:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wywołanie #1 sum: 1 number: 5
Wywołanie #2 sum: 6 number: 8
Wywołanie #3 sum: 14 number: 9
Wywołanie #4 sum: 23 number: 10
</code></pre></div></div>
<p>Jak widać, dla 5 elementów w tablicy metoda <code class="language-plaintext highlighter-rouge">reduce</code> została wywołana tylko 4 razy. Pierwsze wywołanie nastąpiło dla drugiej wartości w tablicy (5), natomiast pod 1. parametr – przechowujący ostateczną wartość – został podstawiony po prostu 1. element tablicy. Może to sprawić pewien problem, gdy zamiast prostej wartości liczbowej będziemy mieli np. obiekty:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">5</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">8</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">9</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">10</span>
<span class="p">}</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">sum</span><span class="p">,</span> <span class="nx">number</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">number</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="p">}</span> <span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">sum</span> <span class="p">);</span> <span class="c1">// [object Object]58910</span>
</code></pre></div></div>
<p>A co tu się stało? Pomyślmy: przy 1. wywołaniu zamiast <code class="language-plaintext highlighter-rouge">sum</code> zostanie podstawiona 1. wartość w tablicy – zatem obiekt. Na tym obiekcie zostanie następnie wywołana operacja dodawania. I tutaj na scenę wkracza dynamiczne typowanie w JS: jeśli do obiektu spróbujemy dodać nie-obiekt (tekst, liczbę, booleana), najczęściej obydwie te rzeczy zostaną zamienione na tekst i dopiero wtedy połączone. Dokładnie to się tutaj stało, a przecież zupełnie nie o to nam chodziło. Na szczęście <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> przyjmuje drugi parametr, który zostanie użyty jako wartość początkowa:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">5</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">8</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">9</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">10</span>
<span class="p">}</span>
<span class="p">];</span>
<span class="kd">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">sum</span><span class="p">,</span> <span class="nx">number</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">number</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="p">},</span> <span class="mi">0</span> <span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">sum</span> <span class="p">);</span> <span class="c1">// 33</span>
</code></pre></div></div>
<p>Niemniej nieco to nam zmieni sposób wywoływania:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wywołanie #1 sum: 0 number: [object Object]
Wywołanie #2 sum: 1 number: [object Object]
Wywołanie #3 sum: 6 number: [object Object]
Wywołanie #4 sum: 14 number: [object Object]
Wywołanie #5 sum: 23 number: [object Object]
</code></pre></div></div>
<p>Tym razem <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> zostało wywołane dla każdego elementu tablicy. Co jest zrozumiałe: wszak przekazaliśmy początkową wartość, więc 1. element tablicy trzeba do niej dodać.</p>
<h2 id="formatujemy">Formatujemy!</h2>
<p>Skoro jakieś strzępki teorii już mamy, zastanówmy się, jakim to sposobem można by wykorzystać <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> do formatowania tekstu. Może od razu wyjaśnię, że pod słowem “formatowanie” mam na myśli coś na wzór funkcji <code class="language-plaintext highlighter-rouge">printf</code> z jej licznymi parametrami. Dodatkowo use case, w którym wykorzystamy <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> jest dość nietypowy.</p>
<p>Napisałem dzisiaj na kolanie prosty <a href="https://jsfiddle.net/Comandeer/dutuuLp9/">generator kodu CSS</a>. Przesuwamy suwaczek i zostaje nam wygenerowany kod CSS. I właśnie przy jego generowaniu wykorzystałem <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>. Zrobiłem to, ponieważ jako dane wejściowe otrzymuję kolekcję (tablicę) <code class="language-plaintext highlighter-rouge">input</code>ów, a chcę na podstawie ich wartości wygenerować kod CSS (tekst). Jak widać chcę przerobić tablicę na pojedynczą wartość – brzmi jak dobre zastosowanie dla <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>! Tak też zrobiłem:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">generateCSSString</span><span class="p">(</span> <span class="nx">container</span> <span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">filters</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span> <span class="nx">container</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span> <span class="dl">'</span><span class="s1">.filters__value</span><span class="dl">'</span> <span class="p">)</span> <span class="p">);</span> <span class="c1">// 1</span>
<span class="k">return</span> <span class="nx">filters</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">code</span><span class="p">,</span> <span class="nx">input</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="c1">// 2</span>
<span class="nx">code</span> <span class="o">+=</span> <span class="s2">`</span><span class="p">${</span> <span class="nx">input</span><span class="p">.</span><span class="nx">name</span> <span class="p">}</span><span class="s2">(</span><span class="p">${</span> <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="p">}${</span> <span class="nx">input</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">unit</span> <span class="p">}</span><span class="s2">) `</span><span class="p">;</span> <span class="c1">// 3</span>
<span class="k">return</span> <span class="nx">code</span><span class="p">;</span> <span class="c1">// 4</span>
<span class="p">},</span> <span class="dl">''</span> <span class="p">)</span> <span class="c1">// 5</span>
<span class="p">.</span><span class="nx">trim</span><span class="p">();</span> <span class="c1">// 6</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Co się dzieje w powyższym kodzie?</p>
<ol>
<li>Pobieram wszystkie <code class="language-plaintext highlighter-rouge">input</code>y z wartościami, na podstawie których chcę wygenerować kod CSS i z tej kolekcji robię tablicę (niestety, <code class="language-plaintext highlighter-rouge">NodeList</code> wciąż nie dochrapało się wsparcia czegokolwiek innego niż najprostszego <code class="language-plaintext highlighter-rouge">forEach</code>).</li>
<li>Na tak powstałej tablicy wykonuję <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>.</li>
<li>Dla każdego <code class="language-plaintext highlighter-rouge">input</code> generuje string oparty na jego własnościach. Warto tutaj zauważyć, że wartość stworzoną na podstawie aktualnego pola <strong>doklejam</strong> do wcześniejszego kodu. Jest to spowodowane tym, że 1. parametr w <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> zawsze musi zawierać pełną wartość.</li>
<li>Tak sklejony kod zwracam.</li>
<li>Warto przy tym zauważyć, że jako 2. parametr do <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code> przekazałem pusty ciąg tekstowy. Inaczej natknąłbym się na opisany wcześniej problem, czyli próbę dołączenia tekstu do obiektu.</li>
<li>Uważne oko zauważy, że sposób generowania kodu sprawia, że na jego końcu będzie niepotrzebna spacja. Ten <code class="language-plaintext highlighter-rouge">String.prototype.trim</code> temu zaradzi – chociaż nie jest to najbardziej eleganckie rozwiązanie.</li>
</ol>
<p>Według mnie takie wygenerowanie kodu CSS na podstawie wartości z wielu pól <code class="language-plaintext highlighter-rouge">input</code> jest o wiele czytelniejsze niż tradycyjna pętla po nich.</p>
<h2 id="uniwersalny-formater">Uniwersalny formater?</h2>
<p>Można by się pokusić o takie zmodyfikowanie powyższej funkcji, żeby dla dowolnej tablicy obiektów zwracała ładnie sformatowany ciąg tekstowy z podstawionymi zmiennymi:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">formatString</span><span class="p">(</span> <span class="nx">source</span><span class="p">,</span> <span class="nx">template</span><span class="p">,</span> <span class="nx">separator</span> <span class="o">=</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 1</span>
<span class="k">return</span> <span class="nx">source</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">output</span><span class="p">,</span> <span class="nx">input</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">output</span> <span class="o">+=</span> <span class="nx">template</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span> <span class="sr">/</span><span class="se">\{\{(</span><span class="sr">.+</span><span class="se">?)\}\}</span><span class="sr">/g</span><span class="p">,</span> <span class="p">(</span> <span class="nx">match</span><span class="p">,</span> <span class="nx">property</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="c1">// 2</span>
<span class="nx">property</span> <span class="o">=</span> <span class="nx">property</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span> <span class="c1">// 3</span>
<span class="k">return</span> <span class="nx">input</span><span class="p">[</span> <span class="nx">property</span> <span class="p">];</span> <span class="c1">// 4</span>
<span class="p">}</span> <span class="p">);</span>
<span class="k">return</span> <span class="s2">`</span><span class="p">${</span> <span class="nx">output</span> <span class="p">}${</span> <span class="nx">separator</span> <span class="p">}</span><span class="s2">`</span><span class="p">;</span> <span class="c1">// 5</span>
<span class="p">},</span> <span class="dl">''</span> <span class="p">)</span>
<span class="p">.</span><span class="nx">trim</span><span class="p">();</span> <span class="c1">// 6</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Ale się porobiło! Spróbujmy to przetrawić:</p>
<ol>
<li>Definiuję funkcję <code class="language-plaintext highlighter-rouge">formatString</code>, która przyjmuje 3. parametry: tablicę obiektów z danymi (<code class="language-plaintext highlighter-rouge">source</code>), szablon, do którego dane mają być podstawione (<code class="language-plaintext highlighter-rouge">template</code>) oraz separator pomiędzy kolejnymi przerobionymi elementami tablicy (<code class="language-plaintext highlighter-rouge">separator</code>; w kodzie formatującym CSS był on na sztywno wstawiony do kodu).</li>
<li>Z racji tego, że <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals">template literals</a>, użyte we wcześniejszej funkcji, od razu podstawiają wszystkie wartości, trzeba było wymyślić nieco inny format szablonu. Padło na <a href="https://mustache.github.io/">wąsy</a>, zatem nazwy własności są wstawiane pomiędzy podwójne nawiasy klamrowe (<code class="language-plaintext highlighter-rouge">{{</code> i <code class="language-plaintext highlighter-rouge">}}</code>). A jak już mamy tego typu zagranie, po prostu <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter">musimy zastosować <code class="language-plaintext highlighter-rouge">String.prototype.replace</code></a> i proste wyrażenie regularne odnajdujące wąsy. <code class="language-plaintext highlighter-rouge">/\{\{(.+?)\}\}/g</code> jest właśnie takim wyrażeniem, które pobierze nam wszystko pomiędzy wąsami.</li>
<li>Wewnątrz funkcji wykonującej zamianę usuwam wszystkie spacje i inne białe znaki z pobranej nazwy własności (bo w końcu nie istnieje np. własność <code class="language-plaintext highlighter-rouge">[spacja]name[spacja]</code>, istnieje za to <code class="language-plaintext highlighter-rouge">name</code>).</li>
<li>Za <code class="language-plaintext highlighter-rouge">{{ własność }}</code> podstawiam konkretną wartość własności z naszego obiektu.</li>
<li>Moglibyśmy zwrócić od razu tak przerobiony ciąg, ale trzeba też pamiętać o separatorze! Dopiero po dodaniu go, zwracamy przerobiony ciąg.</li>
<li><code class="language-plaintext highlighter-rouge">String.prototype.trim</code>, bo nie potrzebujemy spacji na końcu.</li>
</ol>
<p>Przykład zastosowania:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">formatString</span><span class="p">(</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test1</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test2</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">2</span>
<span class="p">}</span>
<span class="p">],</span> <span class="dl">'</span><span class="s1">{{ name }} = {{ value }}</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="p">);</span>
</code></pre></div></div>
<p>Ten kod da nam taki wynik:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test1 = 1
test2 = 2
</code></pre></div></div>
<p>Tylnej części co prawda nie urywa, ale robi to, co chcemy!</p>
<h2 id="bonus-ładniejsze-rozwiązanie-na-arrayprototypemap">Bonus: ładniejsze rozwiązanie na <code class="language-plaintext highlighter-rouge">Array.prototype.map</code></h2>
<p>Powyższy formater rozpadnie się w przypadku, gdy jako separator podamy cokolwiek innego niż biały znak. Wówczas radośnie doda nam go także na końcu wygenerowanego ciągu, co, w zależności od przypadku, może być problemem. Wypada się zatem zastanowić, jak temu zapobiec. Jednym z przykładowych rozwiązań może być wykorzystanie w tym celu <code class="language-plaintext highlighter-rouge">Array.prototype.join</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">].</span><span class="nx">join</span><span class="p">(</span> <span class="dl">'</span><span class="s1">;</span><span class="dl">'</span> <span class="p">);</span> <span class="c1">// 1;2;3</span>
</code></pre></div></div>
<p>To jednak oznacza, że zamiast ciągu tekstowego nasz formater powinien zwrócić tablicę. Przeróbmy zatem nasze rozwiązanie oparte o <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">formatString</span><span class="p">(</span> <span class="nx">source</span><span class="p">,</span> <span class="nx">template</span><span class="p">,</span> <span class="nx">separator</span> <span class="o">=</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">source</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span> <span class="p">(</span> <span class="nx">output</span><span class="p">,</span> <span class="nx">input</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">output</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span> <span class="nx">template</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span> <span class="sr">/</span><span class="se">\{\{(</span><span class="sr">.+</span><span class="se">?)\}\}</span><span class="sr">/g</span><span class="p">,</span> <span class="p">(</span> <span class="nx">match</span><span class="p">,</span> <span class="nx">property</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="c1">// 1</span>
<span class="nx">property</span> <span class="o">=</span> <span class="nx">property</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
<span class="k">return</span> <span class="nx">input</span><span class="p">[</span> <span class="nx">property</span> <span class="p">];</span>
<span class="p">}</span> <span class="p">)</span> <span class="p">);</span>
<span class="k">return</span> <span class="nx">output</span><span class="p">;</span>
<span class="p">},</span> <span class="p">[]</span> <span class="p">)</span> <span class="c1">// 2</span>
<span class="p">.</span><span class="nx">join</span><span class="p">(</span> <span class="nx">separator</span> <span class="p">);</span> <span class="c1">// 3</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Jak widać, zmian nie jest dużo:</p>
<ol>
<li>Zamiast dodawania do <code class="language-plaintext highlighter-rouge">output</code> wykorzystuję <code class="language-plaintext highlighter-rouge">Array.prototype.push</code>…</li>
<li>…bo jako początkową wartość ustawiłem pustą tablicę.</li>
<li>Zamiast <code class="language-plaintext highlighter-rouge">String.prototype.trim</code> robię <code class="language-plaintext highlighter-rouge">Array.prototype.join</code>.</li>
</ol>
<p>Sprawdźmy, czy działa:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">formatString</span><span class="p">(</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test1</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test2</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">2</span>
<span class="p">}</span>
<span class="p">],</span> <span class="dl">'</span><span class="s1">{{ name }} = {{ value }}</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">;</span><span class="dl">'</span> <span class="p">);</span> <span class="c1">// test1 = 1;test2 = 2</span>
</code></pre></div></div>
<p>Działa!</p>
<p>Niemniej nie jest to najładniejsze zastosowanie <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>… Przekształcenie tablicy w tablicę nie brzmi jak “zredukowanie” jej. Od produkcji tablicy z tablicy jest inna metoda, <code class="language-plaintext highlighter-rouge">Array.prototype.map</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">formatString</span><span class="p">(</span> <span class="nx">source</span><span class="p">,</span> <span class="nx">template</span><span class="p">,</span> <span class="nx">separator</span> <span class="o">=</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">source</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span> <span class="p">(</span> <span class="nx">input</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">template</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span> <span class="sr">/</span><span class="se">\{\{(</span><span class="sr">.+</span><span class="se">?)\}\}</span><span class="sr">/g</span><span class="p">,</span> <span class="p">(</span> <span class="nx">match</span><span class="p">,</span> <span class="nx">property</span> <span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">property</span> <span class="o">=</span> <span class="nx">property</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
<span class="k">return</span> <span class="nx">input</span><span class="p">[</span> <span class="nx">property</span> <span class="p">];</span>
<span class="p">}</span> <span class="p">);</span>
<span class="p">}</span> <span class="p">).</span><span class="nx">join</span><span class="p">(</span> <span class="nx">separator</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>O wiele ładniej, prawda?</p>
<h2 id="zadanie-domowe">Zadanie domowe</h2>
<p>A co jak będziemy mieli coś takiego?</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">formatString</span><span class="p">(</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">sub</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">test</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">],</span> <span class="dl">'</span><span class="s1">{{ sub.name }}</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">;</span><span class="dl">'</span> <span class="p">);</span>
</code></pre></div></div>
<p>Podpowiedź: <a href="http://ferrante.pl/frontend/javascript/namespace-w-javascript/">Ferrante fajną funkcję napisał</a>.</p>
ComSemRel – raport wojenny #8Comandeer2017-05-14T19:00:00+02:002017-05-14T19:00:00+02:00https://blog.comandeer.pl/comsemrel-raport-wojenny-8.htmlW tym tygodniu wielkie, wspaniałe nowiny: udało mi się zamknąć 4 issues! Wow, ależ produktywny tydzień…<p>W tym tygodniu <em>wielkie, wspaniałe nowiny</em>: udało mi się zamknąć <strong>4 issues</strong>! Wow, ależ produktywny tydzień…</p>
<p>Problem pojawia się, gdy przyjrzymy się, jakie to issue. A no takie jak zawsze: porządkowe. A to jakieś CI, a to jakaś konfiguracja, a to dostawienie lintera gdzieś… Powód jest prosty: realizuję równocześnie kilka innych projektów i na konkurs nie starcza czasu (sorry). Niemniej w przyszłym tygodniu <em>na pewno</em> powstanie renderer.</p>
<p>Serio, nie patrzcie tak na mnie…</p>
BEM jako architekturaComandeer2017-05-12T21:00:00+02:002017-05-12T21:00:00+02:00https://blog.comandeer.pl/bem-jako-architektura.htmlCo bardziej zaznajomieni z moją osobą zapewne już zorientowali się, że udało mi się napisać książkę. Tworzę w niej bibliotekę BEMQuery (wrócę do niej kiedyś… s…<p>Co bardziej zaznajomieni z moją osobą zapewne już zorientowali się, że <a href="http://helion.pl/ksiazki/javascript-programowanie-zaawansowane-tomasz-comandeer-jakut,jascpz.htm">udało mi się napisać książkę</a>. Tworzę w niej <a href="https://github.com/BEMQuery">bibliotekę BEMQuery</a> (wrócę do niej kiedyś… serio), która opiera się na bardzo prostym założeniu: zamiast pobierać elementy jak w jQuery, przy pomocy selektorów CSS, tworzę swój własny język zainspirowany <a href="https://en.bem.info/">metodyką BEM</a>. Można by wręcz odnieść wrażenie, że jestem fanatykiem BEM-u – piszę o nim książkę, piszę narzędzia nim inspirowane, wszędzie, gdzie tylko się da, ewangelizuję ludzi, żeby przeszli na BEM…</p>
<p>Nie zawsze tak jednak było. Kiedyś uważałem BEM za klasyczny przykład <a href="https://www.steveworkman.com/html5-2/standards/2009/classitis-the-new-css-disease/">classitisu</a>. Było to spowodowane prostym faktem, że niemal wszystkie materiały w Sieci, które przybliżyć miały czytelnikowi, czym jest BEM, skupiały się w gruncie rzeczy na jego najmniej istotnym elemencie: konwencji nazewniczej. Niemniej, gdy wspomina się o BEM, niemal wszyscy myślą właśnie o tym – o konwencji nazewniczej:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"block"</span><span class="nt">></span>
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"block__element block__element_modified"</span><span class="nt">></span>Text<span class="nt"></span></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p><a href="http://pasjaonline.pl/krotki-przyklad-na-zywo/">Nieco lepszy przykład</a> skonstruowałem lata temu. Nie zmienia to jednak faktu, że praktycznie wszystkie artykuły o BEM po polsku (ale nie tylko) sprowadzają się do tego samego: wspomnienia, że BEM to podział na bloki, elementy i modyfikatory i że jest to realizowane przy pomocy konwencji nazewniczej. I tutaj pojawia się problem: wiążę się całą metodykę z jej konkretną implementacją…</p>
<p>Wróćmy do samego początku, do podstaw i przyjrzyjmy się BEM nie przez pryzmat jego konwencji nazewniczej, ale <em>terminologii</em>. Pozwolę sobie użyć <a href="http://codepen.io/Comandeer/pen/epbaYM">swojego poprzedniego przykładu, formularza logowania</a> i jeszcze raz spróbujemy go podzielić zgodnie z filozofią BEM. Dla przypomnienia, najważniejsze założenia BEM brzmią:</p>
<ul>
<li>Strona nie jest monolitem. Każda strona składa się z bloków (dzisiaj rzeklibyśmy: komponentów), które są reużywalne i całkowicie odizolowane (samowystarczalne). To oznacza, że dany blok można “wyjąć” z danego miejsca, przełożyć w inne i wciąż będzie działać dokładnie tak samo.</li>
<li>Każdy blok składa się z elementów. Dany typ elementu może występować tylko w danym bloku.</li>
<li>Bloki i elementy mogą mieć swoje warianty, które są wprowadzane dzięki modyfikatorom.</li>
</ul>
<p>Jak widać, takie założenia nie narzucają w żaden sposób konkretnej konwencji nazewniczej. Ba, jak na razie nawet nie poruszamy się w sferze kodu! Nie ma tu ani słowa ani o HTML-u, ani CSS-ie. I to jest właśnie jedna z największych zalet BEM-u, która traci się, gdy jest on sprowadzany tylko i wyłącznie do roli konwencji nazewniczej – wówczas staje się bowiem wyłącznie szczegółem implementacyjnym. A prawda jest taka, że BEM jest całkowicie niezależny od kodu HTML czy CSS. Jest warstwą abstrakcji, która pozwala oderwać się od myślenia HTML-em czy DOM-em i skupić na myśleniu <em>konkretnym projektem</em>. Mówiąc jeszcze inaczej: BEM dla każdego projektu, w którym go używamy, pozwala wytworzyć <em>język</em>, dzięki któremu jak najdokładniej ten projekt opiszemy (w sumie tworzy tzw. <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSL – Domain Specific Language</a>).</p>
<p>I nie jest to bynajmniej twierdzenie na wyrost. W chwili, gdy zaczniemy dzielić stronę na poszczególne bloki, okaże się, że myślenie BEM-owe przychodzi nam w gruncie rzeczy niejako naturalnie. Gdy patrzymy na stronę, widzimy poszczególne bloki: nagłówek, nawigację, ów formularz logowania, który jest naszym przykładem, pasek z reklamami, stopkę itd. Gdy spojrzymy na każdy z tych bloków, mentalnie jesteśmy w stanie je dzielić dalej – na mniejsze bloki (np. menu w nagłówku) czy wreszcie na elementy, których już bardziej się podzielić nie da (np. pole do wpisania loginu w formularzu logowania). Po dłuższym przyjrzeniu się dostrzeżemy także podtypy poszczególnych bloków i elementów (modyfikatory): a to jeden z artykułów jest wyróżniony innym tłem, bo jest najpopularniejszy, a to link w menu jest podkreślony, bo prowadzi do aktualnej strony, a to pole w formularzu logowania ma czerwoną krawędź, bo jest źle wypełnione… Takie myślenie i nazywanie rzeczy bierze się wprost z obserwacji (dalej nie doszliśmy nawet do kodu!), z próby <em>nazwania</em> tego, co widzimy na ekranie. To sprawia, że BEM na takim poziomie rozumienia staje się właśnie owym językiem, w którym możemy mówić o projekcie. I to językiem zadziwiająco kompatybilnym z innymi tego typu językami (np. <a href="http://bradfrost.com/blog/post/atomic-web-design/">Atomic Design</a> – w gruncie rzeczy chodzi w nim dokładnie o to samo!).</p>
<p>Implikacje takiego rozumienia BEM są nie do przecenienia. W chwili przejścia od BEM jako szczegółu implementacyjnego do BEM jako języka zaczyna nam się krystalizować w końcu BEM jako <strong>architektura</strong>. Naturalną konsekwencją jest bowiem używanie tego samego języka na niemal wszystkich poziomach konkretnego projektu:</p>
<ul>
<li>Pierwsze makiety strony (niekoniecznie nawet elektroniczne, bo ten pomysł sprawdzi się równie dobrze przy makietach papierowych!) są złożone z niezależnych bloków, a te – z przesuwalnych elementów. To sprawia, że testy i prototypowanie jest niesamowicie ułatwione. W każdej chwili jakąś część projektu można przesunąć w inne miejsce. Dodatkowo doskonale wiemy, jak przebiegają zależności i granice: formularz logowania jest formularzem logowania, a nie “tym tam w sidebarze”.</li>
<li><a href="https://blog.prototypr.io/design-system-ac88c6740f53">Design systems czy też visual languages</a> skupiają się wokół zbioru puzzli, nie zaś – wielkich monolitów, które trudno ruszyć, a tym bardziej opisać.</li>
<li>Architektura systemu plików niejako też się sama układa. Podział na bloki w tym wypadku przecież też jest po prawdzie <em>naturalny</em> – skoro cały system jest podzielony na poszczególne komponenty, grzechem z tej organizacji nie byłoby skorzystać i na poziomie plików.</li>
<li>W końcu kod też jest skupiony wokół komponentów. Nie dostosowujemy projektu do konkretnej implementacji, ale konkretną implementację do projektu. Innymi słowy: <strong>odrzucamy</strong> myślenie w HTML-u/DOM na rzecz myślenia w BEM.</li>
</ul>
<p>I dopiero tutaj, w tym miejscu, wkraczamy na obszar kodu i implementacji. BEM jako język łączy caly projekt we wspólną całość i pozwala na proste porozumienie się pomiędzy wszystkimi jego uczestnikami: klient lepiej rozumie, z czego składa sie jego aplikacja, designerzy i fachowcy od UX mają z kolei wspólny język z programistami. A ci ostatni w końcu mogą przystąpić do przelewania BEM-u na kod.</p>
<p>Warto się teraz zastanowić, czym jest BEM na poziomie kodu. Skoro w każdym innym miejscu BEM jest <em>warstwą abstrakcji</em>, która organizuje projekt, wypada oczekiwać, że na poziomie kodu BEM również objawi się jako abstrakcja. I tak też się dzieje, BEM staje się abstrakcją nadbudowaną na DOM. Zamiast operować na konkretnych węzłach DOM, zaczynamy operować na blokach i ich elementach. Zamiast dodawać klasę do <code class="language-plaintext highlighter-rouge">input</code>a, <strong>zmieniamy stan elementu służącego do wprowadzenia hasła użytkownika</strong>. Niby zwykłe przesunięcie semantyczne, ale umożliwiające całkowite odseparowanie projektu od jego implementacji. Zmiana stanu (wprowadzenie modyfikatora) bowiem jest operacją spójną i dającą zawsze <strong>ten sam rezultat</strong> niezależnie od tego, w jaki sposób tę operację się przeprowadzi. Zmiana stanu elementu poprzez modyfikację DOM da taki sam rezultat jak zmiana stanu elementu poprzez wyrenderowanie nowej klatki w <code class="language-plaintext highlighter-rouge">canvas</code>. A to otwiera całkowicie nowe możliwości, w których GUI aplikacji niekoniecznie musi być napisane w HTML-u.</p>
<p>I teraz dopiero dochodzimy do konwencji nazewniczej BEM, która jest najpopularniejszą, ale nie jedyną możliwą implementacją BEM-u w HTML-u. Pozwoliłem sobie przygotować <a href="http://codepen.io/Comandeer/pen/dWeWja">przykładową implementację opartą na atrybutach <code class="language-plaintext highlighter-rouge">[data-*]</code></a>, która <em>spełnia</em> założenia BEM: mamy podział na bloki, elementy i są też modyfikatory. Ba, mamy rok 2017 i takie wynalazki jak React czy Web Components, które również mogą być sposobem na implementację BEM:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><login-form></span>
<span class="nt"><login-input</span> <span class="na">name=</span><span class="s">"login"</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">></span>Login<span class="nt"></login-input></span>
<span class="nt"><login-input</span> <span class="na">name=</span><span class="s">"Password"</span> <span class="na">type=</span><span class="s">"password"</span> <span class="na">error</span><span class="nt">></span>Hasło<span class="nt"></login-input></span>
<span class="nt"><login-button></span>Zaloguj mnie<span class="nt"></login-button></span>
<span class="nt"></login-form></span>
</code></pre></div></div>
<p>To wciąż BEM – wciąż mamy wyraźny podział na bloki, elementy i modyfikatory.</p>
<p>Bardzo dobrym przykładem obrazującym sposób, w jaki BEM nadbudowuje się na DOM i stanowi uniwersalny język mówienia o projekcie, jest tzw. BEM tree:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>login form
|
|-- input login
|
|-- input password
|
|-- action button
</code></pre></div></div>
<p>Na poziomie implementacji to drzewko mogłoby wyglądać tak, jako DOM:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>form[method=POST][action="/login"].login-form
|
|-- input[type=text][name=login].login-form__input.login-form__input_login
|
|-- input[type=password][name=password].login-form__input.login-form__input_password
|
|-- button[type=submit].login-form__action-button
</code></pre></div></div>
<p>Podczas gdy drugie drzewko jest zrozumiałe niemal wyłącznie dla programistów i tych, którzy znają choćby podstawy HTML-a, pierwsze drzewko i hierarchia występujących w nim elementów jest zrozumiała dla każdego. Jest bowiem wyrażona w abstrakcyjnym języku, stworzonym na potrzeby opisu konkretnych rzeczy w konkretnym projekcie.</p>
<p>I dlatego bardzo nie lubię, gdy BEM rozpatruje się wyłącznie pod kątem konwencji nazewniczej, skoro traktowanie go jako warstwy abstrakcji pozwala na stworzenie uniwersalnego języka opisu projektu aplikacji (i to na każdym etapie: od stworzenia wymagań biznesowych, poprzez prototypowanie UX-owe, projektowanie graficzne, na faktycznej implementacji kończąc), a tym samym – niejako samo z siebie narzuca architekturę (COMPONENT EVERYTHING!). BEM jako DSL – tak, BEM jako konwencja nazewnicza – meh, nie ma sensu.</p>
ComSemRel – raport wojenny #7Comandeer2017-05-07T23:00:00+02:002017-05-07T23:00:00+02:00https://blog.comandeer.pl/comsemrel-raport-wojenny-7.htmlDalej posucha straszna – za dużo mam innych rzeczy na głowie, żeby na poważnie się zająć ComSemRelem. Teraz siedzę i robię jakieś głupoty, typu poprawna konfig…<p>Dalej posucha straszna – za dużo mam innych rzeczy na głowie, żeby na poważnie się zająć ComSemRelem. Teraz siedzę i robię jakieś głupoty, typu poprawna konfiguracja CI, zapewnienie automatycznej aktualizacji zależności przy pomocy <a href="https://greenkeeper.io/">Greenkeepera</a> itd. Słowem: ruszam, żeby nie było, że umarło.</p>
<p>Równocześnie cały czas kombinuję z rendererem. Doszedłem do wniosku, że metody, jakie będzie posiadał, powinny być jak najbardziej ogólne, typu <code class="language-plaintext highlighter-rouge">print</code>, <code class="language-plaintext highlighter-rouge">displayTable</code> (ach, te spójne nazewnictwo!) itd. Dzięki temu teoretycznie będę mógł bez większego problemu podmienić konsolowy renderer na jakieś GUI w HTML-u. Brzmi jak szaleństwo, ale… ja nazwałbym to bardzo dobrą architekturą aplikacji: całkowite oddzielenie logiki od prezentacji.</p>
<p>Renderer powinien być dostępny w tym tygodniu (i tak, wiem, że powtarzam to od 3 tygodni…). A potem zobaczymy, heh.</p>
CSS w JS – mity o mitachComandeer2017-05-01T18:20:00+02:002017-05-01T18:20:00+02:00https://blog.comandeer.pl/css-w-js-mity-o-mitach.htmlWszyscy, którzy mnie znają, wiedzą doskonale, że w przypadku Sieci należę raczej do konserwatystów, będących wyznawcami starego porządku. To uwielbienie dla tr…<p>Wszyscy, którzy mnie znają, wiedzą doskonale, że w przypadku Sieci należę raczej do konserwatystów, będących <a href="http://webroad.pl/inne/3722-progressive-enhancement-zapomniany-fundament">wyznawcami starego porządku</a>. To uwielbienie dla tradycji rozciąga się także na używane technologie. Jeśli coś jest nowe, lecz <em>niewystarczająco</em> dobre, po prostu tego nie używam. Prawda jest taka, że przeżyłem już niejedną super nowoczesną, gorącą technologię (<a href="https://www.webkrytyk.pl/2014/12/12/moja-prawda-o-angular-js/">prawda, Angular?</a>) i widziałem śmierć niejednego standardu (a nawet przeglądarki!). I mam wrażenie, że kolejny z trendów również przeżyję: CSS w JS-ie.</p>
<p>Trend ten narodził się, rzecz jasna, wraz z <a href="https://speakerdeck.com/vjeux/react-css-in-js">powstaniem Reacta</a> i miał rozwiązywać problemy, których dotąd w CSS-ie nie rozwiązano. Problem polega na tym, że, owszem, czysty CSS ich nie rozwiązywał, ale dobre praktyki i całe ekosystemy skonstruowane wokół różnych metodologii – jak najbardziej. Niemniej powstały mity o tym, jaki CSS jest zły, które pozwoliły ugruntować się nowemu trendowi. Nie przeczę, rozwiązania pokroju CSS Modules czy <a href="https://github.com/styled-components/styled-components"><code class="language-plaintext highlighter-rouge">styled-components</code></a> mają swoją niszę – zwłaszcza w ekosystemie Reacta. Problem polega na tym, że ludzie ich używają z całkowicie <em>bezsensownych</em> powodów lub – o zgrozo! – “bo tak”. Doskonale to pokazują komentarze do <a href="https://hackernoon.com/stop-using-css-in-javascript-for-web-development-fa32fb873dcc">artykułu o mitach odnośnie CSS-a w JS-ie</a>, które zafrapowały mnie na tyle, że części z nich postanowiłem się przyjrzeć bliżej.</p>
<p>Najbardziej w oczy rzuca się fakt, że broniący CSS-a w JS-ie <em>nienawidzą</em> <a href="https://en.bem.info/">BEM</a>. Problem polega na tym, że, gdy przeglądam komentarze, odnoszę wrażenie, że nienawidzą BEM, bo go… nie znają. <a href="https://medium.com/@jakub.gawlas/really-the-difference-is-negligible-b44147a42783">Jakub Gawlas stwierdza wprost</a>, że nie używa BEM, bo</p>
<blockquote>
<p>[BEM] makes a mess in a bigger projects, over styled-components.</p>
</blockquote>
<p><a href="https://medium.com/@sonhanguyen/you-should-name-your-article-stop-using-styled-component-4dcd8d5800d8">Hà Nguyễn dopowiada</a>, że</p>
<blockquote>
<p>Saying BEM is the solution is like saying we don’t need javascript modular system because we can just namespace all the craps in the window object, not to mention we need to give it extra brain power to come up with a hierarchy for styles because they are separated from the javascript code.</p>
</blockquote>
<p>Najdobitniej jednak <a href="https://medium.com/@resistdesign/ewwwww-bem-shit-man-sorry-572aa9c837bb">ujmuje to Resist Design</a>:</p>
<blockquote>
<p>ewwwww, BEM is shit man, sorry.</p>
</blockquote>
<p>No tak, z argumentem takiego kalibru nie ma nawet jak dyskutować…</p>
<p>Wszyscy wyżej wymienieni komentujący <em>nie wiedzą</em>, czym jest BEM – mimo że zdaje się im inaczej. Sprowadzają go bowiem wyłącznie do konwencji nazewniczej. I tutaj muszę się z nimi zgodzić: jeśli rozpatrujemy BEM wyłącznie jako konwencję nazewniczą, nie ma on sensu… Niemniej sama oficjalna dokumentacja nazywa BEM <i>metodologią</i>, a ja powiedziałbym nawet więcej: BEM jest architekturą, wokół której zbudować można cały front aplikacji (i dywagowałbym, czy wepchanie BEM po stronie serwera również nie przyniosłoby korzyści). BEM jest przecież niczym innym, jak warstwą abstrakcji na HTML + CSS + JS – dokładnie tym samym, czym jest React.js + <code class="language-plaintext highlighter-rouge">styled-components</code>! Różnica przebiega na samym poziomie owej abstrakcyjności i rzekłbym, że tutaj nowe rozwiązanie przegrywa z kretesem. Abstracja w przypadku Reacta i <code class="language-plaintext highlighter-rouge">styled-components</code> sprowadza się do przetłumaczenia HTML-a i CSS-a do formy JS-owej. W przypadku BEM tworzone jest coś na wzór <a href="https://pl.wikipedia.org/wiki/J%C4%99zyk_dziedzinowy">DSL-a</a> dla konkretnego projektu, który można użyć na każdym etapie tworzenia: od głupiego projektu na kartce (na szkicu już przecież widać bloki, elementy i ich warianty), poprzez projekt graficzny (organizacja warstw/elementów w Photoshopie/Sketchu według konwencji nazewniczej BEM nie brzmi źle), na <em>faktycznej implementacji</em> kończąc. W przypadku abstrakcji wprowadzanej przez React.js + <code class="language-plaintext highlighter-rouge">styled-components</code> mamy do czynienia tylko z “ułatwianiem” ostatniego etapu.</p>
<p>Jeśli zatem patrzy się całościowo na BEM, wówczas argumenty o tym, że jest “brudny” i zaśmieca kod nie dają się za bardzo utrzymać. Dłuższa nazwa klasy w odniesieniu do całego “języka projektu” (jakby można to nazwać) to wszak i tak o wiele mniejsze ustępstwo niż zależność od narzędzi i bibliotek w przypadku CSS Modules czy <code class="language-plaintext highlighter-rouge">styled-components</code>.</p>
<p>Inne argumenty odnoszą się bezpośrednio do CSS-a i HTML-a i… w gruncie rzeczy są przerażające. Najpierw przyjrzyjmy się <a href="https://medium.com/@vanuan/first-of-all-what-are-we-comparing-here-df089fefa7a1">komentarzowi Vanyi Yania</a>:</p>
<blockquote>
<p>Doesn’t even sound like a myth to me. Styled-components does solve the global namespace problem. Doesn’t it?</p>
</blockquote>
<p>No ok, to nie odnosi się bezpośrednio do CSS-a, ale w sumie jego dotyczy. Wszystko rozbija się o to, czy rozwiązania JS-owe rozwiązują problem globalności stylów. Odpowiedź brzmi prosto: nie. Jedyne, co te narzędzia robią, to <a href="https://www.webpackbin.com/bins/-KeeZCr0xKfutOfOujxN">generują całkowicie losowe nazwy klas, po czym aplikują do konkretnych elementów</a>. To nie jest rozwiązanie problemu, a jego <strong>obejście przy pomocy brudnego hacku</strong>. W gruncie rzeczy konwencja nazewnicza BEM robi <em>dokładnie</em> to samo – z tym, że w niej to my ustalamy nazewnictwo, co często jest wręcz zaletą (brak udziwnionego debuggingu).</p>
<blockquote>
<p>Why do you need to know which tag is used? To me all tags are all the same with only difference the default styling. If you don’t need a special styling, why create a dedicated component?</p>
</blockquote>
<p>A to jest po prostu przerażające. Rozumiem, że tagi w JSX-ie nie przekładają się 1:1 do kodu HTML, ale twierdzenie, że nowy tag jest związany wyłącznie z inną prezentacją, aż boli. Tak ścisłe wiązanie semantyki z prezentacją prowadzi ostatecznie do tego, że wynikowy kod HTML z semantyką się nawet nie widział (pozdrowienia dla Facebooka i jego divowego limbo).</p>
<blockquote>
<p>If you want specific list styling you’d have to name it. And naming it using tag name to me is a more nice way. Otherwise why all these fancy HTML5 semantic markup tags were invented?</p>
</blockquote>
<p>Chociaż w gruncie rzeczy wychodzi na to, że faktycznie myśli się o tych tagach jako przekładalnych do HTML-a… co jest naprawdę bardzo, <em>bardzo</em> przerażające.</p>
<blockquote>
<p>It is impossibe to extend classes in CSS without some kind of preprocessor</p>
</blockquote>
<p>Uhm… Czym niby się różni</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.klasa</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="m">2px</span> <span class="m">#000</span> <span class="nb">solid</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.klasa_modyfikowana</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">blue</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>od</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.klasa</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="m">2px</span> <span class="mh">#000</span> <span class="nb">solid</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.klasa_modyfikowana</span> <span class="p">{</span>
<span class="k">@extend</span> <span class="nc">.klasa</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">blue</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Drugi kod generuje o wiele dłuższy kod CSS i powoduje, że w naszym kodzie HTML traci się informacje o zależnościach pomiędzy poszczególnymi elementami i ich wariantami (bo przecież mówimy tutaj o jakiejś sensownej wartwie abstrakcji, a nie CSS-ie na poziomie <code class="language-plaintext highlighter-rouge">.button-green</code>!).</p>
<p>Zresztą nieprawdą jest, że nie da się rozszerzać klas w CSS-ie, bo ten <a href="https://tabatkins.github.io/specs/css-apply-rule/">dochrapał się ostatnio mixinów</a>.</p>
<blockquote>
<p>implies that css overriding rules are easy to grasp</p>
</blockquote>
<p>Wydaje mi się, że CSS z założenia jest łatwiejszy do czytania niż składnia JS-a.</p>
<blockquote>
<p>#6 tells you that forcing people to create a separate file even for a single rule is a good thing</p>
</blockquote>
<p>Patrzę na mit #6 i nie widzę tam takiego stwierdzenia – typowe <a href="https://www.logicallyfallacious.com/tools/lp/Bo/LogicalFallacies/151/Reductio_ad_Absurdum"><i>reductio ad absurdum</i></a>.</p>
<blockquote>
<p>I don’t see how having a separate file improves performance. Parsing is single-threaded. For what I know, having a separate file worsens network loading performance as there’s a connection latency.</p>
</blockquote>
<p>Ktoś tu chyba nie słyszał o <a href="HTTP/2">HTTP/2</a>. Obecnie liczba żądanych plików nie stanowi aż tak dużego problemu, a w połączeniu z dobrze skonfigurowanym cache potrafi zdziałać prawdziwe cuda. <a href="https://www.polymer-project.org/">Polymer</a> z tym eksperymentuje.</p>
<p><a href="https://medium.com/@hayavuk/regardless-of-how-it-relates-to-styled-components-which-i-could-not-care-less-about-this-1c75825582d0">Hajime Yamasaki Vukelic posuwa się w swych dywagacjach jeszcze dalej</a>:</p>
<blockquote>
<p>The misconception that styling and semantics are orthogonal comes from the artificial separation of concerns that places HTML and CSS into two different buckets without much thought. Having HTML and CSS in separate buckets only splits the problem along <em>syntactical</em> boundaries, without any consideration of the intimate relationship between content and its appearance.</p>
</blockquote>
<p>Oto kolejny – szkodliwy – mit powtarzany w środowisku Reacta: mityczny <em>podział technologii</em>. Według tego mitu podział strony na HTML + CSS + JS jest bezsensowny, bo dzieli według technologii, a nie obowiązków (<i>separation of concerns</i>). Jak bardzo miałki jest to argument, najlepiej obrazuje <a href="https://hackernoon.com/building-modular-interfaces-a4e4076b4307">artykuł o tworzeniu modularnych interfejsów</a>, w którym autor sztuczny podział na HTML + CSS + JS proponuje zastąpić <em>naturalnym</em> podziałem na JSX + JS + CSS. Tutaj nawet nie trzeba komentarza.</p>
<p>Jak niebezpieczne jest natomiast łączenie semantyki z prezentacją, to najlepiej pokazują <a href="https://www.webkrytyk.pl/">recenzje na WebKrytyku</a>, w których najlepiej widać, że zawsze kończy się to <em>całkowitą niedostępnością</em>. Wydawało mi się, że pewne podstawowe koncepty są na tyle… <em>podstawowe</em>, że nie trzeba ich tłumaczyć. Najwidoczniej w chwili, gdy przestało się je tłumaczyć, przestały być podstawowe.</p>
<p><a href="https://medium.com/@equinusocio/i-suggest-you-to-show-an-example-based-on-attributes-instead-of-classes-8c9d6ae54e59">Mattia Astorino z kolei wskazuje na niesprawiedliwość przykładów z CSS-em</a>:</p>
<blockquote>
<p>I suggest you to show an example based on attributes instead of classes. Css status shoud be use the aria-* attributes o normal attributes. So.. .button[aria-primary] is a better example</p>
</blockquote>
<p><a href="https://alistapart.com/article/meaningful-css-style-like-you-mean-it">Przerabialiśmy to już</a>. Łączenie prezentacji bezpośrednio z tożsamością czy rolą elementów nie działa na dłuższą metę, bo brakuje nam pewnej warstwy abstrakcji – właśnie dlatego powstało BEM czy ów nieszczęsny CSS w JS-ie.</p>
<p>Wróćmy jeszcze na chwilę do <a href="https://medium.com/@sonhanguyen/you-should-name-your-article-stop-using-styled-component-4dcd8d5800d8">Hà Nguyễna i jego komentarza</a>:</p>
<blockquote>
<p>About web component and shadow dom, please stop politically correct your fellow devs. I think we have enough of it in real life. If the so called “standard” is not as usable as we want and we as a community can sustain our solution that makes sense, then use what makes sense. What’s the point of conforming to external standard when you are making application whose codebase is only accessible by your already-on-the-same-page team members or even just yourself anyway? I’m not a fan of css as json objects but I’ll definitely pick things like css-modules and csjs over crappy standards for UI components that I don’t intend to publish in my own application.</p>
</blockquote>
<p>I właśnie tak zabija się standardy sieciowie. Zamiast włączyć się w proces ich powstawania, webdevowie postanawiają pójść na łatwiznę. Tym sposobem zamiast standardów mamy “standardy” i zamknięte ekosystemy oparte na rozwiązaniach poszczególnych korporacji (ekosystem Reacta to przecież piaskownica Facebooka, nie można o tym zapominać!).</p>
<p><a href="https://medium.com/@drcmda/pretending-that-css-works-just-fine-with-functional-components-is-a-delusional-as-saying-jss-has-it-9d9455af1eac">Ciekawą kwestię porusza z kolei Paul Henschel</a>:</p>
<blockquote>
<p>Pretending that css works just fine with functional components is as odd as saying jss has it already figured out. […] A declarative, functional component is not a HTML directive and will never be.</p>
</blockquote>
<p>Skoro komponenty Reacta są funkcyjne, to czemu są deklarowane przy pomocy XML-owej składni? Co więcej – są przecież tłumaczone następnie na kod HTML… Odnoszę wrażenie, że funkcyjne komponenty to kolejny mit, jaki towarzyszy Reactowi, który po raz kolejny odwraca naszą uwagę od faktu, że React jest niczym więcej, jak warstwą abstrakcji dla widoku – i tym być powinien.</p>
<p>Chyba jedyny komentarz, z którym jestem w stanie się zgodzić, to <a href="https://medium.com/@nervetattoo/true-to-a-degree-1eb56b5e86fe">komentarz Raymonda Julina</a>, w którym stwerdza on, że, owszem, BEM rozwiązuje wspomniane problemy, ale na poziomie konwencji nazewniczej może dojść do konfliktów nazw i w rozwiązywaniu ich lepsza jest maszyna. I to jest prawda. Pytanie tylko, jak często takie konflikty faktycznie zachodzą i czy nie jest to problem bardziej potencjalny niż rzeczywisty?</p>
<p>Doskonale zdaję sobie sprawę z tego, że CSS w JS-ie ma swoje prawdziwe use-case’y, w których faktycznie sprawuje się lepiej niż tradycyjny CSS… Niemniej po przeczytaniu tych komentarzy odnoszę nieodparte wrażenie, że główna teza zawarta w artykule (użytkownicy używają CSS w JS-ie bez wyraźnego powodu lub z powodu trendu) sama się potwierdziła. Przeraża zwłaszcza lekkość, z jaką <em>znowu</em> zaczyna podchodzić się do semantyki HTML-a.</p>
<p>Obym był tylko zrzędliwym konserwatystą a nie prorokiem…</p>
ComSemRel – raport wojenny #6Comandeer2017-04-30T22:00:00+02:002017-04-30T22:00:00+02:00https://blog.comandeer.pl/comsemrel-raport-wojenny-6.htmlLiczycie na sensowny update, co? A tu nic takiego – ostatni tydzień minął mi na posiadaniu życia, co oczywiście przełożyło się na fakt, że nic sensownego w kod…<p>Liczycie na sensowny update, co? A tu nic takiego – ostatni tydzień minął mi na <em>posiadaniu życia</em>, co oczywiście przełożyło się na fakt, że nic sensownego w kodzie nie rzeźbiłem. Jak ostatnio wspominałem, równolegle do bunldera z obsługą TS-a rozwijam <a href="https://github.com/ComSemRel/renderer">podstawowy renderer</a>. I tym się właśnie zajmę w najbliższych dniach (bo wolne). Być może jutro ComSemRel nabędzie zdolności <del>zwyzywania</del> powitania użytkownika. A przynajmniej taką mam nadzieję.</p>
<p>Trochę głupio, że takie krótkie posty ostatnio, więc mały bonus. Jak już wspominałem, stworzyłem specjalną paczkę (<a href="https://www.npmjs.com/package/@comsemrel/interfaces"><code class="language-plaintext highlighter-rouge">@comsemrel/interfaces</code></a>), w której umieszczone są wszystkie interfejsy wykorzystywane przez różne pakiety wchodzące w skład ComSemRel. W tejże paczce znajduje się także interfejs <code class="language-plaintext highlighter-rouge">IRenderer</code>, składający się z dwóch innych interfejsów – <code class="language-plaintext highlighter-rouge">IInput</code> i <code class="language-plaintext highlighter-rouge">IOutput</code>. Wprowadziłem ten podział, gdyż IMO lepiej oddzielić wszystkie metody pobierania danych od użytkownika od wszystkich metod wyświetlania danych użytkownikowi. A renderer to nic innego jak mały programik, który pobiera i wyświetla – stąd taka a nie inna decyzja.</p>
<p>Serio nie mam co pisać… I nie, nie jest mi z tego powodu wstyd – i tak rozwój ComSemRela idzie lepiej niż przypuszczałem. Być może nawet zacznie działać do końca maja, jeśli choć ciut się zepnę do kupy.</p>
Odjulianiłem się…Comandeer2017-04-26T17:30:00+02:002017-04-26T17:30:00+02:00https://blog.comandeer.pl/odjulianilem-sie.htmlDzisiaj nadszedł ten dzień, w którym uświadomiłem sobie, że niemal całkowicie się odjulianiłem. W tym miejscu jestem Ci winny, Drogi Czytelniku, kilka słów wyj…<p>Dzisiaj nadszedł ten dzień, w którym uświadomiłem sobie, że niemal całkowicie <em>się odjulianiłem</em>. W tym miejscu jestem Ci winny, Drogi Czytelniku, kilka słów wyjaśnienia. Zacznijmy od niezbędnego kontekstu kulturowego:</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/-nOkR7HjyO4?rel=0" frameborder="0" allowfullscreen=""></iframe>
<p>Skoro już mamy to za sobą, pozwól, że zacytuję Ci oficjalną definicję odjulianienia z mojego osobistego <i>Słownika wyrazów, które wymyśliłem, gdy zbyt długo myślałem lub miałem gorączkę</i>:</p>
<blockquote>
<p><b>odjulianienie</b> – stan, w którym, mimo bezrefleksyjnego, szybkiego wykonywania danej czynności, dociera do nas, że jest ona bez sensu</p>
</blockquote>
<p>Ten przedziwny stan dosięgnął mnie ostatnio i wzmożony dodatkowo został przez <a href="http://ferrante.pl/life/publicystyka/to-co-wazne/">niezwykle refleksyjny wpis mojego kolegi po fachu</a>. Doszedłem do wniosku, iż, faktycznie, moje działania wydają się nie mieć sensu.</p>
<p>Dzień w dzień to samo. W pracy – kolejny bugfix dla <a href="https://github.com/ckeditor/ckeditor-dev">skomplikowanego edytora tekstu</a>. Gdy praca się kończy, siadam do <a href="https://github.com/Comandeer?tab=repositories">swoich prywatnych projektów</a> (ostatnio męczę <a href="https://github.com/ComSemRel">ComSemRela</a>… z dość <a href="https://blog.comandeer.pl/daj-sie-poznac-2017/2017/03/01/no-to-zaczynamy.html">oczywistych powodów</a>). Gdy już kodzenie mi nie idzie tak bardzo, jak zawsze, przesiadam się na <a href="http://www.forumweb.pl/">forum</a> (<a href="https://forum.pasja-informatyki.pl/">fora</a>?) i <a href="https://www.facebook.com/groups/742940452405327/">grupy dyskusyjne na FB</a>. I tam zaczyna się moje <em>prawdziwe</em> życie – znów staję się Comandeerem. Tym marudą z takim śmiesznym, niebieskiem avatarem. Tym <em>botem</em>, co to zawsze odpisuje w pięć sekund i wie więcej niż Google. I wszystko byłoby w jak najlepszym porządku, gdybym nie czuł, jak bardzo jest to wszystko <em>odjulianione</em>.</p>
<p>Gdy zostałem redaktorem WebKrytyka (<a href="http://www.webkrytyk.pl/2012/02/12/psz-praca-gov-pl/">mój pierwszy wpis</a> jest z lutego 2012 roku – toż to już ponad 5 lat…), miałem poczucie wspaniałej, wiekopomnej misji. Samotny rycerz, który musi zmierzyć się z siedmiogłowym smokiem polskiej Sieci i pokonać jednym ruchem swej ręki demona niesemantyczności i niedostępności. Od tego dnia minęło już 5 długich lat, a ja wciąż jestem w swej misji tam, gdzie byłem: nigdzie. Odjulianienie coraz silniej zagląda mi w oczy, drwi jawnie ze mnie.</p>
<p>Przez te 5 lat nie udało się nawet <a href="https://www.w3.org/TR/html5/embedded-content-0.html#alt">polepszyć opisów obrazków</a>, nie mówiąc już o podniesieniu świadomości webdeveloperów w kwestii dostępności i semantyki. Jakość materiałów szkoleniowych, jakie powstają, <a href="https://blog.comandeer.pl/refleksje/daj-sie-poznac-2017/2017/04/14/mam-nierowno-pod-sufitem.html">zdaje się spadać</a> zamiast polepszać. <a href="http://informaton.pl/artykuly/dostepnosc-teraz-i-6-lat-temu/">Strony są coraz mniej dostępne</a> i trend ten posuwa się w zastraszającym tempie. <a href="http://www.webkrytyk.pl/2014/12/12/moja-prawda-o-angular-js/">Złota era Angulara</a>, a obecnie Reacta, <a href="https://blog.comandeer.pl/html-css/javascript/daj-sie-poznac-2017/2017/04/02/web-components-koszmar-minionego-lata.html">zabija nieubłaganie standardy sieciowe</a> – zresztą <a href="http://webroad.pl/inne/3035-web-of-intents-czego-brakuje-dzisiejszej-sieci">niektóre były zawsze martwe</a>. Zamiast HTML-a piszemy w <a href="https://developer.mozilla.org/en-US/docs/Archive/Web/E4X">składni, którą nasi mądrzejsi przodkowie dawno odrzucili</a>, udajemy, że <a href="https://speakerdeck.com/vjeux/react-css-in-js">CSS nie istnieje</a>, a JS stał się taką świętością, że <a href="https://hackernoon.com/the-javascript-phenomenon-is-a-mass-psychosis-57adebb09359">jego najzajadlejsi przeciwnicy nazywają to psychozą</a>. Webdev w roku 2017 się po prostu odjulianił – a przynajmniej nie ma już w nim miejsca dla idealistów i innych odszczepieńców mnie podobnych.</p>
<p>Ferrante mylił się w jednym. W tym, że oddzielił webdev od ważnych spraw. Dla mnie jest to ważna sprawa… A może <em>była</em>. To wszystko się odjulianiło, a ja razem z tym.</p>
<p>Tęsknie za <a href="http://www.forumweb.pl/tematy-ogolne/polski-swiat-webdevelopingu,86246">starymi, dobrymi czasami</a>. Wtedy Sieć nie była po prostu biznesem.</p>