Benutzung von JavaScript Code Modulen

Das Konzept von JavaScript Code Modulen wurde in Gecko 1.9 eingeführt und dient der gemeinsamen Verwendung von Code in unterschiedlichen Anwendungsbereichen. Module können außerdem dazu genutzt werden, globale JavaScript Singletons anzulegen, wofür früher JavaScript XPCOM Objekte nötig waren. Ein javaScript Code Modul ist nichts weiter als eine Sammlung von JavaScript code, der an einem bestimmten Ort abgelegt ist. Solch ein Modul wird mit Components.utils.import() oder Components.utils["import"]() in einen bestimmten JavaScript Anwendungsbereich geladen, etwa ein XUL oder JavaScript XPCOM Script.

Erstellen eines JavaScript Code Moduls

Ein sehr einfaches JavaScript Modul sieht so aus:

var EXPORTED_SYMBOLS = ["foo", "bar"];

function foo() {
  return "foo";
}

var bar = {
  name : "bar",
  size : 3
};

var dummy = "dummy";

Wie zu sehen ist, verwendet das Modul reguläres JavaScript, um Funktionen, Objekte, Konstanten und jeden anderen JavaScript Datentyp anzulegen. Das Modul definiert außerdem eine spezielle Variable namens EXPORTED_SYMBOLS. Alle JavaScript Objekte, die in EXPORTED_SYMBOLS angeführt sind, werden aus dem Modul exportiert und in den Anwendungsbereich geladen, der das Modul einbindet. Ein Beispiel:

Components.utils.import("resource://app/my_module.jsm");

alert(foo());         // displays "foo"
alert(bar.size + 3);  // displays "6"
alert(dummy);         // displays "dummy is not defined" because 'dummy' was not exported from the module
Hinweis: Wenn Änderungen an einem Modul getestet werden sollen, muss immer darauf geachtet werden, die Versionsnummer der Anwendung zu erhöhen. Andernfalls kann es passieren, dass die vorherige Version des Moduls verwendet wird.

Die URL eines Code Moduls

Wie im obigen Beispiel ersichtlich ist, benötigt man für den Import eines Code Moduls eine URL. (Die URL im Beispielcode ist "resource://app/my_module.jsm".)

Code Module können nur mit einer chrome: (), resource:, oder file: URL geladen werden.

  • Wenn Du eine Erweiterung für Firefox 4 entwickelst und bereits ein chrome.manifest mit einer content Anweisung verwendest, kannst du das Modul einfach in den content Ordner legen und auf die selbe Art referenzieren wie andere content Dateien, mit chrome://<yourextension>/content/<yourmodule>.jsm.
  • Wenn deine Erweiterung Mozilla 1.9x (Firefox 3.x) unterstützen soll, muss eine neue Resourcen URL registriert werden. Wie das geht, wird weiter unten in "Extending resource: URLs" behandelt.

Gemeinsame Verwendung von Objekten

Eine besonders wichtige Eigenschaft von Components.utils.import() ist es, dass Module beim erstmaligen Importieren im Zwischenspeicher abgelegt werden und alle weiteren Importvorgänge diese gespeicherte Version verwenden anstatt das Modul neu zu laden. Das bedeutet, dass das Modul von allen Anwendungsbereichen die es importiert haben, gemeinsam verwendet wird. Alle Änderungen von Daten, Objekten oder Funktionen sind in jeden Anwendungsbereich, der das Modul benutzt, sichtbar. Wenn wir beispielsweise unser einfaches Modul in zwei JavaScript Bereichen importieren, können Änderungen aus einem Bereich auch im anderen Bereich beobachtet werden.

Bereich 1:

Components.utils.import("resource://app/my_module.jsm");

alert(bar.size + 3);  // displays "6"

bar.size = 10;

Bereich 2:

Components.utils.import("resource://app/my_module.jsm");

alert(foo());         // displays "foo"
alert(bar.size + 3);  // displays "13"

Durch diese Eigenschaft können Module als Singleton Objekte verwendet werden um Daten zwischen verschiedenen Fenstern, oder auch zwischen XUL Scripten und XPCOM Objekten, auszutauschen.

Hinweis: Jeder Bereich, der ein Modul importiert, erhält eine Kopie (by-value) aller exportierten Symbole des Moduls. Änderungen der Werte in dieser Kopie werden nicht an andere Bereiche weitergeleitet (allerdings werden Objekteigenschaften per Referenz manipuliert).

Bereich 1:

Components.utils.import("resource://app/my_module.jsm");

bar = "foo";
alert(bar);         // displays "foo"

Bereich 2:

Components.utils.import("resource://app/my_module.jsm");

alert(bar);         // displays "[object Object]"

Der Haupteffekt dieser by-value Kopie besteht darin, dass globale Variablen eines einfachen Typs nicht in allen Bereichen geteilt werden. Variablen sollten daher immer in eine Wrapper Klasse gehüllt werden (siehe bar im obigen Beispiel).

Code Module Entladen

Requires Gecko 7.0(Firefox 7.0 / Thunderbird 7.0 / SeaMonkey 2.4)

