Wikipedia:Technik/Skin/JS/ResourceLoader

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

ResourceLoader


Der ResourceLoader (RL) ist ein Bestandteil von MediaWiki, über den sich dynamisch JS- und CSS-Definitionen in das HTML-Dokument einer Wiki-Seite einbinden lassen.

Die Funktionalität stand mit der Version MW 1.17 im Februar 2011 zur Verfügung und wird weiter ausgebaut.

Ausschlaggebend für die Entwicklung waren folgende Gesichtspunkte:

  • Die Anzahl der verfügbaren unabhängigen Skriptpakete liegt 2015 bei etwas über 200 – künftig ist noch mit weiterer Zunahme zu rechnen. Das ist irgendwann nicht mehr ohne Hilfsmittel zu überschauen und zu pflegen.
  • Nicht alle Funktionen werden immer, von jedem Benutzer, für jeden Seiteninhalt, für jede individuelle Konfiguration benötigt. Es ist nicht mehr möglich, wie früher einfach immer zu jeder Seite alle Skripte mitzuliefern.
  • Die Ressourcen erwiesen sich oft als veraltet bei den Lesern und in deren Browser. Der Browser-Cache und vor allem die Proxy-Server setzen die mitgelieferten Angaben zum Cache-Management nicht richtig um. Nunmehr wird die Aktualität dadurch sichergestellt, dass eine neue URL generiert wird, wenn sich etwas am Inhalt ändert. Dadurch muss zwangsweise eine neue Version beim Server abgerufen werden.
  • Ab Frühjahr/Mitte 2011 begann zunächst Google Chrome, danach Firefox mit dem „asynchronen Laden“ von Ressourcen. Damit funktionieren alte Skripte nicht mehr, die davon ausgehen, dass ein Skript nach dem anderen in einer festen und bekannten Reihenfolge ausgeführt wird.

Anwendungsbereich

[Quelltext bearbeiten]

Zentrale MediaWiki-Software

[Quelltext bearbeiten]

