search check home clock-o tag tags chevron-left chevron-right chevron-up chevron-down twitter facebook github rss comment comments terminal code

[Joomla!] Kilka porad jak przyspieszyć działanie strony opartej o Joomla!

[Joomla!] Kilka porad jak przyspieszyć działanie strony opartej o Joomla!

Na konferencji JoomlaDay 2014 Polska miałem przyjemność prowadzić prezentację na temat szybkości działania stron internetowych opartych o system CMS Joomla! W poprzednim wpisie obiecałem, że zamieszczę prezentację wraz z opisem jako wpis na tym blogu.

Generalnie rzecz ujmując, zaprezentowane sposoby poprawiania działania strony można stosować nie tylko na stronach opartych na CMS Joomla! ale również opartych na WordPressie czy Drupalu.

Perfomance + Joomla = ?

Czym jest performance? Jest to angielskie określenie oznaczające wydajność, sprawność działania. W połączeniu z drugim słowem, czyli Joomla daje termin oznaczający sprawnie, szybko działającą stronę opartą o ten system.

Strona optymalizowana

W celu poprawienia wydajności na przykładowej stronie opartej o:

  • treści demo (dostępne zaraz po instalacji Joomla!),
  • darmowy szablon od GavickPro - Magazine,
  • dodatkowe, darmowe moduły od GavickPro,

wykorzystałem następujące narzędzia:

  • Google PageSpeed - narzędzie do analizowania strony internetowej w celu optymalizacji wydajności i przyspieszenia jej ładowania;
  • WebPageTest - narzędzie do testowania szybkości ładowania strony z wizualizacją postępu ładowania się strony na przestrzeni czasu. Pozwala na sprawdzenie szybkości działania strony w zależności od lokalizacji użytkownika i przeglądarki z której korzysta;
  • Grunt.js - menedżer zadań oparty o node.js - czyli platformę serwerową bazującą na języku JavaScript. Narzędzie to pomaga w automatyzacji pracy webdevelopera.

Analiza strony przed optymalizacją

Poniżej znajduje się obrazki przedstawiające wynik strony w teście PageSpeed:

Wyniki przed optymalizacją

Oraz wykres przedstawiający ile plików jest ładowanych na stronę i czas potrzebny na wyświetlenie strony:

Analiza ogólna strony przed optymalizacją

Generalnie, strona nie jest tragicznie przygotowana. W teście Google PageSpeed, pełna wersja strony uzyskała dość wysoki wynik - 88, ale w teście dla urządzeń mobilnych wynik jest daleki od ideału - 68. Strona ładuje się ponad 3 sekundy, waży około 1MB i wykonuje 57 żądań po pliki i dane.

Moim celem jest poprawienie wyniku w Google PageSpeed poprzez optymalizację plików, redukcję liczby żądań oraz skrócenie ładowania strony do 1 sekundy.

Optymalizujemy kod CSS

Usprawnianie strony WWW rozpoczniemy od optymalizacji kodu CSS. W wielu szablonach stron internetowych opartych na Joomla! można się spotkać z wykorzystaniem frameworka Bootstrap. Jest to dobre narzędzie do szybkiego tworzenia wyglądu strony czy też interfejsu aplikacji internetowych. Można wręcz powiedzieć, że Bootstrap jest jak jQuery. Można go znaleźć prawie wszędzie.

Pozbądź się zbędnych reguł CSS

Główną zaletą jak i wadą frameworka Bootstrap jest fakt, że zawiera mnóstwo reguł CSS opisujących sposób położenia niemalże dowolnego elementu na stronie. Z tego powodu jego waga w wersji zminifikowanej wynosi ponad 110kB. Jest to bardzo dużo jak na zbiór reguł CSS. Tym bardziej, że w większości przypadków na stronie nie jest używane ponad 90% tych reguł, co daje ponad 99kB zbędnego kodu CSS! Tyle niepotrzebnego kodu musi pobrać przeciętny użytkownik strony.

Aby pozbyć się zbędnego kodu CSS z naszego pliku stylów możemy wykorzystać rozszerzenie do grunt.js - grunt-uncss. Jest to rozszerzenie, które dokona analizy kodu CSS na podstawie dostarczonego zbioru adresów strony i na tej podstawie przygotuje plik z regułami CSS, które są faktycznie wykorzystywane na naszych stronach. Warto również zainteresować się tym wpisem, odnośnie wykorzystania tego rozszerzenia przy budowie stron opartych na CMS-ach. Przykład z tamtego artykułu odnosi się do WordPress, ale faktycznie może być użyty wszędzie tam gdzie mamy dostęp do sitemapy (mapy linków na stronie).

