Dlaczego niektóre moduły ładują CSS i JS na każdej stronie

389 wyświetleń

Główna przyczyna: jak system hooków PrestaShop obsługuje zasoby

Jeśli kiedykolwiek sprawdzałeś kod źródłowy swojego sklepu PrestaShop i zastanawiałeś się, dlaczego moduł, który wyświetla widget tylko na stronach produktów, ładuje swoje CSS i JavaScript na stronie głównej, stronach kategorii, a nawet w kasie — nie jesteś sam. To jeden z najczęstszych problemów wydajnościowych w ekosystemie PrestaShop, który wynika ze sposobu działania systemu hooków w połączeniu z leniwymi praktykami programistycznymi.

PrestaShop wykorzystuje architekturę opartą na hookach, aby umożliwić modułom wstrzykiwanie treści, zasobów i logiki do różnych części strony. Gdy moduł musi dodać pliki CSS lub JavaScript do strony, zazwyczaj robi to za pośrednictwem jednego z dwóch hooków: displayHeader lub actionFrontControllerSetMedia. Oba te hooki uruchamiają się na każdej stronie front office bez wyjątku. W samym systemie hooków nie istnieje wbudowany mechanizm ograniczający wywołanie hooka do konkretnych typów stron. To całkowicie odpowiedzialność dewelopera modułu, aby sprawdzić, która strona jest ładowana i zdecydować, czy dodawać zasoby, czy nie.

Problem polega na tym, że wielu deweloperów modułów idzie na skróty. Zamiast pisać logikę warunkową sprawdzającą bieżący kontroler, po prostu rejestrują swoje zasoby przy każdym wywołaniu hooka. Rozumowanie jest proste: jeśli zasoby są zawsze załadowane, moduł zawsze zadziała niezależnie od tego, gdzie pojawia się jego treść. To podejście „odpal i zapomnij" gwarantuje, że moduł działa poprawnie, ale gwarantuje również słabą wydajność.

Jak nadużywanie hooka displayHeader powoduje rozrost stron

Hook displayHeader jest głównym mechanizmem, za pomocą którego moduły dodają treść do sekcji HTML head na każdej stronie. Gdy moduł implementuje metodę hookDisplayHeader, PrestaShop wywołuje tę metodę przy każdym załadowaniu strony front office. Wewnątrz tej metody moduły zazwyczaj wywołują $this->context->controller->addCSS() i $this->context->controller->addJS(), aby zarejestrować swoje pliki zasobów.

Oto co dzieje się w typowym sklepie z 25 zainstalowanymi modułami. Piętnaście z nich ma metodę hookDisplayHeader. Każdy dodaje od jednego do czterech plików CSS i JavaScript. Oznacza to, że każda strona w Twoim sklepie ładuje od 15 do 60 dodatkowych żądań HTTP wyłącznie z modułów, niezależnie od tego, czy te moduły wyświetlają cokolwiek na bieżącej stronie.

Rozważ konkretny przykład. Moduł dodający popup z przewodnikiem po rozmiarach na stronach produktów potrzebuje jednego pliku CSS do stylizacji popupu i jednego pliku JavaScript do obsługi zachowania popupu. Jeśli deweloper zarejestruje te zasoby w hookDisplayHeader bez żadnego warunkowego sprawdzenia, oba pliki ładują się na stronie głównej, na każdej stronie kategorii, na stronie koszyka, w kasie i na każdej stronie CMS. Przewodnik po rozmiarach nigdy nie pojawi się na żadnej z tych stron, ale przeglądarka i tak pobiera, parsuje i przetwarza pliki CSS i JavaScript.

Pomnóż to przez każdy moduł, który podąża za tym samym wzorcem, i zaczniesz rozumieć, dlaczego niektóre sklepy PrestaShop ładują 2 MB lub więcej zasobów modułów na stronach, gdzie większość tych zasobów jest całkowicie zbędna.

Hook actionFrontControllerSetMedia

PrestaShop 1.7 wprowadził hook actionFrontControllerSetMedia jako bardziej nowoczesną alternatywę dla displayHeader do rejestracji zasobów. Ten hook został zaprojektowany specjalnie do rejestrowania plików CSS i JavaScript przy użyciu nowych metod registerStylesheet i registerJavascript. Te metody oferują przewagi nad starszymi funkcjami addCSS i addJS, w tym możliwość ustawiania priorytetu ładowania, określania atrybutów async lub defer oraz kontrolowania pozycji (head lub bottom) znaczników skryptów.

Jednak actionFrontControllerSetMedia cierpi na dokładnie ten sam fundamentalny problem co displayHeader: uruchamia się na każdej stronie. Moduł rejestrujący zasoby w hookActionFrontControllerSetMedia bez sprawdzania bieżącego kontrolera ładuje te zasoby wszędzie, tak samo jak starsze podejście.

