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' );Code-Sprache: PHP (php)

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' );Code-Sprache: PHP (php)

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' );Code-Sprache: PHP (php)

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);Code-Sprache: PHP (php)

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 ) Code-Sprache: PHP (php)

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.

25 Reaktionen zu “WordPress: Scripts & Styles von Plugins nur laden wenn wirklich notwendig

  1. Caspar sagt:

    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. Johannes sagt:

      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. Caspar sagt:

        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! ?

        1. Johannes sagt:

          Danke 🙂

  2. Marc sagt:

    Perfekt! Danke für die Inspiration. Habe ich gerade genutzt, um unnütze Google-Fonts eines Plugins zu entfernen.

  3. Jessica sagt:

    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? 🙂

    1. Johannes sagt:

      Da hast du natürlich recht. Direkt angepasst.

  4. Drivingralle sagt:

    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. Johannes sagt:

      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.

  5. Wolfgang sagt:

    Danke für den Artikel.
    Meine Frage aus Sicht eines NUR-Anwenders:
    Funktioniert das auch, wenn durch ein Plugin (z.B. Fast Velocity Minify) css-Minimierung erfolgt?

    1. Johannes sagt:

      Hallo Wolfgang,

      ja das sollte auch mit einem Plugin wie Fast Velocity Minify funktionieren.

  6. Wolfgang sagt:

    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. Johannes sagt:

      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.

  7. Wolfgang sagt:

    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

    1. Johannes sagt:

      Gerne, sehe du hast auch schon die Plugin-Autor benachricht. Sehr gut 😉

  8. Hallo,

    ich habe da noch ein weiteres Plugin, was zuviel Sachen lädt, wenn diese eigentlich nicht benötigt werden. Es geht um Commons Booking (https://de.wordpress.org/plugins/commons-booking/).

    Ich verwende es auf https://www.diabetes-hilfe-nuernberg.de/ für eine interne Bibliothek. Wenn ich Seiten mit den Shortcodes habe, dann kann ich den Ballast los werden. Was mache ich aber, wenn ein CPT des Plugins in der Form https://www.diabetes-hilfe-nuernberg.de/cb-items/kinder-und-jugendliche-mit-diabetes/ ausgegeben wird? Hier gibt es keinen Shortcode den ich abfragen könnte.

    Wer kann mir da helfen?
    Gruß Frank

    1. Johannes sagt:

      Hallo Frank,

      Leider auch hier wird direkt die enqueue-Funktion verwendet: https://github.com/wielebenwir/commons-booking/blob/master/public/class-commons-booking.php#L624 Interessanterweise aber nur für Styles, für ein paar Scripts wird mit is_singular ( 'cb_items' ) der CPT gechekt: https://github.com/wielebenwir/commons-booking/blob/master/public/class-commons-booking.php#L664

      Genau das Gleiche können wir für die Styles nachrüsten. Versuch mal diese Funktion (nicht selber getestet)

      function cleanup_plugin_commons_booking_dequeue() {
      
      	if ( is_singular( 'cb-items' ) ) {
      		return;
      	}
      
      	wp_dequeue_script( 'commons-booking-comment-length-script' );
      	wp_dequeue_style( 'commons-booking-plugin-styles' );
      	wp_dequeue_style( 'commons-booking-plugin-themes' );
      	wp_dequeue_style( 'commons-booking-profile-cleanup-tml' );
      	wp_dequeue_style( 'commons-booking-profile-cleanup-tml' );
      
      }
      add_action( 'wp_enqueue_scripts', 'cleanup_plugin_commons_booking_dequeue' );

      Ich hoffe das hilft dir.

  9. Hallo Johannes,

    vielen Dank für die Info. Ich konnte mir in der Zwischenzeit schon helfen und habe folgende Lösung (für alle Plugins):

    Ich entferne erstmal alle Scripte und Styles und lade diese erst wieder, wenn diese wirklich benötigt werden (Abfrage mittels has_block oder has_shortcode oder is_singular oder is_page).

    Es schein mir wohl eine Art Plugin-Krankheit zu sein, einfach alles zu laden, egal ob benötigt oder nicht. Ich habe auf der Website (https://www.diabetes-hilfe-nuernberg.de/) neben Commons Booking noch andere aktiv, wo ich dies so mache.

    Meine Idee: Ein eigenes Plugin dafür zu schreiben, wo man im Backend es sauber und schön steuern kann. Johannes, hast Du Lust mich dabei zu unterstützen? Es wäre mein erstes Plugin…

    Gruß Frank

    1. Johannes sagt:

      Hallo Frank,

      im Idealfall sollte man eher die Plugin-Autoren darauf hinweisen wie es korrekt gemacht wird. Dann würde man sich so ein Plugin sparen.

      Die Frage, die sich mir stellt, ist, ob man das für den Endnutzer sinnvoll umsetzen kann. Kann man dem Endnutzer zutrauen die korrekt ID von einem Block oder Custom Post Type herauszufinden? Wenn nicht ist die Frustration schnell sehr groß. Das sehe ich als das größte Problem für so ein Plugin.

      Ich gebe gerne mein Senf zu so ein Plugin und helfe dir gerne mit Rat und Tat aber bevorzugen würde ich den Weg das sinnvolle enquenen von Script/Styles in die Plugin Guidelines aufgenommen und auch vorausgesetzt wird. Vielleicht muss man dazu mal eine Diskussion starten.

  10. Andreas sagt:

    Hi Johannes,
    ich schlagemich mit dieser Problematik auch gerade rum. Ich habe eine Onepage in WP, auf der ich ein per PHP-Code selbst erstelltes Formular habe, mit dem zwei Datumswerte an eine Reservierungsseite übergeben werden, die mit cf7 erstellt ist. Die beiden Datepicker-Felder auf der Hauptseite benutzen allerdings das jquery-ui.min.css von cf7 und nicht das vom datepicker. Vielleicht hast du eine Idee? Mit deiner Lösung habe ich keinen Erfolg gehabt.
    Andreas

    1. Johannes sagt:

      Willst du das jquery-ui.min.css entfernen oder wird es nicht auf der richtigen Seite geladen?

Schreibe einen Kommentar

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