Alle Standardsoftware von MediaWiki wird inzwischen mit Methoden des RL eingebunden: /resources/*

Projektweite Standardressourcen

[Quelltext bearbeiten]

Dies sind:

Modulname: site

Projektweite Helferlein

[Quelltext bearbeiten]

Die Helferlein können auch mittels RL geladen werden, dafür wird auf MediaWiki:Gadgets-definition dies mit eventuellen Abhängigkeiten angegeben; damit werden auch für jede Version spezifische URL generiert und es wird komprimiert.

  • Siehe dazu auch: mw:Extension:Gadgets
  • Zukünftig soll es irgendwann mal vielleicht die Möglichkeit geben, weltweit verfügbare Module an einer zentralen Stelle vollständig vorzuhalten (Version 2).

Standardressourcen des momentanen Benutzers

[Quelltext bearbeiten]

Dies sind die Einstellungen:

Modulname: user (auch bei nicht angemeldeten Benutzern „geladen“, aber leer)

Damit gleichzeitig schon geladen sind (bei angemeldeten Benutzern) globale Einstellungen, falls vorhanden; Modulname: ext.globalCssJs.user

Benutzerskripte

[Quelltext bearbeiten]

Auf eigenständige Ressourcen der Benutzer ist der ResourceLoader 2012 nicht besonders zugeschnitten; er lässt sich aber trotzdem einsetzen: Anwendungsbeispiel.

PHP auf dem Server

[Quelltext bearbeiten]

Diese Seite beschreibt schwerpunktmäßig den ResourceLoader auf der Browser-Seite in JavaScript; als Gegenstück gibt es einen analogen in PHP geschriebenen ResourceLoader auf den Servern, namentlich unter /w/load.php (oder früher auf bits.wikimedia.org) zur Zusammenstellung.

Ein Modul ist die Zusammenfassung folgender Komponenten, die einen inhaltlichen Bezug haben sollten:

  1. Modul-Name (unentbehrlich)
  2. JavaScript (praktisch immer)
  3. CSS (optional)
  4. Systemnachrichten (optional)

Die Quellen können von einer oder mehreren statischen Seiten stammen oder spontan generiert worden sein.

Mit Vollzugsmeldung für einen Modul-Namen bestätigt die Software, dass eine bestimmte Funktionalität (JS-Funktionen, CSS-Deklarationen) einsatzbereit ist. Es wird keine Aussage darüber getroffen, woher diese Funktionalität stammt und welche von mehreren Varianten vorliegt.

  • Jedes Modul wird durch einen Modul-Namen (eigentlich: -Identifikator, -Bezeichner) angesprochen.
  • Grundsätzlich ist es egal, wie dieser Name lautet; es könnte auch „Rumpelstilzchen“ heißen, solange es nicht zu Namenskonflikten kommt.
  • Es haben sich aber einige Regeln herausgebildet:
    • Es werden ASCII-Zeichen (ohne Leerzeichen) verwendet.
    • Überwiegend werden Kleinbuchstaben benutzt.
    • Gliederung durch Punkte.
  • Für individuelle Benutzer hat sich noch kein Schema herauskristallisiert. Das Präfix user. wird bereits verwendet für user.groups, user.options und user.tokens – sollte für weltweite Systemdefinitionen reserviert bleiben.

Gebräuchliche Namen sind beispielsweise:

mediawiki.util
Ein Modul mit verschiedenen Hilfsfunktionen (Quelle)
jquery.colorUtil
Ein Modul mit Hilfsfunktionen zu Farben (Quelle)
site
Projektweite Standardressourcen (für das momentane Projekt)
user
Standardressourcen des momentanen Benutzers
user.groups
Ressourcen, die spezifisch für Benutzergruppen sind
user.defaults
Konfiguration für alle Benutzer, einschließlich der nicht angemeldeten
user.options
Spezifische Konfiguration des momentan angemeldeten Benutzers; überschreibt ggf. Komponenten von user.defaults
ext.gadget.Zeitzonenkonverter
Projektweites Helferlein; hier: MediaWiki:Gadget-Zeitzonenkonverter
ext.irgendein-name
Erweiterungen; Extensions auf dem Server oder in JavaScript.
ext.gadget.irgendein-helferlein
Erweiterung oder Gadget in JavaScript.

Siehe auch: mw: Default modules (englisch)

Aus dem Modulnamen lässt sich durch Menschen erraten, auf welcher Seite eine Quelle gespeichert sein könnte; aber dies lässt sich nicht durchgängig automatisch generieren. Es besteht der gleiche feine Unterschied wie zwischen URL und URN.

  • Das Modul kann on-the-fly generiert worden sein; dann gibt es keine Seite.
  • Das gleiche Modul kann in verschiedenen Projekten gespeichert sein; es kann in unterschiedlichen Entwicklungsständen verfügbar sein.
  • Von welcher URL das Modul geladen wurde, spielt letztlich keine Rolle. Entscheidend ist, dass die zugesicherte Funktionalität unterstützt wird.

Die Skriptquellen und CSS-Stile werden nicht einzeln vom Server abgerufen, sondern gebündelt. Dies beschleunigt den Ablauf und senkt den Ressourcenverbrauch; siehe „Webpackaging“.

Es werden in den folgenden Situationen Pakete von Modulen gebildet:

  1. Wenn die welt- und projektweite Grundausstattung einer Seite bereitgestellt wird („Startup“) – spezifisch für jede Skin.
  2. Wenn die Gadgets anhand der Benutzerkonfiguration ausgeliefert werden.
  3. Wenn eine .loader.using()-Anforderung abgearbeitet wurde und dabei fehlende Module festgestellt wurden.

Es werden auf einen Schlag einerseits alle Skripte, andererseits alle angeforderten CSS-Definitionen geladen. Jede URL erhält eine Versionsidentifikation (Zeitstempel).

Im Standardmodus (debug=false) werden dabei außerdem:

  • alle Skripte bzw. Stile hintereinander kopiert und unter jeweils einer einzigen URL bereitgestellt.
    • Die URL erhält einen Zeitstempel gemäß der jüngsten (numerisch größten) Komponente.
  • Die Ressource wird minimiert.
    • Das gepackte Ergebnis wird in einem Cache zur Wiederverwendung eingelagert.
    • Weil gerade im allgemeinen Bereich der Seite immer dieselben Kombinationen von Ressourcen auftreten, kann der Server (und auch Proxy-Server) diese fertigen Pakete für alle Benutzer vorhalten.
  • Weil nur eine URL abgefragt wird, ist der Verkehr zwischen Benutzern und Server geringer.

Debug-Modus und Komprimierung

[Quelltext bearbeiten]

Jede Ressource lässt sich in zwei Varianten vom Server laden:

  1. debug=false   (Standardvorgabe)
    • Für alle gleichzeitig benötigten Skripte oder CSS-Definitionen wird eine einzige URL gebildet und darunter auch hintereinander weg geschrieben als eine kompakte Ressource vom Server geliefert.
    • Der Code jedes einzelnen Skriptes und CSS wird komprimiert:
      • Alle Kommentare werden gelöscht.
      • Alle entbehrlichen Whitespace-Zeichen werden entfernt.
      • Es wird eine Zeilenlänge von etwa 1000 Zeichen durch Verkettung einzelner Statements hergestellt; die (korrekte) Syntax von JavaScript und CSS erlaubt das.
    • Bei erkannten groben Syntaxfehlern in den Quellen wird die Ressource nicht ausgeliefert, statt dessen eine Fehlermeldung gesendet.
    • Die Komprimierung ist allerdings nicht ganz unproblematisch; bei syntaktisch falscher Programmierung kann es zu falschen Konstruktionen kommen, und es werden Syntaxkonstrukte nicht erkannt, die aber auch nicht von jedem Browser richtig interpretiert werden.
    • In den Zeilen von 1000 Zeichen Länge ist bei auftretenden Folgefehlern keine unmittelbare Analyse möglich.
  2. debug=true
    • Es erfolgt keine Komprimierung.
    • Jede Ressource wird einzeln geliefert, so wie sie auch im Quellprogramm definiert ist. Damit sind Zeilennummern identisch.
    • Standardmäßig wird ein Hilfsmittel geladen, das die Auswertung in der Fehlerkonsole unterstützen soll.

Browser-Cache

[Quelltext bearbeiten]

An die mittels load.php gebildete URL wird die jeweils jüngste Änderungszeit auf dem Server mit einer Genauigkeit von zehn Sekunden angefügt. Damit hat jedes Paket eine eindeutige URL, die sich von veralteten Versionen im Browser-Cache unterscheidet. Somit bleibt Browsern und Proxy-Servern nichts anderes übrig, als die aktuelle Version herunterzuladen.

Für die Standardressourcen des Projekts und Benutzers sowie Gadgets wird vom Server dieser Zeitschlüssel an die URL angefügt.

Betreffend der weltweit einheitlichen zentralen MediaWiki-Software wird ein Verzeichnis aller erhältlichen Module mit den jeweiligen Zeitschlüsseln in jede HTML-Seite eingeschrieben. Diese Liste hat selbst aber zurzeit (MW 1.18/1.20) keinen Versionscode; eine Veränderung eines Zeitschlüssels eines Moduls hätte somit keine neue URL zur Folge. Allerdings gelten die Module der zentralen Software als stabil und werden normalerweise zur Laufzeit einer Version nicht verändert.

Im mw-Objekt steht der ResourceLoader als Komponente .loader zur Verfügung.

mw.loader.addSource(modules, props)
Registrierung einer URL (für load.php gedacht) zu einem Quellcode.
mw.loader.getModuleNames()
Array mit allen bekannten Modulnamen.
mw.loader.getState(module)
Status einer Ressource abfragen.
  • module: String mit Modulname.
Rückgabewert ist eine Zeichenkette, wenn das Modul bislang irgendwie bekannt geworden war.
  • "registered" – Modulname ist bekannt – definiert in .loader.register().
  • "loading" – Modul wird zurzeit geladen.
  • "loaded" – Dieses Modul wurde erfolgreich einzeln geladen.
  • "ready" – Dieses Modul und alle seine Abhängigkeiten wurden geladen.
  • "error" – Dieses Modul hat möglicherweise einen Syntaxfehler.
  • "missing" – Zugriff auf die URL dieses Moduls gescheitert.
  • null – Modulname ist nicht bekannt.
Wenn eine Wiki-Seite geladen wurde, sind die Registrierungsinformationen aller nutzbaren Module (einschließlich Versionsinfo) definiert; zunächst als registered, ggf. auch schon als ready.
mw.loader.getVersion(module)
Versionsinformation einer Ressource abfragen – definiert in .loader.register().
  • module: String mit Modulname.
Rückgabewert ist eine Zeichenkette, wenn das Modul bekannt ist.
  • String – ISO8601 YYYYMMDDThhmmssZ, oder "19700101T000000Z" wenn nicht vereinbart.
  • null – Modulname ist nicht bekannt
mw.loader.implement(module, script, style, msgs, templates)
Deklaration eines neuen Modulnamens, Verbindung mit der zugehörigen Implementierung und Laden.
Alle Parameter bis auf den Modulnamen sind optional; gleichwohl muss sinnvollerweise mindestens einer die Implementierung enthalten.
  • module: String mit Modulname. Darf noch nicht mit implement() vereinbart gewesen sein.
  • script: Auszuführende Funktion (Code) oder Array von URL mit JS-Code
  • style: Objekt; entweder aus direkten CSS-Deklarationen oder Liste der URL von CSS-Deklarationen – nicht null.
  • msgs: Objekt; benötigte Systemnachrichten, die den .messages hinzuzufügen sind, als key:value – nicht null.
Wirkung: Die Funktion wird ausgeführt oder die Skript-Quellen werden von den angegebenen URL abgerufen, indem <script src=> in das Dokument eingefügt wird. War das Laden erfolgreich, wird der Status loaded gesetzt; missing oder error wenn ein Fehler detektiert wurde. Über die Registrierung hinaus erfolgt zurzeit auch immer das tatsächliche Laden. Im Erfolgsfall wird nach aufgelaufenen unbefriedigten Abhängigkeiten gesucht; sind keine mehr vorhanden, wird ready für das Modul ausgewiesen. Schließlich werden eventuelle Funktionen aus der .loader.using()-Warteschlange ausgeführt.
mw.loader.inspect(reports)
Analysierte die bekannten und geladenen Module zu Testzwecken.
Gab auf Debugging-Konsolen (Firebug, zurzeit nicht aber Dragonfly) eine Liste der geladenen Modul-IDs aus; dazu die Code-Größe statischer Module und Angaben zum CSS. Die Tabelle war ggf. sortierbar, falls console.table() unterstützt wird.
Nicht mehr verfügbar; siehe jedoch: mw.inspect
mw.loader.load()
  • .loader.load(URL, MIME, async)
    Siehe Laden anderer Skripte für JavaScript und CSS, etwa Benutzerskripte.
    • URL: string; nur // oder http:// oder https://
    • MIME: string; Vorgabe application/javascript – sonst nur text/css (momentan ist noch text/javascript Vorgabe)
    • async: boolean
      • true (Vorgabe) – URL asynchron laden, auch wenn document.ready noch nicht eingetreten war.
      • false – abwarten, bis document.ready und dann erst mit dem Laden beginnen.
  • .loader.load(modules)
    Laden eines Moduls (oder mehrerer Module) einschließlich aller weiteren Module, von denen diese abhängen.
    • modules: String mit Modulname, oder Array mit Modulnamen.
    War das Modul bereits geladen gewesen, sollte nichts weiter passieren.
    Soll anschließend eine Funktion ausgeführt werden, wäre .loader.using() zu verwenden. Wenn das Skript selbst eine Art AutoRun-Funktion enthält, kann .loader.load() benutzt werden.
mw.loader.register(module, version, dependencies, group, source)
Registrierung eines Moduls zum Abruf über load.php (von //bits.wikimedia.org)
Für Benutzerskripte nicht nutzbar.
  • module: String mit Modulnamen, oder Array.
  • version: Versionsnummer; erlaubt Unterscheidung im Browser-Cache.
    Erwartet wird die Anzahl der Sekunden seit dem 1. Januar 1970, 00:00:00.
    Es handelt sich nur um den Zeitpunkt, zu dem die Ressource im Dateisystem ankam – nicht um eine inhaltliche Versionsnummer im Sinne einer Weiterentwicklung. Eine jüngere Version kann einen älteren Inhalt beschreiben, beispielsweise nach einem Revert.
  • dependencies: Abhängigkeiten; Array→String oder einzelner String oder Function→Array.
  • group: Übergeordneter Modulname, wenn module ein Untermodul ist.
  • source: aktuelles Wikiprojekt (wgLoadScript; Vorgabe), oder anderes Wiki; Pfad vor load.php
Erster Parameter Pflicht, Rest optional.
mw.loader.require(module)
Status einer Ressource sichern: Wenn noch nicht bereit, dann Fehler auslösen.
Ermöglicht das Aufspüren vergessener Abhängigkeiten aus tieferen Schichten.
  • module: String mit Modulname
Gleichgesetzt mit window.require() – nur in dieser Form soll es in produktivem Code verwendet werden.
Bei über .using() angeforderten Modulen wird die Funktionsdefinition zurückgegeben.
mw.loader.state(modules)
Setze den Status von Ressourcen.
Dabei ist modules ein object mit Zuweisungen ID→state.
Siehe .loader.getState()
mw.loader.store
Abspeicherung im persistenten WebStorage verwalten (Objekt).
  • .loader.store.clear()
    Lösche im Browser hinterlegten MediaWiki-Code.
  • .loader.store.enabled = false;
    Verhindere Abspeicherung im localStorage des Browsers.
mw.loader.using(dependencies, ready, error)
Starte eine Funktion, nachdem ein Modul geladen wurde, oder wenn dies fehlschlug; lade fehlende weitere Module nach.
Rückgabewert ist ein Objekt vom Typ jQuery.Promise, auf das weitere Callback-Vereinbarungen angewendet werden können. Die reguläre done-Funktion ist bereits der zweite Parameter.
  • dependencies: String oder Array – Name/n der Module, die vor der Ausführung von ready geladen sein müssen. Schließt automatisch diejenigen Module ein, von denen sie intern abhängen.
  • ready: Funktion, die ausgeführt wird, nachdem alle Module geladen worden sind.
  • error: (optional) Funktion, die ausgeführt wird, falls das zusätzliche Laden fehlschlug.
Anwendungsbeispiel

Die restlichen Funktionen sind zurzeit für lokale Projekte und Benutzer kaum einsetzbar oder nicht empfehlenswert: addStyleTag(), resolveIndexedDependencies(), work().

Es gibt für den Fehlerfall auch ein Event resourceloader_exception(e, [module], source).

Benutzer-Skript

[Quelltext bearbeiten]

Um ein bibliotheksartiges Benutzer-Skript (übergangsweise) für bedarfsweises Laden durch den ResourceLoader auszustatten, ist wie folgt vorzugehen:

  1. Eindeutigen und langfristig haltbaren Modul-Namen vergeben.
  2. Am Ende des Skriptes mit .loader.state(name, "ready") das abgeschlossene Laden deklarieren. Falls das Skript zuvor auf eine andere Weise geladen würde, wird ein unnötiger und möglicherweise zu Fehlfunktionen führender mehrfacher Abruf vermieden.
  3. Auf sorgfältige und syntaktisch einwandfreie Programmierung achten.
  4. Den Anwendern den Modul-Namen bekanntgeben sowie mögliche URL mit den Quellen.
  5. Für den weiteren Einsatz siehe Anwendungsbeispiel.
  6. Komprimierung und Versionsaktualisierung erfolgen dabei jedoch nicht.

Bei einem MediaWiki:Gadget-Name.js sind Besonderheiten betreffend Ladereihenfolge und Ausführung zu beachten.

Bisher war es so, dass zuerst die Helferlein (Gadgets) geladen und ausgeführt wurden, danach die Standardressourcen des Benutzers.

Das änderte sich 2011/2012:

  1. Schnelle Browser mit asynchronem Laden von Ressourcen halten sich nicht mehr unbedingt an eine Reihenfolge im Ablauf.
  2. Der ResourceLoader stellt alle vom Benutzer angeforderten (und bereits als dazu geeignet markierten) Gadgets zu einem Paket zusammen.
    • Dieses wird standardmäßig nach dem Benutzerskript-Paket user geladen. Darauf kann man sich jedoch auch nicht verlassen; ein Browser könnte beide Pakete gleichzeitig abfordern und es kann von Netzwerkbedingungen abhängen, welches zuerst bereit ist.

Man darf bei Neuprogrammierungen oder grundlegenden Überarbeitungen grundsätzlich keine Reihenfolgen mehr unterstellen, sondern muss explizit eine erforderliche Synchronisation durch Deklaration von Abhängigkeiten herstellen.

Definition und Abhängigkeiten

[Quelltext bearbeiten]

Beim Speichern im Namensraum für Gadgets müssen in jedem Fall die benötigten dependencies in einem Kommentar am Beginn des Skriptes angegeben werden, damit sie richtig in MediaWiki:Gadgets-definition eingepflegt werden können.

Es würde auch nicht viel schaden, die Funktion in eine .using-Klausel einzubetten, wenn zu erwarten ist, dass das Skript auch öfters außerhalb der automatischen Helferlein geladen werden soll. Das wäre bei aufwändigen Skripten etwa mit API-Abfragen anzunehmen.

Die auf MediaWiki:Gadgets-definition dafür vereinbarten Skripte können allerdings spontan über .loader.load("ext.gadget.name"). statt .loader.load(URL) bedarfsweise geladen werden: Im ersten Fall versorgen sie sich dann selbsttätig mit ihren aktuell benötigten dependencies, und wenn sie bereits geladen waren, passiert nichts Überflüssiges oder Selbstzerstörendes – und die aktuellste Version wird abgerufen. Genauso mit .loader.using().

Mit .loader.load(URL) kann es hingegen zum Überschreiben von Initialisierungs- und Konfigurationsdaten kommen, wenn das Skript auf anderem Weg bereits geladen war; auch werden die dependencies ggf. nicht befriedigt und es kann eine veraltete Version im Browser-Cache liegen.

Benutzerkonfiguration

[Quelltext bearbeiten]

Im Benutzerskript kann nicht mehr stehen:

 if (window.thisGadget) {
    window.thisGadget.thatOption = true;
 }

Dies muss künftig bei den Anwendern geschrieben werden als:

 if (! mw.libs.thisGadget) {
    mw.libs.thisGadget  =  { };
 }
 mw.libs.thisGadget.thatOption = true;

Auf der Seite MediaWiki:Gadgets-definition muss es bei konfigurierbaren Skripten heißen:

   thisGadget[ResourceLoader|dependencies=user]|thisGadget.js

Im Gadget muss eingangs geprüft werden, ob vom Anwender das Anwendungsobjekt schon definiert wurde:

 if (! mw.libs.thisGadget) {
    mw.libs.thisGadget  =  { };
 }
 mw.libs.thisGadget.init  =  function () { .................

Grundsätzlich sollte kein Anwendungsobjekt einfach überschrieben und ungeprüft definiert werden; es könnte im Lauf vorangegangener Prozeduren bereits mit Inhalten und Funktionalität gefüllt worden sein.

In einem mittels mw.loader.load() geladenen Skript hat man im Unterschied zum Gadget die Kontrolle über die Ladereihenfolge. Wenn Benutzer ein Skript selbst laden, können sie vorher die Optionen setzen.

Wenn Benutzer über die Gadget-Option das Helferlein automatisch geladen haben, wird auch der Gadget-Status auf ready gesetzt.

Da es aber ein normales Skript ist, könnte die Seite auch später dynamisch mit .loader.load() geladen werden. Um für diesen Fall den korrekten Status zu verdeutlichen und Interaktion mit anderen Skripten sicherzustellen, sollte zusätzlich am Ende vereinbart werden:

mw.loader.state( { "ext.gadget.meinHelferlein": "ready" } );

Weitere Informationen

[Quelltext bearbeiten]

(alle englisch)

MediaWiki: ResourceLoader – Projektseite