It's the simplest way to show a counter of visitors on your website – just copy-paste this simple HTML code!
]]>I strive to optimise this blog's performance as well as I can. But chasing a goal of a lightweight website while keeping it pretty prevented me from realising the obvious truth that the most performant assets are… no assets.
So, inspired by Sijmen J. Mulder's directory of text-only websites, I decided to create a bare version of my blog.
Here's how it went:
Many pages of this blog are already available in multiple formats, eg. the Atom feed of all entries or a JSON version of the list of my projects, so adding a new one, .lite
, was relatively easy.
The main part was copy-pasting all <page-type>.html.twig
files to <page-type>.lite.twig
and removing all the bullshit from them. All the HTML nodes that exist solely to make the page look nicer: wrappers, containers, columns, etc. All the icons, fonts, logos, twemoji, everything that's not essential.
There's no JavaScript loaded whatsoever.
There's no external stylesheets loaded, just some minimal styling in <style>
tag and few inline style
attributes.
Images inside articles had to stay, becuase in many cases they are a very important part of the content, and not just decoration. But I made them way smaller – max 240px in width – and linked to open a bigger version, if necessary. I also replaced the JS-based lazy loading with HTML5 native loading="lazy"
.
However, the browser support for this feature is still not perfect – and if it doesn't work on someone's machine, they'll have to download 8 MB (!) of tiny images when visiting /blog.lite
… So I implemented a simple pagination, all inside a Twig template, based on ?after=<timestamp>
parameter in the query string.
Anyways… It's time for the results! 🥁
Normally, opening the the homepage makes 40 requests and loads 522 kB of resources. In the lite version, it's 2 requests (HTML and favicon) that weight just 15.8 kB. Just 3% of the original weight!
The heaviest page, /blog
makes 37 requests of 3.5 MB. In the lite version, it's 10 requests of 198 kB. Just 6% of the original weight.
Opening a random article made 34 requests of 493 kB normally, and just 3 requests of 31.2 kB in the lite version. Also just 6% of the original weight.
The differences are huuuuuuuge!
Summing up: if you don't mind websites looking ascetic in return for loading quicker, working smoother, and sparing data plan and battery, definitely check out Sijmen's directory.
And on my blog you can just head to /lite or add .lite
at the end of any subpage. 😉
Lightweight sorting of tables.
Just add [data-sort]
attributes to the th
elements in columns you’d like to sort a table by, include ~1kB of JS & CSS, and initialise with sorter()
– and that’s it!
For installation instructions and more customisation options, you can check out the readme file.
]]>Flags on websites made easy.
Inlude country flags in your websites using simple CSS classes.
]]>Proste zadanie: umieścić na stronie przycisk, który przekieruje nas do usuwania jakiegoś obiektu z bazy, ale zanim to zrobi, spyta, czy na pewno tego chcemy.
Standardowa część interfejsu, możliwa do zrealizowania w webie na wiele różnych sposobów. Najprostszym z nich jest zwykłe okienko confirm()
.
W wersji najbardziej prymitywnej wykonanie zadania wygląda mniej więcej tak:
<a href="{{ route('product_delete', {id: product.id}) }}"
onclick="return confirm('{{ 'delete.confirm'|l|e }}')">
{{ 'delete'|l }}
</a>
Prosta sprawa – linkujemy do adresu, który usunie produkt z bazy, ale przy kliknięciu pytamy użytkownika, czy na pewno chce to zrobić. Jeśli nie chce, wtedy confirm()
zwróci false
, a więc wykonanie przekierowania zostanie zablokowane.
Jest z tym kodem tylko jeden problem – mieszamy ze sobą HTML i JS. A to nie tak powinny działać internety.
Poprawnie napisana strona internetowa składa się (oczywiście w części frontendowej) z trzech warstw:
Najlepiej, gdyby warstwy te w ogóle się ze sobą nie mieszały. Dzięki temu kod jest czystszy, a warstwy mogą być używane i modyfikowane niezależnie od siebie. Można na przykład w banalnie prosty sposób zaimplementować różne skórki na stronie, po prostu podmieniając używany akrusz stylów na inny. Jeśli w HTML-u nie umieścimy żadnych stylów inline (mam nadzieję, że nikomu choć trochę znającemu temat nie trzeba tłumaczyć, jak zły jest to pomysł), lecz tylko dane oraz strukturę, w jakiej są ułożone, to całkowita zmiana wyglądu strony jest nadzwyczaj łatwa. (Dla przykładu choćby bootswatch.com albo csszengarden.com – wszystkie podstrony w dziale “themes” różnią się tylko jedną rzeczą: nazwą pliku .css)
Dokładnie to samo tyczy się JavaScriptu. Jeśli kod wykonywalny umieścimy bezpośrednio wewnątrz znacznika HTML, nasz kod staje się zdecydowanie mniej czytelny i bardziej narażony na powtarzalność. Wprowadzanie w nim potem zmian może się okazać drogą przez mękę. Powiedzmy, że po jakimś czasie zechcemy zmienić to brzydkie okienko confirm()
na przykład na bootstrapowy modal. Będziemy wtedy musieli znaleźć wszystkie użycia podobnego kodu w naszym projekcie i przerobić każde z nich. Po co się tak męczyć?
Natknąłem się ostatnio na taki oto kod, mający wykonać nasze proste zadanie bez mieszania ze sobą warstw aplikacji:
<a class="js-delete-event"
href="javascript:void(0)"
data-delete-url="{{ route('product_delete', {id: product.id}) }}"
data-confirm-message="{{ 'delete.confirm'|l|e }}">
{{ 'delete'|l }}
</a>
Natomiast w pliku JavaScriptu:
$('.js-delete-event').on('click', function() {
if (true == confirm($(this).data('confirm-message'))) {
window.location.href = $(this).data('delete-url');
}
});
Wygląda na zdecydowanie bardziej skomplikowany niż powien być... Niby jest w porządku: w HTML-u nie mamy skryptów, a jedno z drugim komunikuje się za pomocą klas i atrybutów data
, tak jak powinno być. HTML definiuje, że link jest klasy js-delete-event
, dokąd powinien kierować i jak brzmi przetłumaczona prośba o potwierdzenie, natomiast JS szuka wszystkich elementów z klasą js-delete-event
i podpina pod nie listenera, który o potwierdzenie faktycznie spyta, i w zależności od odpowiedzi, przekierowuje użytkownika do strony usuwającej, bądź też nie.
A jednak JS w tym HTML-owym kodzie się pojawia, i to w swojej najbrzydszej formie: jako pseudoprotokół. Link, zamiast kierować nas do rzeczywistej strony, odsyła nas do jakiegoś void(0)
przez nieistniejący protokół javascript
... Jest to zwykły zabieg mający na celu zablokowanie linka, skierowanie go donikąd, ale jakże strasznie psuje semantykę strony...
I ma w dupie Progressive enhancement. A to bardzo źle, mieć go w dupie...
W progressive enhancement chodzi mniej więcej o to, aby zapewnić dostęp do wszystkich podstawowych danych na stronie oraz do wszystkich podstawowych funkcji naszej aplikacji dla każdego użytkownika. Nawet jeśli używa IE6 albo nie daj bogini jakiegoś lynxa czy innej wyłącznie tekstowej przeglądarki. Natomiast im jego przeglądarka jest lepsza, nowocześniejsza i zgodniejsza ze standardami, tym więcej deweloper może zaszaleć z ładnym wyglądem i wodotryskami.
Nie zawsze użytkownikowi zadziałają skrypty. Może CDN z jakąś biblioteką zaszwankuje, może user jedzie pociągiem i mu net przerywa, a może po prostu sam je wyłączył (tak, niektórzy to robią).
Jak w takim przypadku zadziała powyższy kod? Ano właśnie, nie zadziała w ogóle. Chyba że user zajrzy w źródło strony, domyśli się, o co chodzi, i ręcznie przejdzie do URL-a podanego w atrybucie data-delete-url
. Powodzenia.
A więc progressive enhancement leży. Dla pewnej grupy użytkowników dostęp do ważnej funkcjonalności jest niemożliwy.
Spójrzmy teraz na inny kod:
<a href="{{ route('product_delete', {id: product.id}) }}"
data-confirm="{{ 'delete.confirm'|l|e }}">
{{ 'delete'|l }}
</a>
$('a[data-confirm]').click(function() {
return confirm($(this).data('confirm'));
});
Tutaj mamy w HTML-u najzwyczajniejszy w świecie link, po prostu z jednym dodatkowym atrybutem. Gdyby z jakiegoś powodu nie załadował się skypt, świat się nie zawali. Użytkownik dalej ma możliwość usunięcia elementu, po prostu jest bardziej narażony na zrobienie tego przez przypadek, bo nie zobaczy potwierdzenia.
Jeśli natomiast JS działa, skrypt znajdzie wszystkie elementy posiadające atrybut data-confirm
i podepnie pod nie listenera, zadającego użytkownikowi pytanie, które się w tym atrybucie znajduje.
Ten kod jest o wiele lepszy od poprzedniego nie tylko ze względu na respektowanie progressive enhancement:
false
w event hadlerze automatycznie zatrzymuje propagację zdarzenia. W bardzo elegancki i zwięzły sposób zatrzymaliśmy przekierowanie po kliknięciu, bez potrzeby uciekania się do pseudoprotokołów ani event.stoppropagation(); event.preventdefault();
, przy okazji jeszcze pytając o zgodę użytkownika – w jednej prostej linijce!window.location.href
. Pozwalamy na zwyczajną obsługę operacji przekierowania po kliknięciu w link, po prostu rozszerzając ją o możliwość warunkowego przerwania, zamiast sztucznie i nieelegancko blokować przekierowanie tylko po to, by potem jeszcze sztuczniej wywołać je od nowa...Tworząc kiedyś stronę internetową, trzeba było się nieźle napracować, by na każdym komputerze wyglądała mniej więcej tak samo. Każda przeglądarka interpretowała sobie kod po swojemu. Teraz jednak, gdy wszystkie nowe wersje popularnych przeglądarek trzymają się standardów (a nawet mój ulubiony były klient, Santander, przerzucił się z dinozaurów na aktualne wydania), no a zdecydowaną większość pozostałych jeszcze różnic między przeglądarkami można zniwelować używając normalize.css oraz jQuery, mogę z całą stanowczością uznać przenośność za największą zaletę technologii webowych. Piszesz kod raz, a działa wszędzie.
Z drugiej jednak strony tę samą przenośność można też uznać za wadę. Cały Internet sprowadza się bowiem ostatnio do tej świętej trójcy: HTML5 + CSS3 + JS. Bo skoro te trzy potrafią już wszystko to, co kiedyś umiały tylko Flash czy aplety Javy, to po co ryzykować bezpieczeństwo witryny, zmuszać użytkownika do instalowania wtyczek i odbierać sobie możliwość lepszej integracji elementów strony? Po prostu nie ma sensu wychodzić poza świętą trójcę. A jej rozwój jest niestety ograniczony aktualizacjami przeglądarek. Czy zatem jesteśmy na nią skazani?
Na szczęście nie! Istnieje cała rodzina języków, które bardzo sprytnie obchodzą ten problem. Wprowadzają ogromne ulepszenia i ułatwienia, ale bez potrzeby interpretowania ich przez przeglądarki! Po prostu po stronie programisty są kompilowane do jednego z tych języków, które każda przeglądarka rozumie.
Powiedzmy na przykład, że chcesz stworzyć w CSS klasy od col-1
do col-12
, z których każda będzie nadawała DIV-owi szerokość równą ileśtam dwunastych szerokości kontenera. W Sass zajmuje to trzy linijki:
@for $i from 1 through 12
.col-#{$i}
width: $i/12 * 100%
A jest kompilowane do takiego kodu:
.col-1 {
width: 8.33333%; }
.col-2 {
width: 16.66667%; }
.col-3 {
width: 25%; }
.col-4 {
width: 33.33333%; }
.col-5 {
width: 41.66667%; }
.col-6 {
width: 50%; }
.col-7 {
width: 58.33333%; }
.col-8 {
width: 66.66667%; }
.col-9 {
width: 75%; }
.col-10 {
width: 83.33333%; }
.col-11 {
width: 91.66667%; }
.col-12 {
width: 100%; }
Pierwszy z nich jest prosty, krótki i czytelny dla człowieka, drugi natomiast zostanie zrozumiany przez wszystkie przeglądarki. I wilk syty i owca cała! Sass zawiera wiele elementów, których prostemu CSS-owi brakuje: zmiennych, instrukcji warunkowych, pętli, funkcji (tzw. mixins), zagnieżdżeń, operacji arytmetycznych... O ile bardziej elastyczny staje się kod, gdy możemy zdefiniować na przykład zmienną $navWidth: 300px
i użyć jej w pięciu innych miejscach (również w wyrażeniach arytmetycznych, np. width: $navWidth / 2 + 16px
), zamiast wszędzie podawać ją wprost. Każda zmiana szerokości nawigacji jest wtedy po prostu banalna, nie wymaga szukania wszystkich zależności. O ile krótszy i bardziej przejrzysty staje się kod, jeśli możemy często powtarzające się elementy zamknąć w parametryzowane mixiny i po prostu się do nich odwoływać? Świetną listę sassowych ficzerów można znaleźć choćby na Wikipedii.
Podobnymi do niego językami są Scss oras Less. Wolę jednak Sassa z jednego powodu – brak klamer. Skoro i tak w każdym kodzie powinno się stosować odpowiednie wcięcia tabulatorami, to klamry stają się zwyczajnie zbędne. Język, który wymusza stosowanie wcięć w kodzie po pierwsze wymaga mniej klepania w klawiaturę, a po drugie daje gwarancję, że po kim byśmy kodu nie przejęli, zawsze przynajmniej pod tym względem będzie on czytelny. Same plusy! Z tego samego założenia wyszli chociażby twórcy Rubiego, Pythona czy CoffeeScriptu.
A no właśnie, CoffeeScript. Żywy dowód, że nawet tworzenie JavaScriptów może być przyjemne (słowo daję, nie uwierzyłbym, gdybym nie spróbował). W JS to dopiero jest nadmiar nawiasów i klamer:
sse = new EventSource(Routing.generate('conversation_check'));
sse.addEventListener('conversation', function(e) {
if (e.data == 'test' || e.data == 'dev') { return; }
$.post(Routing.generate('conversation_fetch'), { ids: e.data }, function(data) {
conversation.html(data);
});
}, false);
Ta sama funkcjonalność w CoffeeScripcie wygląda następująco:
sse = new EventSource Routing.generate 'conversation_check'
sse.addEventListener 'conversation', (e) ->
return if e.data in ['test', 'dev']
$.post(Routing.generate('conversation_fetch'), { ids: e.data }, (data) ->
conversation.html(data)
)
, false
Wystarczyło zastąpienie składni function(a) {...}
prostym (a) -> ...
, zmiana klamer na wcięcia oraz możliwość pominięcia niektórych nawiasów w parametrach funkcji – i voilà! Mniej klepania w klawiaturę i doszukiwania się par nawiasów, a treści tyle samo!
Nie sposób się nachwalić wszystkich ficzerów CoffeeScriptu... JSON-owe obiekty można w nim definiować podobnie jak w YAML-u, pętlę można od 1 do 100 zawrzeć w króciutkim zapisie 1..100
, zmiennych nie trzeba deklarować, można im przypisywać wartości do wielu na raz (a nawet zamieniać je wartościami w jednej linijce: [foo, bar] = [bar, foo]
i tyle!). Najlepiej po prostu przejrzeć jego stronę domową i milczeć w zachwycie!
Również w przypadku HTML-a istnieje wiele technologii, które pozwalają na wygenerowanie go na postawie bardziej wyrafinowanych języków. Lecz tutaj to trochę co innego. Mowa o template engines takich jak Twig, Smarty, Haml czy Erb – one jednak są kompilowane do języków wykonywanych po stronie serwera (PHP, Ruby, Python...), więc niezbyt pasują do mojego zestawienia. (Nota bene w Hamlu jeszcze bardziej zredukowana jest klamrowa redundancja: z <html></html>
robi się zwykłe %html
)
Potrafię znaleźć tylko dwie wady całej tej rodziny technologii w porównaniu do świętej trójcy. Po pierwsze, tak jak przeglądarki, tak samo frontendowi programiści trójcę znają z całą pewnością, czego nie zawsze można powiedzieć o jej młodszym rodzeństwie. Więc jeśli ktoś dostanie po tobie projekt, w którym używałeś Sassa czy CoffeeScriptu, może nie być mu zbyt kolorowo. Z drugiej jednak strony, są to przecież technologie bardzo łatwe do przyswojenia, a zarazem niezmiernie przydatne i wygodne. Więc chyba tylko wyświadczyłbyś mu przysługę, prawda?
Po drugie natomiast, kompilowanie trwa i wymaga zachodu. Wystarczy jednak zainstalować Bowera albo skonfigurować sobie watchera w PhpStormie, aby każde zapisanie pliku automatycznie uruchamiało kompilację. Naprawdę, wygodniej już się tego zrobić nie da!
A poza tym same plusy. Jeśli tworzysz strony, a jeszcze nie znasz tych technologii – koniecznie je wypróbuj! Z odrobiną wprawy i doświadczenia, połowa twoich frontendowych problemów może zniknąć.
]]>