Redukcja ważności selektorów na stronie - specificity w CSS

W skrajnych przypadkach, gdy selektor dla któregoś elementu jest wielokrotnie zagnieżdżony może to doprowadzić do wolniejszego renderowania się strony (jeden taki selektor nie zrobi większej różnicy, ale kilkaset może mieć wpływ), np:
body #page .articles .article .module.module-sidebar .title a .thumb
Co możemy zrobić z takim kodem? Możemy zastosować jedną z metodologii pisania kodu CSS, np: OOCSS, SMACSS, BEM czy też AMCSS. Każda z tych metodologii ma swoje wady i zalety, dlatego kwestię wyboru pozostawiam Tobie. Nie ma jednej słusznej metodologii.

Jak można zmierzyć poziom specificity? Bardzo prosto. Za każdy identyfikator - #page, dajemy 100 punktów. Za każdą klasę, selektor atrybutu lub pseudoklasę, np. .article dajemy 10 punktów. Za każdy selektor elementu, np. a dajemy 1 punkt. Natomiast za każdy element ze stylami wewnątrz HTML, np. <div style="display:block;"></div> dajemy 1000 punktów.

W naszym przypadku, który wymieniłem wcześniej, wynik punktowy wynosi 162 punkty. Dlatego aby go nadpisać, należałoby utworzyć selektor który osiągnąłby wyższy wynik punktowy, np.
body #page .articles .article .module.module-sidebar .title a span.thumb
co daje nam 163 punkty. Taki sposób pisania kodu CSS jest nieefektywny, a późniejsze zarządzanie takim kodem CSS stanie się koszmarem dla każdego webdevelopera. Dlatego też warto się przyjrzeć wcześniej wymienionym metodologiom pisania kodu CSS.

Należy też mieć świadomość, że nieumiejętne korzystanie z preprocesorów CSS także może doprowadzić do podobnych rezultatów jak wspomniałem wyżej.

Optymalizacja animacji CSS

Jest to bardzo ważne zagadnienie ze względu na fakt, że nie każdy może się pochwalić wydajnym sprzętem mobilnym czy nawet stacjonarnym. Dlatego pierwszą rzeczą jaką należy zrobić to włączyć wspomaganie renderowania animacji przez procesor graficzny (GPU) za pomocą reguły CSS: transform: translate3d(0, 0, 0);. Dzięki temu animacje nie będą obciążały procesora a tym samym, bateria urządzenia mobilnego, z którego użytkownik korzysta przeglądając naszą stronę, nie rozładuje się za szybko, a do tego animacje zaimplementowane na stronie będą znacznie płynniejsze.

Właściwości, które możemy bezpiecznie animować należą do grupy composite (jednej z trzech; pozostałe to paint - bardzo zły wpływ na wydajność, i layer - zły wpływ na wydajność):

Położenie transform: translate(x, y);
Skalowanie transform: scale(n);
Obracanie transform: rotate(deg);
Przeźroczystość opacity: 0 … 1;

Powyższe zestawienie przedstawia tylko i wyłącznie właściwości CSS, które nie wpływają negatywnie na wydajność renderowania strony przez przeglądarkę. Pełną listę własności CSS wraz z ich wpływem na przeglądarkę możesz przejrzeć pod tym adresem.

Optymalizacja kodu JS

Kolejnym punktem, który znajdował się na liście zadań do wykonania była optymalizacja plików Javascript.

Łączenie wielu plików JS w jeden

Tutaj sprawa jest jasna. Powinniśmy zadbać o to, aby pliki JS, które są pobierane z naszego serwera, były połączone w jedną całość. Dzięki temu, zamiast wielu żądań po pliki otrzymamy jedno żądanie. To może znacząco wpłynąć na szybkość ładowania się strony. Jednakże, trzeba mieć na uwadze, aby nie doprowadzić do sytuacji gdzie jeden plik jest bardzo duży, tzn. przekracza wielkość powyżej 200kB. Wtedy należy się zastanowić czy jednak nie warto byłoby utworzyć dwa mniejsze pliki JS po 100kB każdy.

Korzystanie z AMD

