critical css gulp workflow für eine bessere Benutzer Erfahrung

Wenn es um den Kritischen Rendering Pfad geht, ist das Ziel, dass insbesondere mobile Browser sehr schnell mit dem Rendering der Website anfangen können. Dem Besucher kommt die Reaktion der Website dann schneller vor, als wenn der Browser erst das gesamte CSS lädt und danach die Website rendert. Gerade bei Verbindungen jenseits von LTE, ist ein Render blockender Abruf von CSS, Webfonts, JS ein wahrer Dämpfer des Seitenaufbaus.

Irgendwann im Laufe der Zeit stellte das Web zum Beispiel fest, dass Javascript gar nicht im <head> geladen werden muss, sondern es völlig ausreicht das meiste Javascript vor dem </body> einzubinden. Bis dahin darf der Browser den DOM und das CSSOM erstellen.

Dank Google Pagespeed oder dem schickeren TestMySite rückt jetzt das CSSOM in den Fokus des Rendering.

Die Idee ist, dass man das CSS, welches zur Darstellung einer gewissen Bildschirmgröße notwendig ist, in die Seite einbettet und das restliche CSS asynchron (non blocking) nachlädt.

Eine Aufgabe, die ohne Hilfsmittel kaum zu bewerkstelligen ist. Man müsste bei jeder Zeile (S)CSS überlegen ob diese für eine bestimmte Bildschirmgröße in den Bereich above the fold fällt und dann ein System entwickeln, welches das CSS als Datei erstellt, aber auch im above the fold Teil unterbringt.

Moderne Frontendprojekte sind heutzutage meistens schon mit npm, yarn, gulp, bower, usw. aufgeladen und selbst für reine HTML Projekte gibt es aufwendige Werkzeugketten die ineinander greifen.

Genau hier, setzt man noch eins drauf: Critical. Dieses Tool nimmt eine Menge Arbeit ab, mit dem einen und anderen Fallstrick. Weitere Critical Css Tools hat der Autor von Critical ebenfalls aufgelistet.

Das Henne Ei Problem

Naturgemäß kann die above the fold Generierung erst starten, nachdem das Projekt die bisherige Werkzeugkette durchlaufen hat. Positiv ist hier die Möglichkeit neben einem Pfad zu einer HTML Datei, auch eine URL angeben zu können, wenn man zum Beispiel an einem dynamischen Projekt arbeitet.

Zur Wahl steht entweder die Möglichkeit, die abgerufene URL neu generiert lokal als HTML Datei abzuspeichern (inline: true), oder nur das erzeugte above the fold CSS in eine Datei zu speichern (inline: false). Der Vorteil der HTML Datei ist, dass hierbei direkt ein idealer Weg zum asynchronen CSS nachladen mitgeliefert wird (Laut Doku: loadCSS der filamentgroup).

Allerdings ist hier Vorsicht geboten, wenn JSON in ein Data Attribut geschrieben ist, kommt es zu dem Problem, dass aus ' (singlequotes) " (doublequotes) werden:

Vor critical:

<div data-slick='{"slidesToShow": 4, "slidesToScroll": 4}'>

Nach critical:

<div data-slick="{"slidesToShow": 4, "slidesToScroll": 4}">

