Perfekt: 100 % Javascript: cache, async und defer nutzen

Beim Thema Javascript hat man eigentlich immer nur zwei verschiedene Optionen: Entweder gilt es als Render Blocking Script. Oder es wird gecached, ist dann nicht mehr Render Blocking – funktioniert dann bestenfalls aber nicht mehr richtig. Um das Render Blocking wegzubekommen, setzen fast alle Methoden darauf, Javascripte entweder mit dem Schlüsselwort defer oder async quasi „aus der Schusslinie“ zu nehmen. Aber gibt es womöglich auch eine Möglichkeit, ein Javascript aus dem Render Blocking zu bekommen und trotzdem ein sauber laufendes Script zu erhalten? Ja, das geht! Ein möglicher Ansatz dafür soll hier beschrieben werden. 

Wie wird Javascript eigentlich grundsätzlich bearbeitet?

Javascript ist fast ein Kuriosum: Einerseits läuft vieles in Javascript Asynchron ab – also unabhängig davon, wie der Browser den Rest der Webseite aufbaut (HTML,CSS). Es dürfte also so gut wie kaum Render Blocking Javascripte geben!? Dennoch hat bestimmt jeder schon mal spätestens bei Tools wie PageSpeed den Hinweis bekommen, entsprechende Render Blocking Scripte zu entfernen. Und darunter fallen dann meist sogar auch sehr einfach gehaltene Javascripte. Warum?

Weil ein Browser zur „initialen“ Erstellung der Webseite in drei Schritten vorgeht:

  1. Das vom Server erhaltene HTML wird (grob) im Hintergrund gerendet.
  2. Anschließend wird darüber all das gelegt, was das zutreffende CSS noch hergibt.
  3. Danach erfolgt ein weiterer Durchlauf und alles was Javascript noch ändern möchte, wird hinzugefügt.

Nach jedem Schritt wird die Seite intern nochmals gerendert, um die entsprechenden Auswirkungen einzufügen. Dabei steht aber bereits im HTML das Javascript mit drin – entweder inline oder zumindest als Source-Verlinkung zum Nachladen. Der Browser lädt also das HTML, stellt fest, dass ein Javascript geladen werden muss und hält erst mal an, um dieses Javascript nachzuladen. Wenn man es also genau nimmt, sieht die „initiale“ Erstellung also folgendermaßen aus:

  1. Das vom Server erhaltene HTML wird (grob) im Hintergrund gerendet, dabei werden im Quelltext angegebene weitere Ressourcen wie Bilder, CSS und Javascript vom Server angefordert und mitbearbeitet, sobald diese vom Server vorliegen.
  2. Anschließend wird darüber all das gelegt, was das zutreffende CSS noch hergibt. Sind hier weitere Import-Angaben enthalten, wird auch das noch nachgeladen.
  3. Danach erfolgt ein weiterer Durchlauf und alles was Javascript noch ändern möchte, wird hinzugefügt. Wird durch das Javascript noch was nachgeladen, wird dies in die Seite eingefügt und ggf. wird das Rendern nochmal bei Schritt 1 begonnen.

Was passiert, wenn die Scripte mit defer oder async markiert werden?

Defer und Async nehmen die Javascripte aus dem Ladevorgang des Schrittes 1 heraus. Unterschied ist dabei, wann dann letztendlich die Javascripte geladen werden. Defer stellt die so markierten Scripte an das Ende der Ladewarteschlange. So muss das Rendern nicht immer unterbrochen werden, weil der Server ein zusätzliches Element komplettiert. Die Seite kann erst mal ganz „in Ruhe“ die Render-Phase 1 durchlaufen, da Javascript ja ohnehin erst so richtig in Schritt 3 benötigt wird. Die Reihenfolge, in der die Scripte angefordert und letztendlich auch bearbeitet werden, wird dabei eingehalten.

Async funktioniert ein bisschen anders. Async ist die Abkürzung für Asynchron. Das bedeutet, dass die Scripte losgelöst von irgendeiner Reihenfolge geladen werden. Es kann also gut vorkommen, dass ein zuerst angegebenes Javascript als Letztes eintrifft. Ist aber gerad dieses Javascript eine wichtige Ressource, die weitere Scripte auf der Seite als Voraussetzung benötigen, kann man eigentlich nur sagen „dumm gelaufen“.

Klassisches Beispiel: jQuery wird auf vielen Seiten eingebunden. Die Seite selbst kann dann auf jQuery aufsetzende weitere Scripte benutzen. Grundvoraussetzung für diese weiterführenden Scripte ist dann aber das Vorhandensein von jQuery. Ist das benutzende Script aber sehr klein (oder sogar inline), so wird es aller Voraussicht nach geladen und behandelt, noch bevor jQuery zur Verfügung steht.

