Lazy-load JavaScript

Mein kürzlich schon mal erwähntes Blogprojekt basiert, wie diese Seite hier auch, auf einem Static Site Generator zur Erzeugung des HTML. Hier läuft Nikola im Hintergrund, allerdings bin ich mit einigen Dingen nicht ganz so zufrieden. Zuvorderst ist mir das Datenvolumen, welches für das Laden einer Seite transferiert wird, viel zu groß. So werden aktuell beim Aufruf von blog.pvitt.de 488,04 kB versendet. Davon entfallen aber nur 42,3 kB auf HTML, dafür aber 136,39 kB auf CSS und sogar 311,13 kB auf JavaScript. Das Problem kann man sicher über die Erstellung eines Themes angehen, aber Nikola macht es den Nutzern durch fehlende oder schlechte Dokumentation oder komplexe Strukturen nicht gerade einfach.

Daher habe ich für das neue Projekt nach einem alternativen Static Site Generator umgesehen und habe einige ausprobiert. Nach einigen Tests bin ich dann bei Pelican gelandet, der mich bisher auch nicht enttäuscht hat. Nach der einfachen Installation und der ebenfalls einfachen Installation eines Themes für die Seite sowie ein wenig Optimierung von CSS und mit Verwendung von SVG als Image-Format komme ich mit zwei Testposts auf der Startseite aktuell auf ein Transfervolumen von von 7,13 kB, 3,8 kB HTML, 2,74 kB für CSS und 609 B für SVG. Mit Transportkomprimierung sind das sogar nur 3,85 kB, die tatsächlich über die Leitung gehen. Das ist mal eine andere Hausnummer.

Umso ernüchterter war ich, als ich dann die Kommentarfunktion via isso nachrüsten wollte. Denn dafür ist ein JavaScript-Paket nötig, welches zwar schon minimiert wurde, trotzdem aber noch 54,63 kB auf die Waage (und 20,36 kB auf die Leitung) bringt. Das ist eindeutig zu viel für eine einfache Kommentarfunktion.

Meine Idee war nun, die Kommentarfunktionalität nur dann zu laden, wenn der Nutzer diese auch nutzen möchte. Dazu musste ich zwar ein wenig HTML ergänzen, aber die paar Hundert Byte fallen im Relation zu den 54 kB dann doch nicht wirklich ins Gewicht.

Der Platzhalter im DOM, an dem das JavaScript seinen HTML-Code ablegt, blieb unverändert im Code. Aber das Script-Tag aus dem Header habe ich entfernt:

<script data-isso="//comments.example.tld/"
    src="//comments.example.tld/js/embed.min.js"></script>

Stattdessen wird dieses nun erst auf Benutzerwunsch geladen:

{% if ISSO_SITENAME %}
<input id="issoBtn" type="image" height="16" src="/theme/images/comment.svg" onclick="
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.src = '//{{ ISSO_SITENAME }}/js/embed.min.js';
    document.body.appendChild(s);
    this.style.display = 'none';
    " />
{% endif %}

Das input-Tag wird (wie auch der Platzhalter) nur eingefügt, wenn ISSO auch konfiguriert wurde. Es zeigt eine Sprechblase an, mit SVG realisiert nur 184 Byte groß. Wenn der Benutzer auf diese Sprechblase klickt, wird das JavaScript geladen und angehängt, sodass es aktiv werden kann. Im Anschluss wird noch die Sprechblase versteckt, da nun die Kommentare ja geladen sind.

Isso verfügt auch noch über die Möglichkeit, das clientseitige JavaScript zu konfigurieren. Dies geschieht an Hand von data-*-Attributen, die am Script-Element definiert werden. Da ich das Script-Element erst zur Laufzeit erzeuge, müssen natürlich auch die Attribute zur Laufzeut erzeugt werden. Dazu fügen wir diese Zeilen vor dem Aufruf von document.body.append ein:

var aia = document.createAttribute('data-isso-avatar');
aia.value = 'false';
s.setAttributeNode(aia);
var aiv = document.createAttribute('data-isso-vote');
aiv.value = 'false';
s.setAttributeNode(aiv);

Mit dieser Lösung bleibt das Datenvolumen bei einem normalen Pageload klein, und nur, wenn kommentiert werden soll, wird auch das große JavaScript nachgeladen. Was mich an dieser Lösung noch stört, sind zwei Dinge:

  1. Der Leser sieht nicht, ob es schon Kommentare gibt. Hier überlege ich aktuell noch, wie ich die Anzahl der bisher schon vorhandenen Kommentare ermitteln kann, bevor ich das JavaScript geladen habe.
  2. Die Sprechblase auf der Seite wird auch dann versteckt, wenn das Javscript nicht geladen wurde. Das ist in der Praxis sicher kein wirkliches Problem, da im Fall, dass das JavaScript nicht geladen werden kann, wahrscheinlich die Seite an sich oder der Server einen Knacks hat. Da wäre ein Reload der Seite sicher besser. Aber aus Gründen der Vollständigkeit und weil ich es einfach gern wissen würde, interessiert mich schon, wie ich den HTTP-Response für das Script ermitteln kann.