wodurch das Markup unbrauchbar wird. Das liegt daran, dass der Prozess einen DOM erzeugt und ihn später in die Datei schreibt. In diesem Fall funktioniert zum Beispiel der Slick Slider nicht mehr :-(

Daher hat sich folgende Konfiguration des Critical Plugin bewährt:

gulp.task('critical', function () {
    'use strict';
    return critical.generate({
        inline: false, // keine html Datei erstellen
        base: './public/', // *Root Verzeichnis* des Webauftritts
        src: 'index.html', // Pfad zur Datei
        dimensions: [{
          height: 200,
          width: 400
        },{
          height: 320,
          width: 576
        },{
          height: 1024,
          width: 768
        },{
          height: 1024px,
          width: 1280px
        }],
        dest: 'above-the-fold.css', // Name der CSS Datei die im *Root Verzeichnis* angelegt wird
        minify: true, // Minifizierung des CSS
        ignore: ['@font-face'] // Webfonts im above the fold Teil ignorieren
    });
});

Es ist auch möglich anstatt des dimensions Array, nur eine width und height Kombination anzugeben. Mit den verschiedenen Viewports wird sichergestellt, dass auch Ausnahmen für bestimmte Viewports nicht übersehen werden.

Dass die erstellte CSS Datei im Root Verzeichnis erstellt wird, mag auf den ersten Blick Bauchweh erzeugen. Da die Pfade zu Bildern und Co in dieser Datei relativ erzeugt werden, muss die Datei den Webroot als Ausgangsbasis haben, sonst sind Grafiken innerhalb des CSS falsch verlinkt.

Ignorieren der Webfonts hat den Nachteil, dass der Benutzer die Webfont Ersetzung wahrnehmen wird. Allerdings ist das Laden der Webfonts wieder render blocking und damit dem gesetzten Ziel nicht zuträglich. Eine Base64 Einbettung der Webfonts würde die Payload immens erhöhen, und bei kommerziellen Webfonts ist das auch gar nicht immer erlaubt.

Nachdem das above-the-fold CSS jetzt erzeugt ist, muss dieses noch mindestens in die Startseite eingebettet werden. Wir, bei der i22, nutzen bei reinen Frontend Projekten gerne twigjs zur Markup Generierung. Wie anfangs beschrieben, muss dieser Task nun ein zweites Mal ausgeführt werden. Das erste Mal um die Arbeitsgrundlage für critical zu erschaffen, das zweite Mal um das above-the-fold CSS einzubetten:

gulp.task('critical-html', function criticalHtmlTask() {
  let task;

  if (fs.existsSync('./public/above-the-fold.css')) {
    config.twigData.criticalCss = true;
    task = gulp
      .src([config.twig.src + 'public/**/*.twig'])
      .pipe(twig({
        data: config.twigData
      }))
      .on('error', consoleErrFunction)
      .pipe(gulp.dest(config.twig.target))
      .pipe(browserSync.stream())
      .on('end', function setCriticalCssToFalse() {
        config.twigData.criticalCss = false;
      });
  }
  return task;
});

und das entsprechende Twig Template sieht so aus:

…
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
{% if criticalCss %}
<style id="above-the-fold">{% include "../public/above-the-fold.css" %}</style>
<link rel="preload" type="text/css" href="css/{{project_name}}" as="style">
<noscript><link rel="stylesheet" type="text/css" href="css/{{project_name}}"></noscript>
<script>!function(e){"use strict";var t=function(t,n,r){function o(e){return i.body?e():void setTimeout(function(){o(e)})}function a(){d.addEventListener&&d.removeEventListener("load",a),d.media=r||"all"}var l,i=e.document,d=i.createElement("link");if(n)l=n;else{var s=(i.body||i.getElementsByTagName("head")[0]).childNodes;l=s[s.length-1]}var u=i.styleSheets;d.rel="stylesheet",d.href=t,d.media="only x",o(function(){l.parentNode.insertBefore(d,n?l:l.nextSibling)});var f=function(e){for(var t=d.href,n=u.length;n--;)if(u[n].href===t)return e();setTimeout(function(){f(e)})};return d.addEventListener&&d.addEventListener("load",a),d.onloadcssdefined=f,f(a),d};"undefined"!=typeof exports?exports.loadCSS=t:e.loadCSS=t}("undefined"!=typeof global?global:this),function(e){if(e.loadCSS){var t=loadCSS.relpreload={};if(t.support=function(){try{return e.document.createElement("link").relList.supports("preload")}catch(e){return!1}},t.poly=function(){for(var t=e.document.getElementsByTagName("link"),n=0;n<t.length;n++){var r=t[n];"preload"===r.rel&&"style"===r.getAttribute("as")&&(e.loadCSS(r.href,r),r.rel=null)}},!t.support()){t.poly();var n=e.setInterval(t.poly,300);e.addEventListener&&e.addEventListener("load",function(){e.clearInterval(n)}),e.attachEvent&&e.attachEvent("onload",function(){e.clearInterval(n)})}}}(this);</script>
{% else %}
<link rel="stylesheet" type="text/css" href="css/{{project_name}}.css">
{% endif %}
{% include "./partials/e1-favicons.twig" %}
…

In einem CMS könnte man innerhalb eines Templates ähnlich verfahren. Die asynchrone CSS Einbindung ist von einem Durchlauf mit inline: true entnommen und laut critical Doku eine Implementierung des loadCSS Tool.

Zu guter Letzt fehlt noch ein extra Gulp Task, der nach der bisherigen Werkzeugkette ausgeführt wird:

gulp.task('critical-css', gulpsync.sync(['critical', ['critical-html'], 'version']));

Man beachte hier die Nutzung von gulpsync, da die Tasks aufeinander aufbauen. Die normale Asynchronität von Gulp & Co muss hier leider ausgeschaltet werden.

Und in der package.json steht dann sowas wie:

"scripts": {
  "postinstall": "npm build",
  "start": "gulp watch",
  "build": "gulp && gulp critical-css"
}

Anhand dieses Beispiels sieht man, dass critical css und above the fold Optimierung automatisierbar ist und gar nicht so viel Extra Aufwand mit sich bringt.

Schreibe einen Kommentar

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