Archiwum Szsz.pl: stycznia 2010

Tpl.js vs JSlint

czwartek, 14 stycznia 2010

Na początek muszę zaznaczyć że jestem zwolennikiem weryfikacji HTML, CSS i JavaScript. Ten blog jest jedynym odstępstwem od tej zasady. Po części ze względu na jego eksperymentalny charakter, po części ponieważ jego tworzenie jest dla mnie zaworem bezpieczeństwa, ale głównie dla tego że silnik Bloggera, o który jest oparty, nie jest w stanie wygenerować poprawnych stron.

Główny powód używania walidatorów jest banalnie prosty: To co Firefox i Chrome uznają za poprawny kod, przez Operę i IE może zostać odrzucone z błędami składniowymi.

JSLint

Ostatnio pisałem o systemie szablonów stron na użytek JavaScript ( Szablony). Zamieszczony przykład nie przechodzi walidacji, za sprawą dwóch konstrukcji: eval i with. Jeśli pierwsze z nich Douglas Crockford uważa za Zło Wcielone, to drugie traktuje jak portal do piekielnych wymiarów.

Późniejsze wersje Tpl.js przekształciłem w prawdziwy kompilator, co pozwoliło wykorzystać możliwości współczesnych silników z kompilacją JIT. Powstała wersja, która przechodzi przez JSLint bez najmniejszych ostrzeżeń. Wydawało by się że narzędzie osiągnęło stan doskonały…

Czarna Magia