Mit Components.utils.unload() kann ein zuvor importiertes Modul wieder entladen werden. Nach dem Aufruf dieser Funktion sind zwar alte Referenzen auf das Modul weiterhin gültig, aber ein nachträgliches Importieren wird das Modul neu laden und ihm auch eine neue Referenz zuweisen.

Beispiele

Erweiterung von resource: URLs

Vor der Einführung von Gecko 2.0 war der übliche Weg, ein Modul einzubinden, die resource: URL. Die grundlegende Syntax sieht wie folgt aus:

resource://<alias>/<relative-path>/<file.js|jsm>

<alias> bezeichnet einen Ort, üblicherweise einen physikalischen Ort relativ zur Anwendung oder der XUL Laufzeitumgebung. Die XUL Laufzeitumgebung bietet verschiedene vordefinierte Aliase:

  • app - Speicherort der XUL Anwendung
  • gre - Speicherort der XUL Laufzeitumgebung

Der Pfad in <relative-path> kann beliebig tief sein und ist immer relativ zum in <alias> angegbenen Ort. Der übliche relative Pfad, der auch von XUL Runner und Firefox verwendet wird, ist "modules". Code Module sind einfache JavaScript Dateien mit einer .js oder .jsm Dateiendung.

Das verwendete <alias> muss für jede Erweiterung einzigartig sein, da alle definierten Alias Werte aller Anwendungen und Erweiterungen in einem gemeinsamen Namespace gespeichert werden.

Mittels chrome.manifest

Die einfachste Möglichkeit, ein neues Alias für eine Erweiterung oder XUL Anwendung anzulegen, besteht darin, eine Zeile wie die Folgende in chrome manifest einzufügen:

resource aliasname uri/to/files/

Wenn zum Beispiel die XPI deiner foo Erweiterung einen top-level Ordner namens modules/ besitzt, in dem sich das bar.js Modul befindet (das beudeutet, der modules/ Ordner liegt neben chrome.manifest und install.rdf), kann ein Alias etwa so angelegt werden:

resource foo modules/

(Beachte den Schrägstrich am Ende!) Danach kannst du das Modul wie folgt in deinen JavaScript Code importieren:

Components.utils.import("resource://foo/bar.js");

Programmatisches Hinzufügen eines Alias

Aliase auf Pfade, die als nsILocalFile dargestellt werden können, können auch programmatisch angelegt werden. Zum Beispiel:

// Import Services.jsm unless in a scope where it's already been imported
Components.utils.import("resource://gre/modules/Services.jsm");

var resProt = Services.io.getProtocolHandler("resource")
                      .QueryInterface(Components.interfaces.nsIResProtocolHandler);

var aliasFile = Components.classes["@mozilla.org/file/local;1"]
                          .createInstance(Components.interfaces.nsILocalFile);
aliasFile.initWithPath("/some/absolute/path");

var aliasURI = Services.io.newFileURI(aliasFile);
resProt.setSubstitution("myalias", aliasURI);

// assuming the code modules are in the alias folder itself

Anmerkungen

Eigene Module und XPCOM Komponenten

In früheren Versionen als Gecko 2.0 werden JavaScript XPCOM Komponenten bereits vor der Chrome Registrierung geladen. Das bedeutet, dass Components.utils.import() nicht mit einer eigenen URL in einer Komponente verwendet werden kann. Eine mögliche Lösung des Problems besteht darin, den Aufruf von Components.utils.import() in den Konstruktor der XPCOM Komponente zu legen (Diskussion).

Packaging Anmerkungen

Unter normalen Umständen gibt es keinen guten Grund JavaScript Code Module in einer JAR Datei zu verpacken. Von Firefox 3.6 wird diese Form der Distribution gar nicht unterstützt. Nur in einem Anwendungsfall kann es sinnvoll sein, Module in JAR zu packen: In einer Erweiterung, die nur Firefox 4 unterstützt, und die ungepackt installiert werden muss. In allen anderen Fällen wird dadurch nur unnötigerweise die Kompatibilität zerstört.

CommonJS Module Importieren

Die hier beschriebenen JavaScript Code Module sind nicht zu verwechseln mit CommonJS modules, aber auch CommonJS Module können in jeden Bereich importiert werden, der Components.utils.import unterstützt. Der folgende Aufruf wird require() in deinen Anwendungsbereich importieren:

const { require } = Cu.import("resource://gre/modules/commonjs/toolkit/require.js", {})

Damit können danach andere CommonJS Module importiert werden. Auch Add-on SDK Module können auf die selbe Art importiert werden, wie aus einem SDK Add-on:

// import the SDK's base64 module

var base64 = require("sdk/base64");
base64.encode("hello"); // "aGVsbG8="

Auch andere CommonJS Module können importiert werden, solange der Pfad bekannt ist:

// import my module

var myModule = require("resource://path/to/my/module.js");

In diesem Fall kann es aber ratsam sein, einen eigenen Loader zu entwickeln, damit das paths Attribut selbst gewählt werden kann.

Weiterführende Links