Poznajemy ścieżkę od pliku z HTMLowym markupem do drzewa DOM, początek bardziej zaawansowanych tutoriali frontendowych tutaj. Do dzieła.

Ok, gdy odpalamy plik HTML przeglądarką droga do drzewa DOM wygląda tak:

  • plik zawiera kod maszynowy (jedynki, zera)
  • kod maszynowy zamieniony na markup HTML
  • markup HTML zamieniony na tokeny
  • tokeny zamienione na węzły
  • drzewo DOM

Drzewo DOM razem z innymi elementami jest wykorzystywane przez rendering engine przeglądarki do renderowania contentu na stronie. Nie mylić z JavaScript Engine, który służy do zamiany kodu maszynowego na JS i odwrotnie oraz implementuje stos i kopiec i kolejkę. Nie mylić z pętlą zdarzeń, która to jest znowu biblioteką libuv, napisaną w C i cross-platformową…

Ok, powoli. Zamiana maszynowego na markup. To tam często używamy takiego atrybutu:

<meta charset="UTF-8" />

To jest charset, abyśmy mieli (w UTF-8) znaki inne niż ASCII. Ok, zamiana na tokeny.

Podam taki przykład, tylko funkcję lexerPrint musimy sobie wyobrazić:

$phrase = 'echo "hello world"';

lexerPrint($phrase);

// [
//     'phrase' => [
//         ['T_KEYWORD' => 'echo'],
//         ['T_TEXT' => 'hello world'],
//     ] 
// ]

To jest zamiana na tokeny. I markup HTML też podlega takiej analizie, też jest tokenizowany. Czy to oznacza, że HTML jest językiem programowania, wbrew temu co inni mówią?

A bo ja wiem? Zobaczmy na taki trik:

<body>
    <div style="width: 400px; height: 400px; background-color: tomato;">
        <div style="width: 200px; height: 200px; background-color: teal;"></div>
    
</body>

I co mamy? Div w divie, choć ten zewnętrzny div nie ma zamknięcia. A teraz proszę sobie napisać dwie pętle pod rząd, w dowolnym języku programowania, i tej zewnętrznej nie zamknąć.

I co będzie? W jaki sposób język programowania ma się domyślić, że to są pętle zagnieżdżone a nie pętla po pętli? Z drugiej strony tutaj przeglądarka mogłaby też zinterpretować, że nam chodzi o div i kolejny div.

Albo mogłaby w ogóle parse error wyświetlać zamiast starać się wyświetlić poprawnie stronę (dział biznesowy twórców takiej przeglądarki nie byłby zadowolony).

Z drugiej strony można taki feature wstawić do kompilatora, że się będzie różnych rzeczy domyślał sam. I nie mówimy tu o jakichś projektach pokroju TempleOS (choć tam o ile się orientuje były takie czary, chyba z funkcjami, których nie trzeba wywoływać nawiasami).

C# potrafi już domyślać się typów dynamicznie. Ok, my to mamy w JavaScript. Ale jest coś, co mamy, a nieczęsto doceniamy – ASI.

Pokoduj trochę w C, to zobaczysz, że „nagle zgłupiałem” i zapominam średników stawiać. Potem zobacz swój kod w JS i ogarnij, ile średników nie stawiasz, a jakoś ten kod działa.

Działa bo Automatic Semicolon Insertion działa. Taki feature. Aczkolwiek jeżeli robimy wielolinijkowego returna i źle średnik postawimy, nie zrobimy nawiasu, to ASI może nam popsuć kod (wstawić średnik tam, gdzie tego nie chcemy).

Ok, ale przechodzimy na inny temat. W każdym razie takie features różnych kompilatorów czy interpreterów, że coś się potrafi domyślić z kontekstu czegoś, zamiast ściśle wywalać błąd nie świadczą o tym, czy coś jest językiem programowania.

Analiza leksykalna, tokenizacja, parsowanie – to też nie do końca musi świadczyć, że zaraz mamy z językiem programowania do czynienia.

Możliwość zdefiniowania zmiennej, funkcji i inne takie – to świadczy, czy mamy do czynienia z językiem programowania. I tego nie traktujemy jak definicję, bo SASS według tego też by językiem programowania był.

Natomiast warto być świadomym pewnych rzeczy. Ok, mamy tokeny. Teraz te tokeny są zamieniane na węzły, czyli nodes.

