CSS- und JavaScript-Overhead reduzieren

Nicht nur, was die Serverseite anbelangt, auch der von WordPress an den Client ausgelieferte Code lässt sich im Hinblick auf Performance kaum beschreiben, ohne unflätige Worte zu gebrauchen. Erfahren Sie in diesem Beitrag, wie Sie das technisch rückständige CMS ein Stück weit näher in Richtung einer modernen Website bringen.

Weg mit emojicons

Während das Vorhandensein von Smileys in Form von Grafiken auf Website noch Geschmackssache sein mag (ich hasse sie und auf geschäftlichen Websites haben sie m.E. nichts verloren), ist die Implementierung in WordPress der reinste Horror.

<script type="text/javascript">
window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/2.2.1\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/2.2.1\/svg\/","svgExt":".svg","source":{"concatemoji":"http:\/\/192.168.2.105\/werbe-markt.de\/2017\/wp-includes\/js\/wp-emoji-release.min.js?ver=4.7.3"}};
!function(a,b,c){function d(a){var b,c,d,e,f=String.fromCharCode;if(!k||!k.fillText)return!1;switch(k.clearRect(0,0,j.width,j.height),k.textBaseline="top",k.font="600 32px Arial",a){case"flag":return k.fillText(f(55356,56826,55356,56819),0,0),!(j.toDataURL().length<3e3)&&(k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,65039,8205,55356,57096),0,0),b=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55356,57331,55356,57096),0,0),c=j.toDataURL(),b!==c);case"emoji4":return k.fillText(f(55357,56425,55356,57341,8205,55357,56507),0,0),d=j.toDataURL(),k.clearRect(0,0,j.width,j.height),k.fillText(f(55357,56425,55356,57341,55357,56507),0,0),e=j.toDataURL(),d!==e}return!1}function e(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var f,g,h,i,j=b.createElement("canvas"),k=j.getContext&&j.getContext("2d");for(i=Array("flag","emoji4"),c.supports={everything:!0,everythingExceptFlag:!0},h=0;h<i.length;h++)c.supports[i[h]]=d(i[h]),c.supports.everything=c.supports.everything&&c.supports[i[h]],"flag"!==i[h]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[i[h]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(g=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",g,!1),a.addEventListener("load",g,!1)):(a.attachEvent("onload",g),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),f=c.source||{},f.concatemoji?e(f.concatemoji):f.wpemoji&&f.twemoji&&(e(f.twemoji),e(f.wpemoji)))}(window,document,window._wpemojiSettings);
</script>
<style type="text/css">
img.wp-smiley,
img.emoji {
display: inline !important;
border: none !important;
box-shadow: none !important;
height: 1em !important;
width: 1em !important;
margin: 0 .07em !important;
vertical-align: -0.1em !important;
background: none !important;
padding: 0 !important;
}
</style>

Wer das unruhige und unseriös wirkende Gedöns auf seiner Website haben möchte, kann das auch auf technisch ausgereifte Methode umsetzen. In diesem Fall geht es aber schlichtweg um die Entfernung und dabei hilft das schlichte Plugin Disable Emojis. Typisch für WordPress ist mal wieder, dass elementarste Errungenschaften des www wie title-Attribute für Links oder die Verwaltung von Metaangaben fehlen, während extra ein Plugin installiert werden muss, um unnütze Features zu entfernen.

Etwas besser, aber immer noch schlimm sieht es nach dem Entfernen der emojicons aus.

Das Plugin entfernt bzw. verhindert die Einbindung des auf jeder Seite im Kopfbereich eingebundenen Codes. Damit ist das Problem der die CSS-Dateien blockierenden JavaScript-Datei gelöst. Die Anzahl überflüssiger CSS-Regeln ist mit 679 aber nur um 1 geringer als zuvor.

WooCommerce-Styles und -Scripts nur wo nötig

Dieser Absatz ist für Sie nur unter 2 Bedingungen relevant:

  1. Sie verwenden WooCommerce.
  2. Ihre Website ist kein Onlineshop, sondern enthält einen solchen.
3 zu 100% überflüssige CSS-Dateien

Zu diesem Thema gibt es ein wunderbares Tutorial von Devin Walker, das ich hier nicht einfach kopieren bzw. plump übersetzen möchte. Zudem ist der Code nicht die ultimative und für alle geltende Lösung, da Styles und Scripts je nach Version, Theme und installierten Extensions variieren. In der von mir aktuell genutzten Version 2.6.14 von WooCommerce beispielsweise greift keine einzige der 4 im Beispiel genannten wp_dequeue_style()-Aufrufe.

Was stattdessen bei Verwendung des Themes Twenty Seventeen alle WooCommerce-Stylesheets aus Nicht-WooCommerce-Seiten entfernt, sind nachfolgende Codezeilen. Diese können in einem eigenen Plugin oder der functions.php eines Child-Themes untergebracht werden:

add_filter('woocommerce_twenty_seventeen_styles', function ($styles) {
if (!is_woocommerce() && !is_cart() && !is_checkout()){
return array();
}
return $styles;
});

Ohne jegliche Einschränkungen für die Darstellung der Website sind es damit nur noch 4 statt 7 externe CSS-Dateien. Der Anteil überflüssiger CSS-Regeln beträgt 67% statt den ursprünglichen 77%. Das zeigt, wir sind auf dem richtigen Weg. Vom Ziel sind wir aber noch weit entfernt.

Kümmern wir uns aber zunächst um die WooCommerce JavaScript-Files. Dabei koste ich aus, eine noch funktionsfähige Version von Firebug installiert zu haben.

Jede Menge kleine JavaScript-Dateien werden separat geladen.

Die verbleibenden der 15 JavaScript-Dateien zu einer einzigen zusammenzufassen, um die Anzahl der Requests zu reduzieren, steht später noch auf dem Programm. Zunächst entfernen wir die überflüssigen WooCommerce-JavaScript-Dateien. Dabei handelt es sich um insgesamt 5 Stück und zwar von add-to-cart.min.js bis einschließlich cart-fragments.min.js gemäß obigem Screenshot.

add_action('wp_enqueue_scripts', function () {
if (!is_woocommerce() && !is_cart() && !is_checkout()){
wp_dequeue_script('wc-add-to-cart');
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('woocommerce');
}
}, 99);

Ich habe keine Lust zu schauen, welche beiden der 5 Script-Dateien wegen der Abhängigkeit nicht geladen werden. Auf die paar Byte überflüssigen Arbeitsspeicherverbrauchs kommt es nun wirklich nicht an. Die Geringfügigkeit der Fortschritte zeigt, wie katastrophal die Ausgangssituation war.

„Nur noch“ 10 externe JavaScript-Dateien werden nach Entfernen der WooCommerce-Scripts geladen.

Brauchen wir jQuery Migrate?

Nein, also ich nicht. Plugins von anno dazumal nutze ich nicht und WordPress selbst wird ja wohl keinen veralteten jQuery-Code verwenden.

Leider ist es mit dem Entfernen nicht getan. Es muss außer dem die Abhängigkeit jQuerys von jQuery Migrate aufgelöst werden. Ohne die Zusammenfassung der Scripts bedeutet das: 1 blockierende Ressource weniger.

In diesem Fall orientiere ich mich an einer fertigen Lösung, werde diese aber deutlich modifieren. Der zugehörige Blog-Beitrag beschreibt zudem sehr gelungen das Wie und Weshalb. Meine Version hat den großen Vorteil, dass im bei zukünftigen WordPress- und jQuery-Versionen die von WordPress vorgegebene Versionsabhängig ebenso erhalten bleibt wie ggf. hinzugefügte weitere Abhängigkeiten.

add_filter('wp_default_scripts', function (&$scripts) {
if (!is_admin() && isset($scripts->registered['jquery']->deps)){
$scripts->registered['jquery']->deps=array_diff($scripts->registered['jquery']->deps,array('jquery-migrate'));
}
});

Weiteres überflüssiges CSS-Gedöns entfernen

Jetzt wird es heftig. Vor allem: Wenn ich sehe, dass 70% der twentyseventeen CSS-Datei nicht verwendet werden, frage ich mich schon, warum ich ein Child-Theme davon erstelle und nicht selbst ein schlankes Layout from scratch erstellt habe. Sei’s drum.

UnCSS heißt das unschätzbar wertvolle Node.js-Modul. Es filtert aus einer oder mehreren CSS-Dateien die Regeln heraus, die auf einer bestimmten Seite benötigt werden. Das Tool kann noch viel mehr, aber darum soll es gerade nicht gehen. Kümmern wir uns um die Probleme, die wir trotzdem noch haben:

  • Welche CSS-Regeln sollen in eine globale, auf jeder Seite eingebundene CSS-Datei und welche in eine spezifische, nur auf bestimmten Seiten eingebundene CSS-Datei?
  • Was passiert bei Aktualisierungen von Themes, Plugins oder Plugin-Neuinstallationen?

Der letzte Punkt ist ganz einfach zu beantworten: Der Aufwand steht nach jeder Änderung an den als Quelle dienenden CSS-Dateien wieder an. UnCSS ist mit einem schönen One-Liner zu bedienen, weshalb der Vorgang automatisiert werden kann. Möchten Sie dann auch noch die Tests automatisieren, wünsche ich Ihnen viel Spaß dabei. Ich bin schon bei der UnCSS-Automatisierung ausgestiegen. Es bedarf m.E. zu viel Menschenverstand und händischer Prüfung, als dass eine Automatisierung eine bedeutsame Aufwandsersparnis mit sich bringen würde.

Damit wären wir auch schon beim ersten Punkt. Ist die Unterscheidung, ob die bzw. dann eine WooCommerce-CSS-Datei(en) auf einer Seite zu laden ist oder nicht, noch relativ einfach, ist sie bei einzelnen Beiträgen oder Seiten umso schwieriger. Zum Beispiel: CSS-Regeln zur Darstellung von Kommentaren werden logischerweise nur auf Seiten benötigt, auf denen Kommentare zulässig sind bzw. teilweise nur dann dann, wenn auch Kommentare vorhanden sind. Alleine aus diesem trivialen Beispiel ergeben sich 3 Lösungsansätze:

  1. CSS-Regeln, die nur auf bestimmten Seiten benötigt werden, werden trotzdem in die globale CSS-Datei eingefügt. Auf allen übrigen Seiten bedeutet das einen Overhead, auf Seiten mit Kommentaren spart es jedoch – je nach Entscheidung bei 2 einen zusätzlichen Request.
  2. Wie konsequent verfolgt man die Auslagerung von Style-Angaben in externe Dateien? Wird zum Beispiel auf nur einer Seite eine einzige Regel benötigt, die auf keiner anderen Seite benötigt wird – erstellt man hierfür eine separate CSS-Datei, fügt den Code via style-Attribut ein oder innerhalb eines style-Tags?

Hier spielen Paradigmen und ganz individuelle Begebenheiten eine Rolle. Ich beschränke mich deshalb darauf, anhand eines Beispiels eine nur für eine Seite gültige CSS-Datei mit UnCss zu generieren, einzubinden, die übrigen zu deaktivieren und einen Screenshot des Ergebnisses in Chrome Audits einzufügen.

Auch zu diesem Thema existieren mehrere Tutorials. Die meisten haken sich in wp_enqueue_scripts ein und nutzen die Funktionen wp_deregister_style() und wp_dequeue_style() in einer Schleife. Das mag die sauberere Vorgehensweise sein, erwischt aber nicht alle Stylesheets in der Warteschleife. Einfacher, effektiver und ressourcenschonender ist nachfolgende Lösung.

add_action('wp_print_styles', function () {
    if (!is_admin()) {
        global $wp_styles;
        $wp_styles->queue=array();
        wp_enqueue_style('UnCss', get_stylesheet_directory_uri().'/css/uncss.css');
    }
});

Dieses Beispiel ist falsch, führt aber in WordPress 4.7.3 zum gewünschten Ergebnis. Die richtige Vorgehensweise wäre, wp_enqueue_style innerhalb eines wp_enqueue_scripts-Hooks aufzurufen. Davon abgesehen, dass ich den Pfad zu in einem Plugin enthaltenen Font anpassen musste, sieht die Seite ohne weiteres Zutun aus wie vorher. Es wird nur noch eine CSS-Datei geladen und die enthält ausschließlich benötigte Regeln.

Keine überflüssigen CSS-Regeln mehr und nur noch eine einzige externe CSS-Datei.

Externe JavaScript-Dateien zusammenfassen

Es wird. Für das Zusammenführen der JavaScript-Dateien greife ich auf ein Plugin zurück. Das berühmte W3 Total Cache fasst 9 JavaScript-Dateien in immerhin 4 Dateien zusammen. Das ist ganz nett, reicht aber nicht.

Das mit Abstand beste Tool für diesen Zweck ist Merge + Minify + Refresh. Das Plugin ist der Traum eines jeden Performance-Fetischisten. Mit HTTP2 Server Push und der Vorhaltung gz-komprimierter Dateien auf dem Server (ein Traum!) werde ich mich bei Gelegenheit noch befassen. Festzuhalten bleibt zunächst, dass das Plugin alle 9 JavaScript-Dateien in einer zusammengefasst und zudem noch komprimiert hat.

Merge + Minify + Refresh eliminiert überflüssige Requests auf externe JavaScript-Dateien.

Die verbliebenen Punkte (keine Ahnung, warum jetzt wieder 7 nicht angewandte CSS-Regeln existieren) haben wenig mit WordPress zu tun. In der Entwicklungsumgebung werde ich mich ganz sicher nicht mit Expiry-Dates herumschlagen. Die fehlende Größenangabe zu Bildern ist unschön, aber nicht das große, alle Seiten betreffende Problem und sie haben auch nichts mit dem Thema dieses Beitrags zu tun.

»«

Schreiben Sie einen Kommentar