Jedyną różnicą jest metoda rejestracji, nie zakres ładowania. Czy deweloper użyje addCSS w displayHeader, czy registerStylesheet w actionFrontControllerSetMedia, wynik jest taki sam, jeśli nie doda logiki warunkowej. Zasoby ładują się globalnie.

Prawidłowe ładowanie warunkowe: co robią dobre moduły

Dobrze opracowany moduł sprawdza, którą stronę przegląda klient, zanim załaduje jakiekolwiek zasoby. Standardowym sposobem na to w PrestaShop jest sprawdzenie nazwy kontrolera. Każda strona front office jest obsługiwana przez konkretny kontroler: IndexController dla strony głównej, CategoryController dla stron kategorii, ProductController dla stron produktów, CartController dla koszyka i tak dalej.

Wewnątrz metody hookDisplayHeader lub hookActionFrontControllerSetMedia deweloper może uzyskać dostęp do bieżącego kontrolera przez $this->context->controller. Sprawdzając nazwę klasy lub właściwość php_self kontrolera, moduł może zdecydować, czy ładować swoje zasoby. Moduł recenzji produktów, na przykład, powinien sprawdzić, czy bieżąca strona jest stroną produktu i załadować swoje CSS i JavaScript tylko w takim przypadku.

To podejście warunkowe nie jest trudne do wdrożenia. Dodaje może od pięciu do dziesięciu linii kodu do metody hooka. Jednak zaskakująco wiele modułów, w tym popularne płatne moduły na marketplace PrestaShop Addons i znane darmowe moduły, całkowicie pomija to sprawdzenie. Powodem jest połączenie wygody dewelopera i faktu, że PrestaShop nie wymusza ani nawet nie zachęca do warunkowego ładowania w swojej dokumentacji rozwoju modułów.

Niektórzy deweloperzy argumentują, że globalne ładowanie zasobów zapewnia kompatybilność z niestandardowymi szablonami lub nietypowymi konfiguracjami stron, gdzie treść modułu może pojawić się niespodziewanie. Choć ten argument ma ziarno prawdy, nie uzasadnia kosztu wydajnościowego. Lepszym podejściem jest domyślne warunkowe ładowanie zasobów z opcją konfiguracyjną umożliwiającą globalne ładowanie dla sklepów, które tego potrzebują.

Metoda setMedia: najlepsze praktyki dla deweloperów modułów

Dla deweloperów modułów czytających ten artykuł, oto najlepsze praktyki ładowania zasobów, które szanują wydajność stron użytkowników.

Po pierwsze, zawsze używaj hooka actionFrontControllerSetMedia zamiast displayHeader do rejestracji zasobów w PrestaShop 1.7 i nowszych. Nowszy hook zapewnia lepszą kontrolę nad zachowaniem ładowania zasobów i oddziela rejestrację zasobów od generowania HTML.

Po drugie, zawsze sprawdzaj bieżący kontroler przed rejestracją zasobów. Użyj podejścia z białą listą: zdefiniuj listę kontrolerów, na których Twój moduł wyświetla treść, i rejestruj zasoby tylko wtedy, gdy bieżący kontroler jest na tej liście. To podejście jest bardziej utrzymywalne niż czarna lista, ponieważ nowe typy stron dodane w przyszłych wersjach PrestaShop nie będą przypadkowo ładować Twoich zasobów.

Po trzecie, używaj registerStylesheet i registerJavascript zamiast addCSS i addJS. Nowsze metody pozwalają określić id dla każdego zasobu, co umożliwia szablonom i innym modułom czyste nadpisywanie lub usuwanie Twoich zasobów. Obsługują też ustawienia priorytetów kontrolujące kolejność ładowania zasobów.

Po czwarte, rozważ, czy Twój JavaScript naprawdę musi ładować się w sekcji head, czy może ładować się na dole strony z atrybutem defer. JavaScript w sekcji head blokuje renderowanie, co oznacza, że przeglądarka nie może wyświetlić żadnej treści, dopóki Twój skrypt nie zakończy pobierania i parsowania. Przeniesienie skryptów na dół lub dodanie defer eliminuje to blokowanie renderowania.

Po piąte, minimalizuj rozmiary plików zasobów. Minifikuj pliki CSS i JavaScript przed dystrybucją modułu. Usuń nieużywane reguły CSS. Unikaj dołączania całych bibliotek jak jQuery UI czy Bootstrap, gdy używasz tylko niewielkiej części ich funkcjonalności. Każdy kilobajt się liczy, gdy zasoby Twojego modułu ładują się przy tysiącach odsłon dziennie.

Mierzenie wpływu globalnych zasobów na wydajność