Innym podejściem może być wykorzystanie sposobu działania AMD, czyli asynchronicznie ładowanych modułów. Poprzez wykorzystanie tego sposobu ładowania plików, pozostajemy przy wielu plikach, które są ładowane na żądanie w zależności od tego czy i kiedy są potrzebne. Tym samym, możemy ograniczyć liczbę żądań po pliki JS na stronie.

Ładowanie ważnego kodu JS od razu a resztę potem

Kolejne podejście opiera się na założeniu, że wiemy które funkcjonalności na stronie są krytyczne pod względem funkcjonalności strony (tj. strona nie może się bez nich obejść). Taki kod JavaScript powinien się znajdować w jednym pliku JS (pamiętamy o wadze pliku), a pozostały kod, np. kod odpowiedzialny za odpalanie funkcjonalności społecznościowych czy też odpowiedzialny za wyświetlanie reklam, ładujemy w momencie gdy strona zostanie załadowana.

Minifikacja plików CSS i JS

Należy pamiętać również o tym, aby zmniejszyć wielkość plików przy pomocy mechanizmów kompresji, usuwających komentarze z kodu czy też usuwające spacje. Tym samym, zmniejszymy również opóźnienia związane z ładowaniem plików na stronie.

Warto też wspomnieć o takim zagadnieniu jak obfuskacja kodu JS za pomocą Uglify2 lub Google Closure. Obfuskacja sprawi, że kod stanie się o wiele mniej czytelny przez to, że nazwy funkcji, nazwy zmiennych, itd.; zostaną zmienione na ich krótsze zamienniki i tak na przykład zmienna w kodzie JS o nazwie var container; stanie się zmienną o nazwie var c;. Kilka znaków mniej w wielu miejscach i waga pliku znacząco się zmniejsza.

Optymalizacja obrazków

Obrazki możemy zoptymalizować na kilka sposobów, które się nie wykluczają (w większości przypadków):

  • Zmniejszenie rozmiarów obrazka - jeśli na stronie wstawiamy obrazki ilustrujące artykuł, to nie warto linkować do obrazka o rozmiarach 3000x2000 pikseli, tylko wykorzystajmy mniejszy obrazek o wymiarach 720x480 pikseli;x
  • Optymalizowanie jakości obrazka - chodzi o kompresję jakości obrazka tak, aby zmiana jakości nie była widoczna gołym okiem. Warto skorzystać w tym celu z pluginu do grunt.js - imagemin;
  • Obrazki jako sprite'y - jest to połączenie wielu obrazków w jedną obrazek, a następnie wykorzystanie CSS do ich ustawienia na stronie. Również w tym przypadku może przyjść nam z pomocą plugin do grunt.js - sprite-packer
  • Ładowanie dużych obrazków na żądanie - jeśli musimy zadbać o to, aby urządzenia posiadające ekrany z dużą gęstością pikseli wyświetliły ostre obrazki, to ładujmy duże obrazki na żądanie, tzn. przy ładowaniu strony ładujmy ich mniejsze wersje, a potem po zakończeniu ładowania się strony wykorzystajmy JS do pobrania obrazków w lepszej jakości. Warto mieć na uwadze funkcjonalność wprowadzoną do HTML5 - <picture>, unikniemy wtedy korzystania z JS. Niestety, jeśli musimy dbać o starsze przeglądarki, to nie jest to dobre rozwiązanie dla nas.
  • Logo strony jako kod SVG lub Base64 - zamiast pobierać obrazek, możemy wstawić kod SVG (strukturą podobny do HTML) lub jako ciąg znaków czyli Base64. Jednakże, w tym drugim przypadku warto mieć na uwadze fakt, że takie zaszyfrowane ciągi znaków mogą być traktowane jako trojany lub wirusy przez mechanizmy sprawdzające stronę pod kątem bezpieczeństwa (uwaga od Darka Śniega, jednego z prelegentów na JoomlaDay).

Optymalizacja ładowania czcionek

