Script-Elemente sind Blocker im Rendering-Prozess. Browser arbeiten den Quelltext einer Seite von oben nach unten durch. Wenn ein script-Element auftaucht, muss es erst ausgef√ľhrt werden, bevor der Browser sich um den nachfolgenden Quelltext k√ľmmern kann. Externe JavaScripts k√∂nnen aus diesem Grund massiven Einfluss auf die Ladegeschwindigkeit einer Seite haben, auch wenn die eigentliche Website schon l√§ngst vom Web-Server an den Browser ausgeliefert wurde.

Falls ein externes Script auf einem langsamen Server liegt, muss der Browser warten, bis er es komplett abgerufen und ausgef√ľhrt hat. Sollte das aufgerufene Script gar nicht mehr vorhanden sein, wartet der Browser seine Timeout-Einstellung ab, bis er weitermacht. Sicherlich hat jeder schon mal gesehen, dass eine Website nur bis einem gewissen Teil angezeigt wird und erst nach ein paar Sekunden der Rest dargestellt wird. Mit sehr hoher Wahrscheinlichkeit, war dies ein nicht mehr vorhandenes oder nur sehr langsam ladendes JavaScript.

Asynchrones Abholen findet erst nach dem Rendern der Seite statt und blockiert daher nichts. Dazu lässt sich bei konsequentem Nutzen von RequireJS der Einsatz von script-Elementen weitgehend vermeiden. Im Idealfall gibt es nur noch ein script-Element, das RequireJS lädt und ein weiteres Script startet, um die gesamte JavaScript-Funktionalität einer Seite zu initialisieren.

Konfiguration

Hier ein simples Beispiel einer Require-JS-Konfiguration mit jQuery:

(function() {
    require.config({
        paths: {
            'jquery': 'libs/jquery/jquery-1.7.1'
        }
    });

    require(['jquery'], function(jQuery){
      jQuery.noConflict();
    });
})();

Zuerst wird RequireJS so konfiguriert, dass jQuery mittels des Keywords “jquery” geladen werden kann. Im Konfigurations-Objekt “paths” wird daf√ľr als Attributsname “jquery” und als Wert der Pfad (ausgehend vom Verzeichnis des Scripts) zu jQuery gesetzt. Anschlie√üend wird require() aufgerufen und als erster Parameter wird ein Array mit den aufzul√∂senden Abh√§ngigkeiten erwartet – hier jQuery. Man kann hier ein unter “paths” gesetztes Keyword, den Pfad oder auch externe JavaScripts √ľber die URL angeben. Falls es sich um ein Modul handelt, wird auf die Dateiendung “.js” verzichtet.

Im zweiten Parameter wird eine Callback-Funktion definiert, deren Aufruf unmittelbar nach dem Laden der Abh√§ngikeiten stattfindet. Als Parameter folgen hier die R√ľckgaben der angeforderten JavaScript-Dateien. In diesem Fall ist es ein jQuery-Objekt, das sogleich in den No-Conflict-Modus versetzt wird, um nicht mit evtl. anderen geladenen Bibliotheken zu kollidieren. Dieses Verfahren ist besser bekannt als Dependency Injection.

Module

RequireJS bietet √ľber die Modul-Definition eine elegante L√∂sung, Programmbestandteile zu kapseln und Abh√§ngigkeiten (z.B. jQuery) aufzul√∂sen. Aktuell setze ich hier drei Module ein, die Funktionen f√ľr einzelne Beitr√§ge (Syntax Highlighting, Lightbox etc.) √ľbernehmen sowie das Tracking √ľber Piwik bzw. Google Analytics starten.

Alle Module sind so aufgebaut, dass sie Abh√§ngigkeiten in Form von Bibliotheken oder anderen Modulen erst aufl√∂sen, wenn sie gebraucht werden. Gleichzeitig stellt RequireJS sicher, dass eine Abh√§ngikeit nur einmal geladen wird und f√ľr jedes andere Modul zur Verf√ľgung steht.

Ein Beispiel-Modul:

define(['jquery'], function($) {
    var exports = {};

    exports.init = function() {
        $('body').append('<h1>Hello World!</h1>');
    }

    return exports;
});

Mit define() wird die Moduldefinition gestartet. Anschlie√üend passiert das gleiche, wie in der Funktion require(). Zuerst werden die Abh√§ngigkeiten als Array definiert, danach startet eine Dependecy Injection in die Callback-Funktion. An dieser Stelle wird ein Dollarzeichen als Parameter f√ľr die Abh√§ngigkeit verwendet, um jQuery wie gewohnt einsetzen zu k√∂nnen. Anschlie√üend wird das Objekt “exports” mit der Methode init() erstellt und zur√ľckgegeben. Wird das Modul an anderer Stelle √ľber require() geladen und das exports-Objekt in die Callback-Funktion injiziert, kann man in ihr die init-Methode aufrufen und damit das Modul starten. Nat√ľrlich es ist aber auch m√∂glich, darauf zu verzichten und innerhalb der Modul-Callback-Funktion direkt Code auszuf√ľhren.