Znamy klasę Node i jej rodzaje:

  • Dokument
  • Fragment dokumentu
  • Atrybut
  • Komentarz
  • Tekstowy węzeł
  • Element

Znamy też klasę Element oraz interfejs HTMLElement. Element ma tagname, id, atrybuty, ale element może być elementem XML na przykład. HTMLElement to już uściślenie, że chodzi nam o element HTMLowy.

Większe uściślenia mamy już w konkretnych interfejsach. Np. jeżeli document.createElement utworzy tag, który nie istnieje, albo markup będzie zawierał nieistniejący tag (i w żaden sposób nie dopisany do custom elements) to będzie to HTMLUnknownElement.

Będzie to nadal Element, czyli coś więcej niż węzeł tekstowy, atrybut czy komentarz, ale element interfejsu HTMLUnknownElement.

Dalej, tag a to interfejs HTMLAnchorElement. I tak dalej. Dziedziczenie z tych interfejsów też już znamy:

class AnchorScroll extends HTMLAnchorElement {
    connectedCallback(){
        this.addEventListener("click", function(e){
            e.preventDefault();
            const anchor = this.getAttribute('href');
            document.querySelector(anchor).scrollIntoView({
                behavior: 'smooth'
            });
        });
    }
}

customElements.define('custom-anchor', AnchorScroll, {extends: 'a'});

I atrybut is też znamy… Bo tam będzie tag <a> ale z is ustawionym na custom-anchor.

Swoją drogą, nazwy custom elementów zawsze muszą być dwuwyrazowe z myślnikiem (aby nie robić overshadow nazw tagów HTML, które jeszcze nie zostały dodane do specyfikacji, ale kiedyś może będą), ale nigdy nie sprawdzałem, czy nazwy tych custom components rozszerzających elementy interfejsów HTML*Element (tych ścisłych, ogólnej nie można) muszą mieć taką nazwę.

Moim zdaniem mogą mieć nazwę jaką chcą, w końcu nie są używane jako tag HTML tylko w atrybucie is, a on jest dla deweloperów i ich komponentów, jak do HTML dodadzą nowy tag, to będzie nowy tag, a nie atrybut is na starym tagu.

Z drugiej strony – nie wiem, będę musiał sprawdzić, niektóre rzeczy w praniu wychodzą.

Tak czy inaczej nodes zostają zamienione na drzewo DOM. Co się dzieje potem to już nie JS engine ale rendering engine i o tym też sobie powiemy.

Warto też mieć na uwadze koncept VDOMu. To jest używana przez takie biblioteki jak React wirtualna reprezentacja DOMu (też swego rodzaju wirtualnej reprezentacji).

I o co chodzi – o renderowanie. Żeby nie ruszać renderowania co klatkę, co akcję, co byle zmiana bądź potencjalna zmiana. Masz takie coś jak VDOM, czyli wirtualny DOM danego komponentu. Masz jakąś zmianę, nowy VDOM jest tworzony i jest wykonywany diffing. I dopiero jak framework wykryje, że te vdomy się różnią, to odpala renderowanie tego komponentu, inaczej nie ma sensu męczyć się re-renderem czegoś, co tego nie wymaga.

Tak, jak chcemy zrozumieć frameworki frontendowe to jest zła wiadomość dla nas – ludzie, który nam mówią, że możemy to zrobić słabo znając JavaScript, słabo znając to, co dzieje się w przeglądarce i po prostu pisząc te countery nie wiedząc nawet co to funkcja bind i tworzyć te komponenty, renderować je, nie wiedząc co to renderowanie… cóż, oni kłamią.

Żeby zrozumieć, dlaczego eventy we Vue nie bąbelkują powyżej swój komponent musieliśmy poznać:

  • czym w zasadzie są eventy w JS, jakie są ich fazy, co to propagacja, propagacja wszerz
  • jak się tworzy custom eventy
  • jak się tworzy custom components, co to shadow DOM

Tyle rzeczy trzeba było ogarnąć, żeby zrozumieć, dlaczego kod skopiowany z dokumentacji Alpine.js pozwala tu zrobić dispatch a tu nasłuchiwanie i fajnie jest, a kod we Vue nie pozwala tu zrobić emit, a tam słuchać, bo za wysoko…

No, tak jest ze wszystkim, trzeba sprawy znać porządnie.