JavaScript w WordPressie to piekło… i oto dlaczego
Opublikowany: 2022-05-05Stos deweloperski WordPressa bardzo się zmienił w ostatnich latach. Od czasu pojawienia się Gutenberga rola JavaScript w naszym ulubionym CMS jest ważniejsza niż kiedykolwiek. W tym blogu omówiliśmy już szczegółowo korzyści, jakie to pociąga za sobą dla programistów (by wymienić kilka przykładów, rozmawialiśmy o rozszerzeniach dla Gutenberga, poradach dotyczących TypeScript i React, narzędziach programistycznych, przykładach wtyczek i nie tylko), ale każdy historia ma swoją ciemną stronę … io tym dzisiaj porozmawiamy.
W dzisiejszym poście podzielę się z wami 3 głównymi problemami , z którymi, jako twórca wtyczek do WordPressa, może się zmierzyć w przyszłości. Zabawne jest to, że każdy z nich ma innego winowajcę: sam WordPress, innych programistów lub Ciebie. A więc zaczynamy: najczęstsze problemy związane z JavaScriptem, jakie możesz napotkać i co możesz zrobić, aby je uniknąć/naprawić.
#1 Wtyczki WPO, które zepsują Twoją wtyczkę i witrynę
Zacznijmy od problemu, który był źródłem wielu biletów w Nelio: wtyczki WPO.
Jestem pewien, że przeczytałeś wiele artykułów i postów na blogu, które podkreślają znaczenie posiadania lekkiej witryny, która szybko się ładuje. To znaczy, pisaliśmy o tym również kilkakrotnie! Wśród wskazówek, które zwykle dają, znajdziesz takie rzeczy, jak przejście na lepszego dostawcę hostingu, korzystanie z wtyczek pamięci podręcznej i sieci CDN, aktualizowanie serwera i WordPressa lub (i tu pojawia się pierwszy problem) zainstalowanie wtyczki WPO. Niektóre przykłady tych ostatnich obejmują:
- Całkowita pamięć podręczna W3 z ponad milionem instalacji
- SiteGround Optimizer z ponad 1 milionem instalacji (z których, nawiasem mówiąc, używamy w naszej witrynie)
- Poprawki i optymalizacje WordPress WPO autorstwa naszego dobrego przyjaciela Fernando Tellado
Wtyczki te obiecują przyspieszyć działanie Twojej witryny dzięki serii rozsądnych optymalizacji, z których ogólnie „może skorzystać każda witryna WordPress”. Te optymalizacje obejmują:
- Usuwanie z kolejki niepotrzebnych skryptów w interfejsie, takich jak emotikony lub Dashicons
- Buforowanie stron i zapytań do bazy danych
- Zmniejszenie ilości informacji zawartych w nagłówku
- Łączenie i minimalizowanie skryptów JavaScript i stylów CSS
- Minimalizowanie kodu HTML
- Usunięcie argumentu zapytania o wersję z adresów URL zasobów statycznych
- Odraczanie skryptów JavaScript i/lub ładowanie ich asynchronicznie
- itp
Jak już mówiłem, tego typu optymalizacje mogą być generalnie korzystne. Ale z naszego doświadczenia wynika, że wszystkie optymalizacje JS w witrynie WordPress powodują więcej problemów , przez co ich rzekome ulepszenia stają się bezużyteczne. Prawdziwe przykłady, które widziałem na przestrzeni lat to:
- Łączenie skryptów. Im mniej skryptów ma żądać Twoja przeglądarka, tym lepiej. Dlatego warto połączyć wszystkie skrypty w jeden. Może to jednak być problematyczne. Ogólnie rzecz biorąc, jeśli skrypt JavaScript ulegnie awarii, jego wykonanie zostanie zakończone, a błąd zostanie zgłoszony w konsoli przeglądarki. Ale tylko wykonanie tego skryptu jest zatrzymywane; Twoje inne skrypty będą działać normalnie. Ale jeśli połączysz je wszystkie… cóż, jak tylko jeden skrypt ulegnie awarii, inne (w tym może twój) nie będą działać, a użytkownicy pomyślą, że to twoja wtyczka nie działa zgodnie z oczekiwaniami.
- Skrypty minifikacyjne. Widziałem procesy minifikacji, które, wierzcie lub nie, łamały wyrażenia regularne i powodowały, że skrypty JS miały błędy składniowe. Jasne, minęło trochę czasu od ostatniego spotkania z tym, ale… :-/
- Argumenty zapytania. Kiedy umieszczasz skrypt w kolejce w WordPressie, możesz to zrobić za pomocą jego numeru wersji (który, nawiasem mówiąc, został prawdopodobnie automatycznie wygenerowany przez
@wordpress/scripts
). Numery wersji są niezwykle pomocne: jeśli zaktualizujesz wtyczkę, a skrypt się zmieni, ten nowy numer wersji zagwarantuje, że wszyscy odwiedzający zobaczą inny adres URL, a zatem ich przeglądarki zażądają nowej wersji. Niestety, jeśli wtyczka WPO usunie ciąg zapytania, odwiedzający mogą nie zdawać sobie sprawy, że skrypt się zmienił i będą używać buforowanej kopii tego skryptu… co może, ale nie musi, spowodować niezamierzone konsekwencje. Cholerny!
Kompletna katastrofa, prawda? Ale poczekaj, aż usłyszysz następny:
Odroczenie skryptów
W Nelio wdrożyliśmy wtyczkę A/B Testing, za pomocą której można śledzić odwiedzających i odkrywać, który projekt i treść uzyskuje najwięcej konwersji. Jak możesz sobie wyobrazić, nasz skrypt śledzący wygląda podobnie do tego:
window.NelioABTesting = window.NelioABTesting || {}; window.NelioABTesting.init = ( settings ) => { // Add event listeners to track visitor events... console.log( settings ); };
Oznacza to, że udostępnia funkcję init
, którą musimy wywołać, aby powiadomić skrypt o aktualnie uruchomionych testach. Aby wywołać tę metodę, umieszczamy w kolejce skrypt wbudowany w PHP w następujący sposób:
function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTesting.init( %s );', wp_json_encode( nab_get_tracking_settings() ) ) ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );
co skutkuje następującymi tagami HTML:
<head> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
Ale co się stanie, gdy twoja wtyczka WPO doda atrybut defer
do naszego skryptu?
<head> ... <script defer <!-- This delays its loading... --> type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
Cóż, skrypt jest teraz odroczony… co oznacza, że poprzedni fragment jest odpowiednikiem tego:
<head> ... <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> </body> </html>
w rezultacie nab-tracking-js
nie jest już ładowany, kiedy powinien, a zatem skrypt wbudowany, który pojawia się po nim i na nim polega, po prostu się nie powiedzie: nab-tracking-js-after
używa NelioABTesting.init
, który , dzięki dyrektywie defer
, nie jest jeszcze dostępny. Okropny!
Rozwiązanie
Najskuteczniejsze rozwiązanie jest jasne: powiedz swoim użytkownikom, aby wyłączyli optymalizację skryptów i zadzwoń do tego dnia. W końcu zarządzanie zależnościami w JavaScript jest na ogół niezwykle skomplikowane (zwłaszcza jeśli używamy dyrektyw defer
i async
), a WordPress nie jest wyjątkiem. Wystarczy spojrzeć na tę 12-letnią dyskusję na ten temat!
Ale jeśli nie jest to możliwe (a wiem, że nie jest), radzę zrobić to samo, co zrobiliśmy: pozbyć się metody init
i odwrócić obowiązki zwykłych i wbudowanych skryptów. Oznacza to, że dodaj skrypt wbudowany przed zwykłym skryptem i użyj go do zdefiniowania zmiennej globalnej z wymaganymi ustawieniami:
function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTestingSettings = %s;', wp_json_encode( nab_get_tracking_settings() ) ), 'before' ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );
aby wynikowy kod HTML wyglądał tak:
<head> ... <script type="text/javascript" > NelioABTestingSettings = {"experiments":[...],...}; </script> <script type="text/javascript" src="https://.../dist/tracking.js" ></script> ... </head> <body> ...
więc nie ma znaczenia, czy wykonanie zewnętrznego skryptu jest opóźnione, czy nie — zawsze pojawi się po skrypcie wbudowanym, spełniając w ten sposób zależność między nimi.
Na koniec, jeśli chcesz się upewnić, że nikt nie będzie modyfikował twoich ustawień, zadeklaruj zmienną jako const
i zamroź jej wartość za pomocą Object.freeze
:
... sprintf( 'const NelioABTestingSettings = Object.freeze( %s );', wp_json_encode( nab_get_tracking_settings() ) ), ...
który jest obsługiwany przez wszystkie nowoczesne przeglądarki.
#2 Zależności w WordPressie, które mogą, ale nie muszą działać…
Zarządzanie zależnościami również może być problematyczne w WordPressie, zwłaszcza gdy mówimy o wbudowanych skryptach WordPressa. Pozwól mi wyjaśnić.
Wyobraź sobie na przykład, że tworzymy małe rozszerzenie dla Gutenberga, jak wyjaśniliśmy tutaj. Kod źródłowy naszej wtyczki prawdopodobnie będzie zawierał kilka instrukcji import
, takich jak te:
import { RichTextToolbarButton } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { registerFormatType } from '@wordpress/rich-text'; // ...
Kiedy ten kod źródłowy JS zostanie przetransponowany, Webpack (lub narzędzie, którego używasz) spakuje wszystkie zależności i twój własny kod źródłowy w pojedynczym pliku JS. Jest to plik, który później dodasz do kolejki z WordPressa, aby wszystko działało zgodnie z oczekiwaniami.
Jeśli użyłeś @wordpress/scripts
do utworzenia takiego pliku, niektóre zależności nie zostaną uwzględnione w pliku wyjściowym, ponieważ wbudowany proces zakłada, że pakiety będą dostępne w zasięgu globalnym. Oznacza to, że poprzednie importy zostaną przeniesione na coś podobnego do tego:
const { RichTextToolbarButton } = window.wp.blockEditor; const { __ } = window.wp.i18n; const { registerFormatType } = window.wp.richText; // ...
Aby upewnić się, że nie przegapisz żadnej z zależności twojego skryptu, @wordpress/scripts
nie tylko przetranspiluje twój kod JS, ale także wygeneruje plik PHP z jego zależnościami od WordPressa:
<?php return array( 'dependencies' => array('wp-block-editor','wp-i18n','wp-rich-text'), 'version' => 'a12850ccaf6588b1e10968124fa4aba3', );
Całkiem fajnie, co? Więc w czym problem? Cóż, te pakiety WordPress są w ciągłym rozwoju i dość często się zmieniają, dodając nowe funkcje i ulepszenia. Dlatego, jeśli opracujesz swoją wtyczkę przy użyciu najnowszej wersji WordPressa, możesz przypadkowo użyć funkcji lub funkcji dostępnych w tej najnowszej wersji (a zatem wszystko działa tak, jak powinno), ale nie w „starszych” wersjach WordPressa…
Jak możesz powiedzieć?
Rozwiązanie
Moja rada tutaj jest bardzo prosta: rozwijaj swoje wtyczki korzystając z najnowszej wersji WordPressa, ale testuj swoje wydania na starszych wersjach. W szczególności sugeruję przetestowanie wtyczki przynajmniej z minimalną wersją WordPressa, którą ma obsługiwać wtyczka. Minimalna wersja, którą znajdziesz w pliku readme.txt
swojej wtyczki:
=== Nelio Content === ... Requires PHP: 7.0 Requires at least: 5.4 Tested up to: 5.9 ...
Przełączanie się z jednej wersji WordPress na inną jest tak proste, jak uruchomienie następującego polecenia WP CLI:
wp core update --version=5.4 --force
#3 Funkcje strzałek są trudniejsze niż myślisz
Na koniec podzielę się jednym z ostatnich problemów, które napotkałem zaledwie kilka dni temu i który doprowadzał mnie do szału. W skrócie, mieliśmy plik JavaScript podobny do tego:
import domReady from '@wordpress/dom-ready'; domReady( () => [ ...document.querySelectorAll( '.nelio-forms-form' ) ] .forEach( initForm ) ); // Helpers // ------- const initForm = ( form ) => { ... } // ...
który inicjuje Twoje formularze Nelio na interfejsie użytkownika. Scenariusz jest dość prosty, prawda? Definiuje funkcję anonimową, która jest wywoływana, gdy DOM jest gotowy. Ta funkcja używa funkcji pomocniczej (strzałki) o nazwie initForm
. Jak się okazuje, taki prosty przykład może się zawiesić! Ale tylko w określonych okolicznościach (np. jeśli skrypt został „zoptymalizowany” przez wtyczkę WPO przy użyciu atrybutu defer
).
Oto jak JS wykonuje poprzedni skrypt:
- Zdefiniowano funkcję anonimową wewnątrz
domReady
-
domReady
działa - Jeśli DOM nie jest jeszcze gotowy (i zwykle nie jest, gdy skrypt jest ładowany),
domReady
nie uruchamia funkcji zwrotnej. Zamiast tego po prostu śledzi to, aby móc zadzwonić później - JavaScript kontynuuje parsowanie pliku i ładuje funkcję
initForm
- Gdy DOM jest gotowy, w końcu wywoływana jest funkcja wywołania zwrotnego
A co, jeśli do czasu trzeciego kroku DOM jest gotowy, a zatem domReady
wywołuje bezpośrednio funkcję anonimową? Cóż, w takim przypadku skrypt wywoła niezdefiniowany błąd, ponieważ initForm
nadal jest undefined
.
W rzeczywistości najciekawsze w tym wszystkim jest to, że te dwa rozwiązania są równoważne:
domReady( aux ); const aux = () => {};
domReady( () => aux() ); const aux = () => {}
linter JavaScript zwróci błąd tylko na pierwszym, ale nie na ostatnim.
Rozwiązanie
Możliwe są dwa rozwiązania: albo definiujesz funkcję pomocniczą za pomocą słowa kluczowego function
i zapominasz o funkcji strzałki, albo przenosisz instrukcję domReady
na koniec, po zdefiniowaniu wszystkich funkcji pomocniczych:
domReady( aux ); function aux() { // ... }
const aux = () => { // ... }; domReady( aux );
Jeśli zastanawiasz się, dlaczego pierwsze rozwiązanie działa, jeśli najwyraźniej jest równoważne oryginalnemu, które mieliśmy, to wszystko dotyczy tego, jak działa przenoszenie JavaScript. Krótko mówiąc, w JavaScript możesz użyć funkcji (zdefiniowanej za pomocą function
) przed jej definicją, ale nie możesz zrobić tego samego ze zmiennymi i stałymi (a zatem funkcjami strzałek).
W podsumowaniu
W JavaScript jest wiele rzeczy, które mogą pójść nie tak. Na szczęście wszystkie mają rozwiązanie, zwłaszcza jeśli zwracamy uwagę na to, co robimy. Mam nadzieję, że dzisiaj nauczyłeś się czegoś nowego i ufam, że dzięki błędom i błędom, które popełniłem w przeszłości, będziesz w stanie uniknąć ich cierpienia we własnym ciele w przyszłości.
Polecane zdjęcie Iana Stauffera na Unsplash.