Aby zrozumieć, ile globalne ładowanie zasobów kosztuje Twój sklep, potrzebujesz konkretnych pomiarów. Otwórz Chrome DevTools na stronie głównej i przejdź do zakładki Sieć (Network). Przeładuj stronę i filtruj żądania po ścieżce /modules/. Policz łączną liczbę żądań i ich sumaryczny rozmiar. To jest narzut zasobów modułów na stronie, gdzie większość modułów nie wyświetla żadnej treści.

Teraz zrób to samo na stronie produktu, gdzie wiele modułów legalnie potrzebuje swoich zasobów. Porównaj liczby. W dobrze zoptymalizowanym sklepie strona produktu powinna ładować znacznie więcej zasobów modułów niż strona główna, ponieważ to tam większość modułów wyświetla swoją treść. W słabo zoptymalizowanym sklepie liczby są prawie identyczne, ponieważ każdy moduł ładuje wszystko wszędzie.

Konkretny benchmark z audytów w rzeczywistych sklepach: sklep z 35 zainstalowanymi modułami ładował 1,8 MB CSS i JavaScript modułów na stronie głównej. Po wdrożeniu warunkowego ładowania we wszystkich modułach zasoby modułów na stronie głównej spadły do 340 KB. Strona produktu, gdzie większość modułów legalnie potrzebowała swoich zasobów, przeszła z 2,1 MB do 1,4 MB. Czas ładowania strony głównej poprawił się o 1,3 sekundy, a wynik wydajności Lighthouse wzrósł z 42 do 71.

Te liczby nie są niczym niezwykłym. Globalne ładowanie zasobów jest jednym z największych źródeł niepotrzebnej wagi stron w sklepach PrestaShop, a jego naprawienie często przynosi najbardziej dramatyczne poprawy wydajności.

Jak zidentyfikować problematyczne moduły w swoim sklepie

Znalezienie modułów ładujących zasoby globalnie wymaga systematycznego podejścia. Zacznij od zakładki Sieć w DevTools, jak opisano powyżej. Dla każdego zasobu modułu znalezionego na stronie głównej zadaj sobie pytanie: czy ten moduł wyświetla jakąkolwiek treść na stronie głównej? Jeśli odpowiedź brzmi nie, ten moduł ładuje zasoby niepotrzebnie.

Innym podejściem jest użycie trybu profilowania debugowania PrestaShop. Gdy profilowanie jest włączone (poprzez ustawienie _PS_DEBUG_PROFILING_ na true w pliku config/defines.inc.php), PrestaShop wyświetla szczegółowe dane o wykonywaniu hooków na dole każdej strony. Te dane pokazują dokładnie, które moduły wykonują się na każdym hooku i jak długo to zajmuje. Każdy moduł, który wykonuje się na displayHeader lub actionFrontControllerSetMedia, ale nie wykonuje się na żadnym hooku wyświetlającym widoczne wyjście na bieżącej stronie, jest kandydatem do optymalizacji.

Możesz również sprawdzić programistycznie, analizując kod źródłowy każdego modułu. Sprawdź metody hookDisplayHeader i hookActionFrontControllerSetMedia w głównym pliku PHP modułu. Jeśli metoda zawiera wywołania addCSS, addJS, registerStylesheet lub registerJavascript bez jakichkolwiek warunkowych sprawdzeń nazwy kontrolera, ten moduł ładuje zasoby globalnie.

Dla sklepów z wieloma modułami ta ręczna weryfikacja może być czasochłonna. Praktycznym skrótem jest skupienie się najpierw na największych zasobach. Posortuj zasoby modułów w zakładce Sieć według rozmiaru i zacznij badać największe pliki. Plik JavaScript o wielkości 200 KB ładujący się globalnie jest znacznie większym problemem niż plik CSS o wielkości 3 KB, więc ustal odpowiednie priorytety.

Żądanie poprawek od deweloperów modułów

Gdy zidentyfikujesz moduł ładujący zasoby globalnie, Twoim pierwszym krokiem powinno być skontaktowanie się z deweloperem i poproszenie o poprawkę. Większość profesjonalnych deweloperów modułów jest otwarta na prośby o poprawę wydajności, szczególnie gdy możesz dostarczyć konkretne dane o wpływie.

Napisz jasną, techniczną wiadomość wyjaśniającą problem. Wspomnij, że metoda hookDisplayHeader modułu ładuje CSS i JavaScript na każdej stronie bez sprawdzania bieżącego kontrolera. Określ, które strony faktycznie potrzebują zasobów, a które ładują je niepotrzebnie. Dołącz rozmiary plików i szacowany wpływ na wydajność. Jeśli masz wyniki Lighthouse pokazujące porównanie przed i po zablokowaniu zasobów modułu, dołącz je.

