WordPress: Scripts & Styles von Plugins nur laden wenn wirklich notwendig

Nicht jeder Programmierer denkt bei der Entwicklung eines Plugin (aber auch Themes) daran, ressourcenschonend mit den eingebunden Scripts & Styles umzugehen. Statt die Assets nur bei Seitenaufrufen zu laden, wo sie auch wirklich verwendet werden, werden häufig alle Datein bei jedem Seitenaufruf geladen. Das ist nicht nur schlecht für die Performance beim Leser sondern kostet auch einfach Resourcen.

Ein gutes Beispiel ist das beliebte Contact Form 7 Plugin (es gibt bessere Kontakformular-Plugins, ich bin z.B. ein Fan von HTML Forms, aber darum soll es hier nicht gehen). Installiert und aktiviert lädt es bei jedem Seitenaufruf 1,61 KB Stylesheet und 14,10 KB (+94,60 KB jQuery als Abhängigkeit) Javascript. Ein Kontaktformular haben die meisten Websites aber nur unter /kontakt/ eingebunden. Es ist also zu 99% unnötig, dafür Bandbreite und Rechenzeit zu verschwenden.

Viel geschickter wäre es, Style & Scripts nur zu laden, wenn bei einem Seitenaufruf auch der Shortcode für ein Formular [contact-form-7] eingebunden ist.

Eigentlich lässt sich das ganz einfach in WordPress umsetzen:

  1. Im Plugin wird immer die Javascript- & CSS-Datei mit den zugehörigen Abhängigkeiten mit Hilfe der Funktion wp_register_script bzw. wp_register_style registriert. Dadurch wird die Datei aber noch nicht auf der Seite eingebunden!
  2. Wird auf der Seite wirklich z.B. der Shortcode ausgeführt, ruft man wp_enqueue_script bzw. wp_enqueue_style mit dem selben Handle auf und die Datei wird in die Seiten eingebunden, vom Nutzer heruntergeladen und verwendet.

Nur wenn die enqueue-Funktion aufgerufen wird, erfolgt auch die Einbindung in die Seite.

Leider wird aber sehr häufig von den Plugin-Autoren direkt und nur enqueue-Funktion aufgerufen, weil sich damit die register-Funktion übergehe lässt oder beide Funktionen werden direkt hintereinander aufgerufen.

Ein Ausweg aus dieser Hölle muss man sich dann selber bauen. Vor allem bei Plugins die Shortcodes (oder Blöcke verwenden), könnt ihr euch mit ein paar Zeilen Code in eurer functions.php (am besten in eurem Child Theme) behelfen:

function cleanup_contact_form_7_dequeue() {
	global $post;

	if ( isset( $post->post_content ) && has_shortcode( $post->post_content, 'contact-form-7' ) ) {
		return;
	}

	wp_dequeue_script( 'contact-form-7' );
	wp_dequeue_style( 'contact-form-7' );

}
add_action( 'wp_enqueue_scripts', 'cleanup_contact_form_7_dequeue' );

Dabei wird der post_content mit der Funktion has_shortcode darauf untersucht, ob er den Shortcode contact-form-7 enthält. Ist das der Fall, wird der Funktionsaufruf mit einem return; beendet.

Gibt die Funktion has_shortcode ein false zurück, wurde der Shortcode nicht im Content gefunden und die Funktion wird weiter ausgeführt.

In diesem Fall werden die für die Einbindung geplanten Scripts und Styles von mit der Funktion wp_dequeue_script wieder aus der Warteschlange entfernt und nicht eingebunden.

Hier noch einmal die Action verallgemeinert:

function cleanup_plugin_xyz_dequeue() {
	global $post;

	if ( isset( $post->post_content ) && has_shortcode( $post->post_content, 'xyz-shortcode' ) ) {
		return;
	}

	wp_dequeue_script( 'plugin-xyz-script-handle' );
	wp_dequeue_style( 'plugin-xyz-style-handle' );

}
add_action( 'wp_enqueue_scripts', 'cleanup_plugin_xyz_dequeue' );