Gibt es eine Möglichkeit, Javascript asny oder defer einzusetzen, ohne auf Ausführungsfehler zu stoßen?

In 90 % aller Fälle wird irgendein Script irgendeinen Fehler melden auf Grund eines fehlenden vorhergehenden Javascripts. Natürlich könnte man jetzt in jedem seiner Javascripts einfach nochmal explizit die benötigten Javascripts laden (in unserem Fall jQuery), aber das ist wohl auch nicht Sinn der Sache. So würde man vermutlich jQuery mehrere Male unnötig laden.

Es gibt aber die Möglichkeit, dem eigenen Javascript-Anteil so auszuführen, dass es auf das vorhergehende Javascript wartet. Das ist aber nicht ganz so offensichtlich, wie es zu Beginn scheint. Genauer gesagt wird nicht geprüft, ob die Datei schon geladen worden ist, sondern vielmehr, ob das daraus resultierende Objekt bzw. die entsprechende Variable schon gesetzt ist. Dies lässt sich deswegen sehr leicht umsetzen, da Javascript dVariablen und Objekte auf sogenannte truthiness prüfen kann. Hierbei wird darauf geprüft, ob eine angegebene Variable bzw. ein angegegenes Objekt auf true evaluiert. Das tut es immer dann, wenn es nicht falsch, leer, 0 oder undefiniert ist. Und genau das ist ja die Fehlermeldung, die wir in unserem Beispiel für jQuery in der Console bekommen: jQuery is not defined.

Also kann vor der Ausführung des eigenen JavascriptCodes einfach folgendes geschrieben werden:

if (jQuery)
{
   // eigener Javascript-Code
}

Aber es gibt noch einen wichtigen Punkt: Der Code würde nur einmal ausgeführt werden und das ziemlich gleich nach dem Laden. Der Consolen-Fehler ist zwar weg, der eigene Javascript-Code wird aber zu 99 % immer noch nicht ausgeführt – eben weil jQuery ja immer noch erst später geladen wird!

Um dies zu umgehen, bedarf es eines weiteren „Javascript-Tricks“: Die Verwendung von setInterval. Die Funktion führt den enthaltenen Code so lange aus, bis das Interval gelöscht wird. Dies passiert mit clearInterval – und das braucht als Parameter eine Variable, die den Verweis auf das entsprechende Interval enthält. Zusammengesetzt sieht dann der funktionierende Quellcode so aus:

var jqueryrunning=setInterval(function() {
  if (jQuery)
 {
     clearInterval(jqueryrunning);
    // eigener Javascript-Code
 }
},200);

Es könnte jetzt noch die Frage aufkommen, ob man statt var vielleicht lieber let benutzen sollte!? Da wir hier aber mit einer anonymen Funktion arbeiten brauchen wir einen etwas erweiterten Kontext und let-Variablen würden hier in der Funktion nicht zur Verfügung stehen.

Wichtig hierbei ist die Zeile mit dem clearInterval – sonst würde der eigene Code immer und immer wieder ausgeführt werden statt jeweils nur einmal. Die 200 als zweiter Aufrufparameter des setInterval ist die Zeitspanne, in der setInterval erneut ausgeführt wird. Wer in seinem Code bemerkt, dass die Zeit zu viel oder zu wenig ist, passt diesn Parameter einfach entsprechend an.

Das Interval sorgt jetzt jedenfalls dafür, dass der Code an beliebiger Stelle geladen werden kann, aber erst dann ausgeführt wird, wenn – in dem Fall jQuery – definiert ist. Das Beispiel lässt sich natürlich entsprechend adaptieren auf andere Abhängigkeiten. Wer mehr als eine Abhängigkeit benötigt kann entweder auf die letzte Abhängigkeit prüfen oder alternativ mit && mehrere Abhängigkeiten im if abprüfen:

if (jQuery && anderes)

Vor- und Nachteile der Methode

 

Vorteile

  • jQuery lässt sich problemlos asynchron laden
  • die Seite blockiert nicht auf Grund zu ladender Scripte wie jQuery und Co.
  • Keine Scriptfehler bei Inline-Scripten, weil jQuery noch nicht geladen ist
  • dadurch keine negativen Einflüsse bei Suchmaschinen wie google
  • kein kompliziertes, verschachteltes Laden der Scripte notwendig

Nachteile

  • läuft in einer Schleife, solange bis die Bedingungen erfüllt sind = das Javascript nachgeladen wurde
  • kann dadurch zur „Endlosschleife“ werden, wenn das asynchron zu ladende Javascript nicht lädt (beispielsweise weil der externe Server, von dem geladen wird, nicht erreichbar ist)
  • bei mehreren Scriptschnippselchen benötigt man ggf. mehrere Hilfsvariablen, um die Ausführung korrekt zu steuern

Schreibe einen Kommentar