Um ein Modul an anderer Stelle mit require() zu laden, gibt man den Pfad, ausgehend vom Verzeichnis der RequireJS-Konfiguration,¬†zum Modul an. Wie vorhin schon beschrieben, muss man hierbei auf die Dateiendung “.js” verzichten.

Zu guter letzt muss RequireJS selbst samt Konfiguration geladen werden:

<script src="js/libs/require/require.js" data-main="js/config"></script>

Ein simples Script-Element hierzu gen√ľgt – am besten unmittelbar vor dem schlie√üenden body-Element. Zus√§tzlich wird mit dem Daten-Attribut “main” noch der Pfad zur Konfiguration mitgegeben. RequireJS bindet das entsprechende JavaScript automatisch ein und startet sie.

Fazit

RequireJS ist ein mächtiges Tool, das alleine schon durch die Kapselung in verschiedene Module dem Entwickler sehr viele Vorteile bringt. Durch die immer größer werdende Komplexität von Web-Sites bzw. Web-Applikationen sind andere Formen der Organisation notwendig geworden, die in anderen Programmiersprachen schon von Anfang an vorhanden waren.

Dank der Module mit ihren von RequireJS automatisch aufgel√∂sten Abh√§ngigkeiten durch Dependency Injection, lassen sich schnell und einfach, neue Funktionalit√§ten in eine Website implementieren, ohne weitere script-Elemente in den HTML-Quelltext einf√ľgen zu m√ľssen.

Bis auf RequireJS selbst werden alle definierten Abh√§ngigkeiten bzw. Module asynchron geladen, ohne das Rendern der Seite zu blockieren. Da Browser¬†asynchrone Anfragen parallel abarbeiten k√∂nnen, werden insgesamt alle Scripts schneller geladen und fr√ľher ausgef√ľhrt, als es bei einer traditionellen Verwendung von JavaScript m√∂glich w√§re.

Das Ergebnis ist ein gro√üer Vorteil f√ľr alle Beteiligten. F√ľr den Nutzer wird eine Website mit viel JavaScript deutlich schneller dargestellt, w√§hrend die Funktionalit√§ten im Hintergrund nachgeladen werden, ohne dass man etwas davon bemerkt. Aus Sicht der Entwickler bietet RequireJS eine elegante M√∂glichkeit, schnelle neue Funktionen zu entwickeln, Abh√§ngigkeiten einfach aufzul√∂sen und durch die Module strukturierter zu arbeiten.

Abgesehen von TypeKit und Disqus laufen alle JavaScript-Bestandteile dieser Seite als Modul. Zwar l√§sst sich TypeKit problemlos als Modul umsetzen, nur werden die Schriften erst nach dem Rendern der Seite geladen. F√ľr etwa eine halbe Sekunde sind die Fallback-Schriften zu sehen, erst dann die √ľber TypeKit-Schriften dargestellt. Da das nicht Sinn und Zweck von TypeKit und Euch Leser irritiert, wird TypeKit klassisch im head-Element vor den Stylesheets geladen.

Disqus ist dagegen als Wordpress-Plug-In eingebunden und darauf w√ľrde ich auch nur sehr ungern verzichten. Ich k√∂nnte das Plug-In zwar anpassen, aber damit w√§re die Update-F√§higkeit dahin. Da Disqus seine Scripts selbst asynchron l√§dt, sehe ich auch keine Notwendigkeit, den ganzen Aufwand zu betreiben und Module zu schreiben.¬†Allerdings habe ich andere Plug-Ins wie die Lightbox und das Syntax Highlighting rausgeworfen und durch RequireJS-Module ersetzt, die nun fester Bestandteil des Themes sind. Geladen werden sie aber nur, wenn auch Beitr√§ge vorhanden sind, die eine der Funktionen brauchen.

Falls ich Euch Interesse zu RequireJS geweckt habe, k√∂nnt Ihr meine Implementierung inkl. eine Ladesystems f√ľr Module aus dem HTML-Quelltext heraus im GitHub-Repository meines Wordpress-Themes anschauen oder auch einen Fork erstellen, um damit selbst entwickeln zu k√∂nnen. Falls jemand Ideen oder Verbesserungen hat, ich freue mich √ľber jede Anregung und jeden Pull-Request in GitHub.