/*jslint white:false, evil:true (…)

Gdy niedawno przeglądałem kod, rzuciła mi się w oczy powyższa dyrektywa. Momentalnie przypomniał mi się cytat z niedawno oglądanego filmu…


— Co się tu dzieje, czy to jakaś…
— Magia. Najczarniejsza magia.

To ten fragment pomiędzy 0:20s a 0:42s

Formatowanie tekstu

Pierwsza dyrektywa white:false, wyłącza sprawdzanie formatowania. Wprowadzając ten test Crockford stara się chyba upodobnić JavaScript do języka Python. Tyle że stosowanie tak sztywnych reguł nie zawsze poprawia czytelność kodu. Oto przykład:

 if (/^\{else\}$/.test(key))  return "}else{";
 if (/^\{\/if\}$/.test(key))  return "}";
 if (/^\{\/for\}$/.test(key))  return "})";

Do tych trzech linijek kodu zostanie wygenerowanych aż 9 ostrzeżeń (!) Poprawienie ich jest banalnie proste… ale efekt będzie odwrotny od zamierzonego – ucierpi na tym czytelność.

Funkcja, której są fragmentem, przestanie mieścić się na ekranie, zmuszając do ciągłego przewijania tekstu.

Dodatkowe klamry obniżą czytelność, przyczyniając się do powstania potworków w stylu { return "}else{"; }. Co gorsze JSLint nie posiada żadnej opcji pozwalającej na wyłączenie tego testu.

Poprawnie sformatowany kod, utraci swoje główne przesłanie, jakim jest uwidocznienie dużej powtarzalności instrukcji.

Podążając ścieżkami wytyczonymi przez JSLint, druga wersja Tpl.js roztyła się do 240 linii i utraciła urok swojego pierwowzoru. Gdy po pewnej przerwie, chciałem rozbudować narzędzie, wybrałem za podstawę starszą wersję, przepadło sprawdzanie składni i sensowne komunikaty o błędach, ale przyszłość stoi otworem.

Porównanie z pierwotną wersją mówi samo za siebie: tpl.js (diff).

Radykalne stosowanie reguł formatowania kodu rodem z C, gdzie występują tylko globalne definicje funkcji, kończy się utratą czytelności.

Zło…

Funkcja eval i pokrewne – tu całkowicie zgadzam się autorem JSLint. Kiedyś natknąłem się na samouczek dla początkujących, gdzie została przedstawiona jako funkcja do konwersji typów, pozwalająca na przekształcenie tekstu na wartość numeryczną… Jeśli kiedyś będzie to czytał ktoś początkujący: Nigdy tak nie rób! Chyba że pewnego dnia chcesz dowiedzieć się jaką wartość ma document.cookie.

Pomimo krytyk, eval, a szczególnie new Function() są bardzo przydatne. Dają dostęp do kompilatora JavaScript, podobnie jak właściwość node.innerHTML daje dostęp do parsera HTML. Tpl.js jest kompilatorem szablonów, więc użycie tej konstrukcji jest jak najbardziej uzasadnione.

Co ciekawe, JSLint piętnuje użycie eval, podczas gdy odwołania do innerHTML przechodzą bez żadnych ostrzeżeń, a przecież to wywołanie odpowiada za podatność na ataki XSS wielu stron. Nie trzeba przecież być specjalnym geniuszem by spróbować czegoś podobnego:

node.innerHTML = "<script>alert('HAHA!')</script>"

lub nieco bardziej subtelnie:

node.innerHTML = "<img src='javascript:alert(document.cookie)'>"
DooM

Pozostało mi jeszcze omówienie instrukcji with, która rzeczywiście jest niczym otwarcie bram piekielnych. Problem w tym że gdy napiszemy a, to tak na prawdę nie mamy pewności czy chodzi o naszą zmienną, czy też właściwość obiektu będącego argumentem dla with.

Tutaj jednak sytuacja wygląda nieco inaczej, ponieważ mamy do czynienia z szablonami, których działanie nie powinno mieć żadnych efektów ubocznych:

Trzymając się tych dwóch prostych zasad, możesz być w miarę bezpieczny i nie obawiać się chodzących trupów, oraz krwawych bestii. Jedyny problem sprowadza się do tego że liczący kilkadziesiąt linii kod nie wymusza tych zasad.

Na zakończenie

Ostatnie skojarzenie…

You underestimate the power of the Dark Side

To co napisałem, jedynie uzasadnia wybór rozwiązań, nie daje jednak odpowiedzi na pytanie, które skłoniło mnie do napisania tego tekstu. Czy programy, które piszę są czarną magią? Widząc miny laików, wiem że wielu z nich jest o tym przekonanych – ale takie jest powszechne postrzeganie współczesnej technologii.

Mogę jedynie napisać że jest to całkowicie bezpieczne, pod warunkiem że będzie używane zgodnie z instrukcją obsługi. Oczywiście żadnych gwarancji, używasz tego na własną odpowiedzialność.

Odnośniki

Etykiety: , , , , , ,

Komentarze (2)

Red 20:51

Szablony

czwartek, 7 stycznia 2010

Ten tekst napisałem w lipcu zeszłego roku. Z powodów, których już nawet nie pamiętam nie opublikowałem go. Opisaną tu bibliotekę używam już z powodzeniem w dwóch moich programach i myślę że zasługuje ona na to bym poświęcił jej nieco czasu.

Zapraszam do lektury…

Szablony

Wprowadzenie

Jakiś czas temu opisywałem tworzenie aplikacji webowych w oparciu o Google AppEngine / Java.

Podany przykład, zamiast generować stronę w HTML, wysyłał surowe dane w formacie JSON, przenosząc cały ciężar na przeglądarkę.

Część odpowiedzialną za wizualizację, potraktowałem wtedy po macoszemu, poświęcając jej zaledwie chwilę. Tym razem chciał bym opisać właśnie ten element.

Stary Przykład

W ramach przypomnienia, oto fragment odpowiedzialny za wizualizację danych..

index.html.

$(function() {
    $.getJSON("/show", function(data) {
        $("body").html(
            data.name + ", " + data.value
        );
    });
});

Jak wspominałem, takie rozwiązanie jest mało eleganckie. Wynik jest efektem działania programu, który trzeba modyfikować przy każdej potencjalnej zmianie wyglądu.

Programiści tworzący klasyczne aplikacje webowe znają bardzo dobrze problemy związane z tekim podejściem. Znają też rozwiązanie: Szablony stron. Klasycznym przykładem szablonów są JSP i Smarty. Korzystanie z nich ułatwia pisanie aplikacji, oraz co ważniejsze, dostosowanie ich do indywidualnych wymagań klienta. Nie można również ignorować faktu że szablony mogą być tworzone przez inne osoby, projektantów i grafików, a nawet funkcjonować niezależnie od aplikacji.

Szablony w JavaScript

Jak uzyskać podobne możliwości, generując stronę na przeglądarce?

W Sieci można znaleźć kilkanaście prostych bibliotek, jednak zazwyczaj są to doraźne narzędzia, które pozwoliły autorowi ominąć ograniczenia, które napotkał pisząc swoją aplikację.

Jedynym w miarę dojrzałym rozwiązaniem jest Trimpath Template. Oferuje składnię wzorowaną na systemie szablonów Smarty i pozwala na tworzenie bardzo rozbudowanych, a jednocześnie całkiem czytelnych szablonów. Trimpath Template może być z powodzeniem używane jako jedyny mechanizm służący do wyświetlania informacjj.

Jednak gdy natknąłem się na dość nieprzyjemny błąd w kodzie, zacząłem się zastanawiać nad napisaniem własnego odpowiednika, który z czasem oferował by znacznie większą wygodę pracy.

Napisanie własnego silnika do szablonów okazało się łatwiejsze niż się tego spodziewałem. Pierwsza działająca wersja, ta którą mam zamiar tu zaprezentować, ma tylko 50 linii kodu.

tpl1.js

HelloWorld.. znowu..

Pora na kolejny przykład. Na początek zmierzmy się z HelloWorld. Oto dane wysłane przez serwer..

{
  'name':  'Hello',
  'value': 'world'
}
.. i szablon:
{name}, {value}

To wszystko.. Szablon zawiera to co ma być wyświetlone i nic więcej.

Prawdziwy przykład

Ponieważ HelloWorld tak na prawdę niczego nie wyjaśnia, poniżej prezentuję nieco bardziej rozbudowany przykład, w którym serwer wysyła coś takiego:

{
  'name':  'rss',
  'list': [
    {
      'title':'40 Lat Temu',
      'url':'http://blog.szsz.pl/2009/07/40-lat-temu.html'
    },
    {
      'title':'Ranking przeglądarek - Aktualizacja',
      'url':'http://blog.szsz.pl/2009/07/ranking-przegladarek-aktualizacja.html'
    },
    {
      'title':'GAE/J - Javascript',
      'url':'http://blog.szsz.pl/2009/05/gaej-javascript.html'
    },
    {
      'title':'GAE/J - Hello World (2)',
      'url':'http://blog.szsz.pl/2009/04/gaej-hello-world-2.html'
    },
    {
      'title':'GAE/J - Hello World (1)',
      'url':'http://blog.szsz.pl/2009/04/gaej-hello-world-1.html'
    }
  ]
}

Szablon może wyglądać tak:

  <h1>{name}</h1>
  <ul>
    {for item in list}
      <li><a href="{item.url}">{item.title}</a></li>
    {/for}
  </ul>
API

Pozostaje jeszcze tylko odpowiedzieć na pytanie jak to wszystko wyświetlić.

Generowanie danych w formacie JSON już opisywałem. Cały szablon jest zaimplementowany jako pojedyńcza klasa Tpl

Zakładam że zarówno tekst jak i dane_json zostaną pobrane za pomocą odpowiednich wywołań XMLHttpRequest, natomiast wynik zostanie wklejony na docelową stronę

  var szablon = new Tpl(tekst);           // kompilacja
  var wynik   = szablon.apply(dane_json); // wywołanie

Pisząc odpowiedni plugin dla jQuery można uprościć wywołanie:

    $.getJSON("/show", function(data) {
        $("body").applyTpl("hello.tpl", data);
    });

... o czym napiszę już wkrótce.

Podsumowując

Zalety

Wady

Etykiety: , , , ,

Komentarze (0)

Red 22:20

2010

piątek, 1 stycznia 2010

Szczęśliwego Nowego Roku!

Komentarze (0)

Red 00:01

Archiwum

Subskrybuj

RSS / Atom