Nie możemy również zapomnieć o zoptymalizowaniu wielkości plików czcionek. W tych przypadkach będą rozważane podejścia wykorzystujące repozytorium czcionek z Google Fonts. Możemy to zoptymalizować na kilka sposobów:

  • Jedno żądanie, wiele czcionek - jest to najlepsze rozwiązanie, gdy zależy nam na wykorzystaniu kilku rodzajów czcionek, np. jedna czcionka do tytułów, a druga to tekstu. Aby tego dokonać należy wstawić regułę: <link href="http://fonts.googleapis.com/css?family=Roboto|Alegreya+Sans" rel="stylesheet">. W ten sposób wykonujemy jedno żądanie o czcionki zamiast dwóch.
  • Ładowanie wybranych liter z czcionki - to rozwiązanie jest najbardziej optymalne wtedy, gdy wiemy jakie litery bądź znaki będą nam potrzebne w danym przypadku. Na przykład dla tekstu w logo JoomlaDay, możemy pobrać czcionkę za pomocą następującego kawałka kodu: <link href="http://fonts.googleapis.com/css?family=Alegreya+Sans:300,700&text=JoomlaDayTM" rel="stylesheet">. W ten sposób możemy znacząco zredukować wagę pliku z czcionką i przyspieszyć jego ładowanie. Co ciekawe, to nie jest reguła która się zawsze sprawdza. Może się zdarzyć sytuacja, że mimo ograniczonej liczby znaków, plik końcowy może być nieznacznie większy od pełnej wersji pliku.
    Ładowanie wybranych znaków z czcionki - rezultaty
  • Wykorzystanie localStorage jako systemu cache dla czcionek - więcej szczegółów tego rozwiązania można znaleźć pod tym adresem. Generalnie, chodzi o to, że kod CSS odpowiedzialny za ładowanie czcionek na stronie jest przechowywany w localStorage i stamtąd jest pobierany przy każdym odświeżeniu strony. Dzięki temu, ograniczamy liczbę żądań a strona się ładuje szybciej.

Analiza strony po ręcznej optymalizacji

Dodatkowo, należy pamiętać o tym aby włączyć cachowanie w ustawieniach Joomla. Dzięki temu, serwer szybciej będzie przesyłał pliki do przeglądarki użytkownika, bez potrzeby odpytywania o dane baz danych.

Gdy już przeprowadzimy powyższe optymalizacje, możemy przeprowadzić kolejną analizę, która nam odpowie na pytanie: jak bardzo udało się ulepszyć stronę? Wyniki znajdują się na obrazkach poniżej.

Wyniki po optymalizacji

Z drugiego wykresu można wyczytać o ile udało się nam przyspieszyć stronę:

Wykres po optymalizacji

Osiągnęliśmy bardzo dobry wynik. Strona ładuje się nam w 1,01 sekundy, a liczba żądań spadła z 57 do 36. Dalsze zmniejszenie liczby żądań już jest praktycznie niemożliwe, gdyż pozostałe żądania są odpowiedzialne za pobranie obrazków do artykułów znajdujących się na stronie.

Podsumowanie

Moim celem jest poprawienie wyniku w Google PageSpeed poprzez optymalizację plików, redukcję liczby żądań oraz skrócenie ładowania strony do 1 sekundy.

W tym artykule skupiłem się na ręcznych poprawkach w Joomla, dzięki którym można było zoptymalizować stronę. Należy pamiętać jednak, że ręczne modyfikacje sposobu ładowania plików CSS, JS i obrazków bardzo często skutkowało tym, że trzeba było usuwać z komponentów i modułów kawałki kodu odpowiadające za ładowanie tych plików, a następnie przeniesienie tych plików do folderu z szablonem, gdzie przy pomocy grunt.js dokonano ich optymalizacji oraz złączono wszystkie pliki CSS w jeden plik CSS, podobnie z plikami JS i obrazkami. Dzięki temu, ograniczyliśmy liczbę żądań do 3-ch.

Bardzo ważne jest, aby przed każdą aktualizacją komponentów czy modułów do Joomla, ponownie ręcznie usunąć kawałki kodu odpowiedzialne za ładowanie plików i ponownie przekopiować pliki CSS, JS i obrazków do plików szablonu. Jest trochę dłubaniny, ale dzięki temu osiągniemy najlepsze efekty

Prezentacja zawiera nieco więcej informacji, między innymi o critical render path oraz o gotowych rozwiązaniach do Joomla, które pomogą zoptymalizować stronę. Ze względu na fakt, że najlepsze wyniki osiągnięto poprzez ręczne modyfikowanie plików, to postanowiłem pominąć je w tym wpisie. Wrócę do nich w jednym z kolejnych artykułów.