Jeśli deweloper jest responsywny, zazwyczaj wyda aktualizację w ciągu kilku tygodni dodającą warunkowe ładowanie. Jeśli deweloper jest niereaktywny lub odrzuca problem, masz kilka opcji: samodzielnie wdrożyć warunkowe ładowanie, edytując kod modułu, użyć modułu wydajnościowego, który może selektywnie blokować zasoby na konkretnych stronach, lub znaleźć alternatywny moduł, który podąża za lepszymi praktykami programistycznymi.

Obejścia, gdy nie możesz modyfikować modułu

Czasami nie możesz bezpośrednio modyfikować modułu. Może być zaszyfrowany przez IonCube, może to być moduł, którego nie chcesz utrzymywać jako fork, lub moduł może nadpisywać Twoje zmiany przy każdej aktualizacji. W takich przypadkach potrzebujesz obejść.

Jednym skutecznym obejściem jest stworzenie małego niestandardowego modułu, który odpina problematyczny moduł od displayHeader i actionFrontControllerSetMedia, a następnie ponownie dodaje zasoby tylko na stronach, gdzie są potrzebne. Ten niestandardowy moduł użyłby hooka actionFrontControllerSetMedia z wysokim priorytetem (aby wykonywał się po problematycznym module) i wywoływał Media::unregisterStylesheet i Media::unregisterJavascript, aby usunąć globalnie załadowane zasoby. Następnie na stronach, gdzie zasoby są faktycznie potrzebne, ponownie by je zarejestrował.

Innym obejściem jest użycie pliku Theme.yml do nadpisywania zasobów modułów. W PrestaShop 1.7 i nowszych plik konfiguracyjny theme.yml szablonu może usuwać konkretne zasoby modułów z ładowania. To rozwiązanie na poziomie szablonu, które utrzymuje się mimo aktualizacji modułów. Jednak usuwa zasoby ze wszystkich stron, nie selektywnie, więc musiałbyś połączyć to z ponownym dodawaniem zasobów na konkretnych stronach przez niestandardowy moduł lub szablon.

Trzecia opcja, dostępna w PrestaShop 8.x, to wykorzystanie wbudowanych funkcji priorytetów i usuwania w systemie zarządzania zasobami. PrestaShop 8 ulepszył potok zasobów, dając szablonom większą kontrolę nad tym, które zasoby modułów się ładują. Sprawdź dokumentację swojego szablonu, aby uzyskać szczegółowe instrukcje dotyczące wykorzystania tych funkcji.

Niezależnie od wybranego podejścia, zawsze dokładnie testuj po wprowadzeniu zmian. Usunięcie CSS modułu może zepsuć jego wygląd wizualny, a usunięcie JavaScript może zepsuć interaktywne funkcje. Przetestuj każdy typ strony, na którym moduł powinien nadal działać poprawnie.

Zapobieganie: ocena modułów przed instalacją

Najlepszym sposobem na uniknięcie problemów z globalnym ładowaniem zasobów jest ocena modułów przed ich instalacją. Choć nie zawsze jest to możliwe, istnieją sprawdzenia, które możesz wykonać.

Jeśli moduł udostępnia sklep demonstracyjny, odwiedź go i sprawdź stronę główną za pomocą DevTools. Sprawdź, czy zasoby modułu ładują się na stronach, gdzie moduł nie wyświetla treści. Jeśli zasoby ładują się globalnie w demo, będą ładować się globalnie również w Twoim sklepie.

Jeśli masz dostęp do kodu źródłowego modułu przed zakupem (niektóre marketplace'y udostępniają podglądy kodu), sprawdź metody hookDisplayHeader i hookActionFrontControllerSetMedia. Poszukaj logiki warunkowego ładowania. Brak jakiegokolwiek sprawdzenia kontrolera jest sygnałem ostrzegawczym.

Przeczytaj recenzje modułu i forum wsparcia. Użytkownicy świadomi wydajności często zgłaszają problemy z globalnym ładowaniem zasobów w recenzjach. Jeśli wielu użytkowników skarżyło się na spowalnianie sklepu przez moduł, potraktuj tę opinię poważnie.

Na koniec weź pod uwagę ogólną jakość kodu dewelopera. Deweloperzy, którzy stosują najlepsze praktyki w jednym obszarze, zazwyczaj stosują je również w innych. Jeśli kod modułu jest czysty, dobrze udokumentowany i przestrzega standardów kodowania PrestaShop, bardziej prawdopodobne jest, że prawidłowo obsługuje ładowanie zasobów. Jeśli kod jest chaotyczny i źle ustrukturyzowany, globalne ładowanie zasobów to prawdopodobnie tylko jeden z wielu problemów.

Czy ta odpowiedź była pomocna?

Masz jeszcze pytania?

Can't find what you're looking for? Send us your question and we'll get back to you quickly.

Loading...
Back to top