JavaScript auf WordPress ist die Hölle … und hier ist der Grund
Veröffentlicht: 2022-05-05Der WordPress-Entwicklungsstack hat sich in den letzten Jahren stark verändert. Seit der Ankunft von Gutenberg ist die Rolle von JavaScript in unserem Lieblings-CMS wichtiger denn je. In diesem Blog haben wir bereits ausführlich über die Vorteile gesprochen, die dies für Entwickler mit sich bringt (um nur einige Beispiele zu nennen, wir haben über Erweiterungen für Gutenberg, Ratschläge zu TypeScript und React, Entwicklungstools, Plugin-Beispiele und mehr gesprochen), aber alle Geschichte hat ihre dunkle Seite … und darüber werden wir heute hier sprechen.
Im heutigen Beitrag werde ich mit Ihnen die 3 Hauptprobleme teilen, mit denen Sie als WordPress-Plugin-Entwickler in Zukunft konfrontiert sein könnten. Und das Lustige daran ist, dass jeder von ihnen einen anderen Schuldigen hat: entweder WordPress selbst, andere Entwickler oder Sie selbst. Also los geht's: die häufigsten JavaScript-Kopfschmerzen, auf die Sie stoßen könnten, und was Sie tun können, um sie zu vermeiden/beheben.
Nr. 1 WPO-Plugins, die Ihr Plugin und Ihre Website beschädigen
Beginnen wir mit dem Problem, das hier bei Nelio viele Tickets verursacht hat: WPO-Plugins.
Ich bin mir sicher, dass Sie viele Artikel und Blogposts gelesen haben, die die Bedeutung einer schlanken, schnell ladenden Website umreißen. Ich meine, wir haben auch schon mehrfach darüber geschrieben! Unter den Tipps, die sie normalerweise geben, finden Sie Dinge wie den Wechsel zu einem besseren Hosting-Anbieter, die Verwendung von Cache-Plugins und CDNs, die Aktualisierung Ihres Servers und von WordPress oder (und hier kommt das erste Problem) die Installation eines WPO-Plugins. Einige Beispiele für Letzteres sind:
- W3 Total Cache mit über 1 Million Installationen
- SiteGround Optimizer mit ebenfalls über 1 Million Installationen (eine davon verwenden wir übrigens auf unserer Website)
- WordPress WPO Tweaks & Optimizations von unserem guten Freund Fernando Tellado
Diese Plugins versprechen, Ihre Website durch eine Reihe sinnvoller Optimierungen zu beschleunigen, von denen im Allgemeinen „jede WordPress-Website profitieren kann“. Diese Optimierungen beinhalten:
- Unnötige Skripte im Frontend aus der Warteschlange entfernen, wie Emojis oder Dashicons
- Zwischenspeichern von Seiten und Datenbankabfragen
- Reduzierung der im Header enthaltenen Informationen
- Kombinieren und Minimieren von JavaScript-Skripten und CSS-Stilen
- Minimierung von HTML
- Entfernen des Arguments für die Versionsabfrage aus den URLs Ihrer statischen Assets
- JavaScript-Skripte zurückstellen und/oder asynchron laden
- etc
Wie ich bereits sagte, können diese Arten von Optimierungen im Allgemeinen von Vorteil sein. Aber nach unserer Erfahrung führen alle JS-Optimierungen auf einer WordPress-Website tendenziell zu mehr Problemen , wodurch ihre vermeintlichen Verbesserungen nutzlos werden. Echte Beispiele, die ich im Laufe der Jahre gesehen habe, sind:
- Skripte kombinieren. Je weniger Skripte Ihr Browser anfordern muss, desto besser. Deshalb ist es sinnvoll, alle Skripte zu einem zusammenzufassen. Dies kann jedoch problematisch sein. Wenn ein JavaScript-Skript abstürzt, wird seine Ausführung im Allgemeinen beendet und der Fehler wird in der Konsole Ihres Browsers gemeldet. Aber nur die Ausführung dieses Skripts wird gestoppt; Ihre anderen Skripte werden normal ausgeführt. Aber wenn Sie sie alle kombinieren … Nun, sobald ein Skript fehlschlägt, werden die anderen Skripte (einschließlich vielleicht Ihres) nicht ausgeführt, und Ihre Benutzer werden denken, dass es Ihr Plugin ist, das nicht wie erwartet funktioniert.
- Minimierung von Skripten. Ich habe einige Minifizierungsprozesse gesehen, die, ob Sie es glauben oder nicht, reguläre Ausdrücke beschädigt haben und zu JS-Skripten mit Syntaxfehlern geführt haben. Sicher, es ist schon eine Weile her, seit ich dem das letzte Mal begegnet bin, aber… :-/
- Argumente abfragen. Wenn Sie ein Skript in WordPress einreihen, können Sie dies mit seiner Versionsnummer tun (die übrigens wahrscheinlich automatisch von
@wordpress/scripts
generiert wurde). Versionsnummern sind äußerst hilfreich: Wenn Sie Ihr Plugin aktualisieren und Ihr Skript geändert wird, garantiert diese neue Versionsnummer, dass alle Besucher eine andere URL sehen und daher ihre Browser die neue Version anfordern. Wenn ein WPO-Plugin die Abfragezeichenfolge entfernt, bemerken Ihre Besucher leider möglicherweise nicht, dass sich das Skript geändert hat, und sie verwenden eine zwischengespeicherte Kopie des Skripts … was zu unbeabsichtigten Folgen führen kann oder auch nicht. Verdammt!
Eine komplette Katastrophe, nicht wahr? Aber warte, bis du den nächsten hörst:
Zurückstellen von Skripten
Bei Nelio haben wir ein A/B-Test-Plugin implementiert, mit dem Sie Ihre Besucher verfolgen und herausfinden können, welches Design und welche Inhalte die meisten Conversions erzielen. Wie Sie sich vorstellen können, sieht unser Tracking-Skript ungefähr so aus:
window.NelioABTesting = window.NelioABTesting || {}; window.NelioABTesting.init = ( settings ) => { // Add event listeners to track visitor events... console.log( settings ); };
Das heißt, es legt eine init
-Funktion offen, die wir aufrufen müssen, um das Skript über die derzeit laufenden Tests zu informieren. Um diese Methode aufzurufen, stellen wir ein Inline-Skript in PHP wie folgt in die Warteschlange:
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' );
was zu den folgenden HTML-Tags führt:
<head> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
Aber was passiert, wenn Ihr WPO-Plugin das Attribut defer
zu unserem Skript hinzufügt?
<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> ...
Nun, das Skript ist jetzt zurückgestellt … was bedeutet, dass das vorherige Snippet diesem entspricht:
<head> ... <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> </body> </html>
und als Ergebnis wird nab-tracking-js
nicht mehr geladen, wenn es sollte, und daher wird das Inline-Skript, das danach kommt und darauf angewiesen ist, einfach fehlschlagen: nab-tracking-js-after
verwendet NelioABTesting.init
which , ist dank der defer
Direktive noch nicht verfügbar. Abscheulich!
Lösung
Die effektivste Lösung ist klar: Sagen Sie Ihren Benutzern, dass sie die Skriptoptimierung deaktivieren und Schluss machen sollen. Schließlich ist das Abhängigkeitsmanagement in JavaScript im Allgemeinen extrem kompliziert (insbesondere wenn wir defer
und async
Direktiven verwenden), und WordPress ist da keine Ausnahme. Schauen Sie sich einfach diese 12 Jahre alte Diskussion zu diesem Thema an!
Aber wenn das nicht machbar ist (und ich weiß, dass es nicht geht), empfehle ich Ihnen, dasselbe zu tun wie wir: die init
-Methode loszuwerden und die Verantwortlichkeiten Ihrer regulären und Inline-Skripte umzukehren. Fügen Sie also vor dem regulären Skript ein Inline-Skript hinzu und verwenden Sie es, um eine globale Variable mit den erforderlichen Einstellungen zu definieren:
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' );
sodass das resultierende HTML so aussieht:
<head> ... <script type="text/javascript" > NelioABTestingSettings = {"experiments":[...],...}; </script> <script type="text/javascript" src="https://.../dist/tracking.js" ></script> ... </head> <body> ...
Daher spielt es keine Rolle, ob die Ausführung des externen Skripts verzögert wird oder nicht – es wird immer nach dem Inline-Skript erscheinen, wodurch die Abhängigkeit zwischen ihnen erfüllt wird.
Wenn Sie schließlich sicherstellen möchten, dass niemand Ihre Einstellungen ändert, deklarieren Sie die Variable als const
und frieren Sie ihren Wert mit Object.freeze
:
... sprintf( 'const NelioABTestingSettings = Object.freeze( %s );', wp_json_encode( nab_get_tracking_settings() ) ), ...
die von allen modernen Browsern unterstützt wird.
#2 Abhängigkeiten in WordPress, die funktionieren oder nicht funktionieren können…
Das Abhängigkeitsmanagement kann in WordPress ebenfalls problematisch sein, insbesondere wenn es um die integrierten Skripte von WordPress geht. Lassen Sie mich erklären.
Stellen Sie sich zum Beispiel vor, dass wir eine kleine Erweiterung für Gutenberg erstellen, wie wir es hier erklärt haben. Der Quellcode unseres Plugins wird wahrscheinlich einige import
wie diese enthalten:
import { RichTextToolbarButton } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { registerFormatType } from '@wordpress/rich-text'; // ...
Wenn dieser JS-Quellcode transpiliert wird, packt Webpack (oder das von Ihnen verwendete Tool) alle Abhängigkeiten und Ihren eigenen Quellcode in eine einzige JS-Datei. Dies ist die Datei, die Sie später von WordPress in die Warteschlange stellen, damit alles so funktioniert, wie Sie es erwarten.
Wenn Sie @wordpress/scripts
zum Erstellen einer solchen Datei verwendet haben, werden einige der Abhängigkeiten nicht in die Ausgabedatei aufgenommen, da der integrierte Prozess davon ausgeht, dass die Pakete im globalen Bereich verfügbar sind. Das bedeutet, dass die vorherigen Importe in etwas Ähnliches transpiliert werden:
const { RichTextToolbarButton } = window.wp.blockEditor; const { __ } = window.wp.i18n; const { registerFormatType } = window.wp.richText; // ...
Um sicherzustellen, dass Sie keine der Abhängigkeiten Ihres Skripts verpassen, @wordpress/scripts
nicht nur Ihren JS-Code, sondern generiert auch eine PHP-Datei mit ihren WordPress-Abhängigkeiten:
<?php return array( 'dependencies' => array('wp-block-editor','wp-i18n','wp-rich-text'), 'version' => 'a12850ccaf6588b1e10968124fa4aba3', );
Ziemlich ordentlich, oder? Also, was ist das Problem? Nun, diese WordPress-Pakete werden ständig weiterentwickelt und ändern sich häufig, indem sie neue Funktionen und Verbesserungen hinzufügen. Wenn Sie also Ihr Plugin mit der neuesten Version von WordPress entwickeln, könnten Sie versehentlich Funktionen oder Features verwenden, die in dieser neuesten Version verfügbar sind (und daher funktioniert alles so, wie es sollte), aber nicht in „älteren“ WordPress-Versionen…
Woran erkennst du das?
Lösung
Mein Rat hier ist sehr einfach: Entwickeln Sie Ihre Plugins mit der neuesten WordPress-Version, aber testen Sie Ihre Releases auf älteren Versionen. Insbesondere würde ich vorschlagen, dass Sie Ihr Plugin zumindest mit der WordPress-Mindestversion testen, die Ihr Plugin unterstützen soll. Eine Mindestversion finden Sie in der readme.txt
Ihres Plugins:
=== Nelio Content === ... Requires PHP: 7.0 Requires at least: 5.4 Tested up to: 5.9 ...
Der Wechsel von einer WordPress-Version zu einer anderen ist so einfach wie das Ausführen des folgenden WP-CLI-Befehls:
wp core update --version=5.4 --force
#3 Pfeilfunktionen sind kniffliger als Sie denken
Lassen Sie mich zum Schluss eines der neuesten Probleme mitteilen, auf die ich erst vor ein paar Tagen gestoßen bin und das mich verrückt gemacht hat. Kurz gesagt, wir hatten eine JavaScript-Datei ähnlich dieser:
import domReady from '@wordpress/dom-ready'; domReady( () => [ ...document.querySelectorAll( '.nelio-forms-form' ) ] .forEach( initForm ) ); // Helpers // ------- const initForm = ( form ) => { ... } // ...
die Ihre Nelio-Formulare auf dem Front-End initialisiert. Das Skript ist ziemlich einfach, nicht wahr? Es definiert eine anonyme Funktion, die aufgerufen wird, wenn das DOM bereit ist. Diese Funktion verwendet eine Hilfsfunktion (Pfeil) namens initForm
. Nun, wie sich herausstellt, kann ein so einfaches Beispiel abstürzen! Aber nur unter bestimmten Umständen (dh wenn das Skript von einem WPO-Plugin mit dem defer
Attribut „optimiert“ wurde).
So führt JS das vorherige Skript aus:
- Die anonyme Funktion in
domReady
ist definiert -
domReady
läuft - Wenn das DOM noch nicht bereit ist (und normalerweise nicht, wenn ein Skript geladen wird), führt
domReady
die Callback-Funktion nicht aus. Stattdessen verfolgt es es einfach, damit es es später aufrufen kann - JavaScript fährt mit dem Parsen der Datei fort und lädt die
initForm
Funktion - Sobald das DOM fertig ist, wird schließlich die Callback-Funktion aufgerufen
Was ist nun, wenn das DOM zum Zeitpunkt des dritten Schritts fertig ist und domReady
daher die anonyme Funktion direkt aufruft? Nun, in diesem Fall löst das Skript einen undefinierten Fehler aus, da initForm
immer noch undefined
ist.
Tatsächlich ist das Seltsamste an all dem, dass diese beiden Lösungen gleichwertig sind:
domReady( aux ); const aux = () => {};
domReady( () => aux() ); const aux = () => {}
Der JavaScript-Linter gibt nur beim ersten einen Fehler aus, aber nicht beim letzten.
Lösung
Es gibt zwei mögliche Lösungen: Entweder Sie definieren die Hilfsfunktion mit dem Schlüsselwort function
und vergessen die Pfeilfunktion, oder Sie verschieben die domReady
Anweisung an das Ende, nachdem alle Hilfsfunktionen definiert wurden:
domReady( aux ); function aux() { // ... }
const aux = () => { // ... }; domReady( aux );
Wenn Sie sich fragen, warum die erste Lösung funktioniert, wenn sie anscheinend der ursprünglichen entspricht, die wir hatten, dreht sich alles darum, wie das Heben von JavaScript funktioniert. Kurz gesagt, in JavaScript können Sie eine Funktion (definiert mit function
) vor ihrer Definition verwenden, aber Sie können dasselbe nicht mit Variablen und Konstanten (und daher Pfeilfunktionen) tun.
Zusammenfassend
Es gibt eine Menge Dinge, die in JavaScript schief gehen können. Glücklicherweise haben sie alle eine Lösung, besonders wenn wir darauf achten, was wir tun. Ich hoffe, Sie haben heute etwas Neues gelernt, und ich vertraue darauf, dass Sie dank der Fehler und Fehler, die ich in der Vergangenheit gemacht habe, in Zukunft vermeiden können, sie in Ihrem eigenen Fleisch zu erleiden.
Beitragsbild von Ian Stauffer auf Unsplash.