Tipp: Falls die dequeue Funktion keinen Effekt habt, checkt ob ihr den richtigen Handler verwendet. Ansonsten könnt ihr versuchen die Priorität eurer Action zu erhöhen indem ihr add_action einen dritten Parameter mit der Priority hinzufügt z.B. 20 (Standard ist 10): add_action( 'wp_enqueue_scripts', 'cleanup_plugin_xyz_dequeue', 20 );

Folgende Punkte müsst ihr ändern:

  • Funktionsname: cleanup_plugin_xyz_dequeue müsst ihr in add_action und als Funktionsname function ändern und muss eindeutig und einmalig sein.
  • Shortcode: Der registrierte Tag des Shortcodes xyz-shortcode in has_shortcode
  • Handler: Jeweils für Script und Style müsst ihr den ursprünglich verwendeten Handler herausfinden und in die wp_dequeue_script bzw. wp_dequeue_style Funktion einsetzen

Blocks

Statt has_shortcode für Shortcodes kann auch has_block für Gutenberg Blöcke verwendet werden:

function cleanup_block_xy_dequeue() {

	if ( has_block( 'block_type' ) ) {
		return;
	}

	wp_dequeue_script( 'my_script_handle' );
	wp_dequeue_style( 'my_style_handle' );

}
add_action( 'wp_enqueue_scripts', 'cleanup_block_xy_dequeue' );

Damit sieht die Funktion auch deutlich übersichtlicher aus, weil nicht das globale $post Objekt verwendet werden muss.

Handler herausfinden

Das ganze funktioniert natürlich bei vielen Plugins. Allerdings muss man immer den verwendeten Handler für das jeweilige Script oder Style herausfinden. Das geht am besten wenn ihr innerhalb eurere Funktion folgende Arrays euch ausgeben lässt:

print_r(wp_scripts()->queue);
print_r(wp_styles()->queue);

Die Ausgabe sieht dann z.B. wie folgt aus:

Array ( [0] => admin-bar [1] => contact-form-7 )
Array ( [0] => admin-bar [1] => wp-block-library [2] => contact-form-7 ) 

In diesem Fall ist wird der Handler contact-form-7 jeweils einmal für das Script und einmal für den Style verwendet. Leider gibt es aber kein Standard für die Benennung des Handlers, deswegen bringt Raten leider meistens nicht den gewünschten Erfolg.

Fazit

Die vielzahl an Plugins und Themes machen WordPress extrem mächtig aber leider ist nicht jedes Theme/Plugin gut programmiert oder wird seit Jahren nicht mehr gewartet. Will man nicht direkt ein Fork, was dank GPL ja möglich ist, pflegen, muss man sich mit solchen Tricks behelfen.