Prezentacja slajdów

  • Co do specyficzności CSS jest bardzo fajna tabelka: http://www.standardista.com/css3/css-specificity/

    Co do AMD – raczej lepiej stosować i to, i minifikację równocześnie (zwłaszcza, że require.js – najpopularniejszy loader AMD – udostępnia fajne narzędzie do tego).

    No i się zastanawiam nad jednym: od lat jak mantrę powtarzamy „skrypty na koniec body, minifikacja etc”. A czy przypadkiem script[async] wrzucony na samym początku head (przed CSS-em) nie będzie wydajniejszy niźli tradycyjny plik JS na końcu body? Logicznie rzec biorąc powinien. Może czas zweryfikować pewne praktyki? 😉

    Co do czcionek i localStorage – czy ja wiem czy to aż tak dobre rozwiązanie? Gdyby lS był asynchroniczny, to problem by nie istniał. Ale jest synchroniczny i wrzucenie czegoś dużego tam jest w stanie zmrozić stronę. Ale skoro piszesz jak ograniczyć wielkość pliku z czcionką, to myślę, że nie będzie źle 😉

    Chyba najczęściej polecaną optymalizacją, której nie wymieniłeś, to wydzielenie tzw. krytycznego CSS-a (dla treści „above the fold”) i wstawienie go na żywca do pliku HTML a reszta CSS-a byłaby wówczas dociągana asyncem (np przy pomocy słynnego już loadCSS od Filament Group).

    Wszystkie te optymalizacje można mieć w pakiecie po instalacji mod_pagespeed. Pojawia się tylko jeden problem: generowany kod owszem, wyciska wszystko z serwera i chodzi szybko, ale równocześnie zaczyna przypominać siekę… Dla aplikacji, które muszą działać na CSP odpada w przedbiegach (dla purystów również ;)). Wydaje mi się, że to, co zostało tutaj przedstawione to taka rozsądna granica – zrobiliśmy wszystko, co możliwe, przy zachowaniu jak największej czystości kodu.

    BTW prawie żadna strona i tak nie jest w stanie wyciągnąć 100/100 w PageSpeed, bo… dostaje punkty karne za krótki czas cache’u pliku JS GA:http://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fcomandeer.pl sabotaż? 😉

  • Ten krytyczny CSS, to jest część prezentacji której nie poruszyłem w tym artykule. Planowałem zająć się tym jako jeden z kolejnych tematów.
    Co do asynchronicznego ładowania JS, to ostatnio widziałem porównanie (nie potrafię go teraz znaleźć), gdzie atrybut async dawał gorsze rezultaty niż defer, bo mroził renderowanie strony w momencie gdy plik JS został pobrany. Przy defer, plik JS jest wykonywany po wyrenderowaniu strony.
    O tym braku 100/100, to ja się tym niespecjalnie przejmuję, bo cele są zupełnie inne. Jeśli strona się ładuje w 1 sekundę i ma wynik powyżej 90 punktów, to uznaję że jest dobrze zoptymalizowana.

  • > atrybut async dawał gorsze rezultaty niż defer, bo mroził renderowanie strony w momencie gdy plik JS został pobrany.
    A to widzę jeszcze inaczej 😉 Trza będzie sobie usiąść pewnego wieczoru i przejrzeć te wszystkie możliwości. Jestem prawie pewien, że połączenie [async][defer] da lepszy rezultat niźli wkładanie pliku na końcu body – ale lepiej to sprawdzę, zanim dam se rękę uciąć.

    >O tym braku 100/100, to ja się tym niespecjalnie przejmuję, bo cele są zupełnie inne. Jeśli strona się ładuje w 1 sekundę i ma wynik powyżej 90 punktów, to uznaję że jest dobrze zoptymalizowana.
    To raczej oczywiste. Po prostu się bawiłem domową, chcąc sprawdzić czy rzeczywiście da się te 100/100 uzyskać i jakim kosztem. Niemniej jednak gdyby się sztywno zawsze stosować do tych zaleceń, trza by popaść w paranoję i odrzucić z połowę dobrych praktyk tworzenia stron. W pewnym momencie faktycznie jest to sztuka dla sztuki. Niestety, strony ładujące się w sekundę to dzisiaj chyba też sztuka dla sztuki (prawda, wp.pl?)…

  • Ja w ogóle pierwsze słyszę o czymś takim jak joomla, kurczę będzie trzeba trochę poczytać na ten temat i się dokształcić. Dzięki za oświecenie. 🙂