16 Kommentare zu “WordPress: Scripts & Styles von Plugins nur laden wenn wirklich notwendig

  1. Siehst du einen Nachteil darin, das Ganze nach template_redirect vorzuverlagern und die Funktion, die Skripte/Styles registriert/enqueued, gleich ganz aus wp_enqueue_scripts zu entfernen?

    Also für CF7 z.B.:

    add_action( 'template_redirect', function () {
    	if ( ! has_shortcode( $GLOBALS['post']->post_content, 'contact-form-7' ) ) {
    		remove_action( 'wp_enqueue_scripts', 'wpcf7_do_enqueue_scripts', 10, 0 );
    	}
    });
    1. Gerade mal getestet: Funktioniert ebenfalls (wenn man if ( ! has_shortcode(... verwendet, ich habe es bei deinem Kommentar angepasst). Allerdings muss man dann im Sourcecode des Plugins nach dem entsprechenden Funktionsnamen suchen. Den Handler herauszufinden ist glaube ich einfacher. Auf jeden Fall aber auch eine gute Lösung an die ich bisher nicht gedacht habe. Ist wahrscheinlich ein bisschen performanter.

      Speziell für Contact Form 7 gibt es auch die Möglichkeit mit zwei Filtern zu arbeiten: https://gist.github.com/cyberwani/38f945007e83600ca0bf0bc48d99361b Ich wollte es aber allgemeiner halten und habe deswegen auf die wp_enqueue_scripts Action gesetzt.

      1. Den Handler herauszufinden ist glaube ich einfacher.

        Stimmt, macht Sinn.

        Ist wahrscheinlich ein bisschen performanter.

        Das habe ich mich tatsächlich auch gefragt, war aber zu faul/müde, es richtig zu testen; und in der Praxis wäre der Unterschied (wenn es denn einen gibt) sicher eh nicht relevant.

        Schön erklärt, btw! 👍

  2. Danke für diesen Beitrag, der fasst das Thema gut zusammen!

    Eine Sache ist mir beim Lesen aufgefallen: du schreibst, dass der Standard-Wert der Priorität für die Actions 20 ist. Ich kenne eigentlich 10 als Standard-Wert – hab ich da was verpasst? 🙂

  3. Hey!

    CF7 hat zu dem Thema auch selber einen Artikel in englischer Sprache:
    https://contactform7.com/loading-javascript-and-stylesheet-only-when-it-is-necessary/

    Ein Fix nur für sein eigenes Projekt ist immer gut. Mehr Leuten würde es helfen wenn das Plugin/Theme selber angepasst wird und dann für alle Websites eine Verbesserung entsteht.
    Hast du mal versucht Issues bei den Herstellern selber aufzumachen? Die aktuelle Debatte sollte dem Wunsch etwas Aufmerksamkeit sichern.

    Es grüßt
    derRALF

    1. Contact Form 7 hat mir hier hauptsächlich als Beispiel gedient. Selber setzte ich es nur bei einem Projekt ein was ich zwar betreue aber dort nicht austauschen darf „Never change a running system“. Leider gibt es für Contact Form 7 keine offizielle Github Repository sonst hätte ich da wohl mal ein Pull Request erstellt.

      Ich glaube die Plugin Autoren haben einfach zu viel Angst daran was zu ändern, weil sie ggf. viele Seiten zerschießen. Vor allem bei Formular Plugins ist mir in der Vergangenheit mehrfach aufgefallen, dass sie Styles & Scripts überall laden. Vermutlich weil die Nutzer das Formular nicht nur in Beiträgen/Seiten einbinden sondern an allen möglichen Stellen.

  4. Danke Johannes; ich hab’s ausprobiert und es klappt gut. Wichtig ist nur, den Cache zu leeren. Ich hatte mich erst gewundert, dass kein Effekt zu sehen war. 😉
    Übrigens:
    Bei der Suche nach weiteren Ressourcenfressern bin ich auf die Funktion COVERAGE in Chrome gestoßen. Das Ergebnis war für mich ein leichter Schock. Von insgesamt 2,3MB meiner Startseite verbrauchen die „Top Ten“ der geladenen js-Dateien 1,3MB „unused Bytes“. Und diese Scripts liegen ausschließlich im Pfad /wp-includes/js/dist/. Da kannst du scripts minimieren und kombinieren – den Müll wirst du dadurch auch nicht los. Bei allen nötigen Bemühungen, performante und effektive Themes zu erstellen – aus meiner Sicht liegt ein großes Problem im Core.
    Oder sehe ich das etwas falsch?

    1. 2,3mb Scripts ist schon sehr sehr viel. Die Scripts aus /wp-includes/ werden i.d.R. nur geladen, wenn sie von Plugins oder dem Theme angefordert werden. Am besten mal alle Plugins deaktivieren die du nicht unbedingt benötigst und schauen ob es dann besser ist.

  5. Jaaaa,
    nach langem Suchen und mit schrittweisem De- und Aktivieren aller Plugins habe ich den Verursacher gefunden: „Event Post“ von N.O.U.S. allein verwendet über 700kB JS und verursacht zusätzlich fast 1,4MB ungenutzte JS-Aufrufe. Es ist zwar komfortabel, aber dieser Verbrauch ist für mich ein ko-Kriterium.
    Mich hat der Pfad in die Irre geführt.
    Danke für den Tip

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.