CommonJS – Plattformübergreifende JavaScript Spezifikationen

Avatar von Thomas Steur

JavaScript hat längst den Weg aus dem Browser heraus geschafft. Es existieren inzwischen viele Interpreter und die Sprache wird auf einer Vielzahl von unterschiedlichen Plattformen eingesetzt. Bei JavaScript ist dabei sehr wenig standardisiert. Hauptsächlich gibt es lediglich einige API’s für Browserbasierte Applikationen. Umso wichtiger ist es, einen modularen Code zu haben, der auf möglichst vielen dieser Systeme wiederverwendet werden kann. Diesem Problem nimmt sich CommonJS seit über 2 Jahren an.

Was ist CommonJS?


CommonJS, kurz CJS, ist eine Mailingliste / Wiki / Initiative die es sich zum Ziel gemacht, Spezifikationen/API’s für JavaScript zu definieren. Ähnlich zu Python, Ruby und Java. Das Ziel ist Standards zu haben, worauf Entwickler Applikationen und Frameworks erstellen können, die unmodifiziert auf allen Interpretern (Rhino, Spidermonkey, v8 und JavaScriptCore) und Umgebungen (Node.js, Titanium Mobile, CouchDB, v8cgi, …) funktionieren.

Näher gehe ich in diesem Artikel auf den Module-Standard ein. Es ist einer der wichtigsten Standards von CJS. Er wird aktuell unter anderem von Node.js, CouchDB und Titanium Mobile implementiert. Für Browser gibt es ebenfalls mehrere Bibliotheken, welche die Verwendung der Module im Browser ermöglichen.

Wie entsteht eine Spezifikation?


Der Prozess beim Entwickeln der API’s soll ermöglichen, das hoch gesetzte Ziel zu erreichen. Bis zu einer finalen Version einer Spezifikation, durchläuft es vier Zustände.

Vorschlag

Wer eine Idee für eine Spezifikation hat, erstellt im Wiki eine Seite und erläutert diese. Bereits funktionierender Code wird dabei gerne gesehen. Der Ersteller der Seite ist dafür verantwortlich, dass die Seite gepflegt und dass darüber diskutiert wird.

Diskussion

Als nächstes wird über die Idee gesprochen. Dazu wird eine Nachricht an CommonJS mit dem Link zur Webseite gesendet. Die Diskussion dauert einige Zeit bis alle abweichenden Meinungen konkretisiert sind.

Entscheidung

Irgendwann kommt es zu einer Abstimmung. Das Ergebnis wird auf der Wiki Seite fest gehalten. Es zählt dabei nicht zwingend die Mehrheit. Haben zum Beispiel Personen, die den Code implementieren, triftige Gründe die dagegen sprechen, so zählen diese mehr. In einem solchen Fall sollten Alternativen vorgeschlagen werden.

Implementierung

Um diesen Zustand zu erreichen, müssen einige einschlägige Projekte die Spezifikation umgesetzt und Links zu den Implementierungen auf der Wiki-Seite hinzugefügt haben. Sind genügend Projekte vorhanden, können Änderungen nur durch Anstoßen des Prozesses von ganz vorne eingebracht werden. So kann man sich sicher sein, dass der Standard nicht ohne Grund geändert wird. Zu einigen Spezifikationen lassen sich Unit-Tests finden. Damit lässt sich fest stellen, ob man die Spezifikation korrekt umgesetzt hat.

CommonJS Module Spezifikation


Damit existierender Code in verschiedenen Umgebungen wiederverwendet werden kann, wurde das Module-System entwickelt.

Module sollen dem DRY (Don’t Repeat Yourself) Prinzip dienen. Dabei werden zusammengehörende Funktionen in Module ausgelagert. Mittels der "require()" Funktion kann ein Modul eingebunden und die darin enthaltenen Funktionen genutzt werden.

Dabei hat jedes Modul seinen eigenen Kontext. Auf Objekte, Funktionen oder Variablen von anderen Modulen kann dabei nicht zugegriffen werden. Es sei den, ein anderes Modul wird mittels "require()" geladen. Erstellt man innerhalb von einem Modul eine Variable, so ist diese innerhalb von diesem Modul privat und nicht im globalen Kontext verfügbar. Ich finde diese Variante sehr nützlich, da es einen dazu zwingt oder zumindest dazu führt, besseren modularen Code zu schreiben.

Innerhalb eines Moduls existiert eine spezielle Variable "exports", um innerhalb von einen Modul Variablen, Objekte oder Funktionen nach außen verfügbar zu machen. Alles was dieser Variablen zugewiesen wird, ist später über das Modul erreichbar.

Beispiel:
library/cart.js

// Dies ist keine globale Variable und lediglich innerhalb des Moduls verfügbar 
var total = 0; 
 
// Die exports Variable ist in jedem Modul verfügbar. Darüber werden Objekte, Variablen, Funktionen nach außen verfügbar gemacht 
exports.addToCart = function(product, price) { 
    // Hier wird ein anderes Modul eingebunden 
    require('console').log('buy', product, price); 
 
    total = total + price; 
}; 
  
exports.getTotal = function () { 
    return total;  
}; 
 
exports.url = '/cart';

Modul einbinden

Ein Modul wird über einen String identifiziert. Ein Modul muss dabei nicht zwingend eine Datei sein. Theoretisch könnte das Modul zum Beispiel auch aus einer Datenbank stammen. In der Praxis ist dies jedoch eine Datei. Der String, der das Modul identifiziert, entspricht dem Pfad zur jeweiligen Datei, wobei auf die Dateiendung ".js" verzichtet wird. Für jedes Modul wird also eine Datei angelegt. Die "require()" Funktion gibt dann ein Objekt mit sämtlichen Properties und Funktionen zurück, welche zuvor der Variable "exports" zugewiesen wurden.

// Einbinden des Cart Moduls 
var cart = require('library/cart'); 
cart.addToCart('Apple iPad 3', '479');  
location = cart.url; 
  
// Alternativ ist der Zugriff auch wie folgt möglich 
require('library/cart').addToCart('Apple iPad 3', '479'); 
 
// total ist undefined da diese Variable nur im 'cart' Modul verfügbar ist, nicht aber im globalen Kontext 
console.log(total);

Stateful

Module sind statusbehaftet. Das heisst, wann immer ein Modul eingebunden wird, wird dieses nicht neu geladen oder neu ausgeführt. Es behält seinen Status, egal wo es eingebunden wird.

Beispiel:

var cart = require('library/cart');                                     // total = 0 
cart.addToCart('Apple iPad 3', '479');                                  // total = 479 
// Das Modul wird neu geladen, aber total bleibt nach wie vor bei 479
require('library/cart').addToCart('Asus Transformer Prime', '599')      // total = 1078 
cart.getTotal();                                                        // Gibt 1078 zurück

Eine weitere Variante

Einige Plattformen erlauben weiterhin eine alternative Schreibweise, zum Beispiel Node.js und Titanium. Dies ist allerdings nicht in der Module Spezifikation beschrieben. Dabei wird einer Variable "module.exports" eine Funktion oder ein Objekt zugewiesen.

Beispiel:
Product.js

function Product(name, price) { 
    this.name = name; 
 this.price = price; 
} 
 
Product.prototype.getDescription = function() { 
    return this.name + ': ' + this.price + 'EUR'; 
}; 
 
Product.prototype.getPrice = function() { 
    return this.price; 
}; 
 
// Verfügbar machen von Product 
module.exports = Product; 

Cart.js

var total = 0; 
module.exports = { 
    addToCart: function (product) {  
        total = total + product.getPrice();  
 } 
}; 

myApp.js

// Laden des Moduls Product 
var product = require('Product'); 
var ipad    = new product('Apple iPad 3', 479); 
 
alert(ipad.getDescription()); 
  
require('Cart').addToCart(ipad);

Wie können CommonJS Module verwendet werden?

Es gibt für Browser- wie auch für Serverbasierte Systeme viele Bibliotheken die CJS Module laden können. Für den Browser kann ich zum Beispiel curl.js empfehlen. Es ist zudem über kleinere Umwege möglich, CommonJS Module in Frameworks wie jQuery und dojo zu verwenden.

AMD (Asynchronous Module Definition)

Einigen Entwicklern dürfte AMD bereits ein Begriff sein. AMD ist ein Module-Standard, der besonders für Browser interessant ist. Er wird unter anderem von dojo implementiert. Besonders interessant ist er für Browser deswegen, da bei AMD die Module asynchron geladen werden.

Über AMD wurde zunächst innerhalb von CommonJS diskutiert, führte aber zu keinem Ergebnis. Daher kümmerte sich im Anschluss die amdjs Gruppe darum. Grundsätzlich lassen sich CommonJS Module nach AMD mittels Tools konvertieren, zum Beispiel mit https://github.com/jrburke/r.js.
Ein Modul wird bei AMD mit der Funktion "define" definiert und mit der bereits bekannten "require" Funktion eingebunden. Allerdings ist das Handling aufgrund des asynchronen Ladens etwas anders. Weiterhin kann ein Modul Abhängigkeiten definieren.

Beispiel:

define('cart', // Name des Moduls 
       ['i18n', 'user'], // Abhängigkeiten werden als Parameter übergeben werden 
    function (i18n, user) { 
 
        var currency = 'USD'; 
 
        var cart = { 
            setCurrency:function(cur){ 
                currency = cur; 
            } 
        } 
 
        return cart; 
    } 
); 
 
require(['cart'], function (cart) { 
    cart.setCurrency('EUR'); 
});

Um ein CommonJS Modul AMD fähig zu machen, muss das Modul nur minimal verändert werden:

define(function(require, exports, module) { 
    // Inhalt des CommonJS Moduls. 
});

Welche Standards gibt es noch?


Neben dem Modul-System gibt es noch eine Reihe weiterer Spezifikationen, wie zum Beispiel Encodings, Binary Strings, Streams und Sockets. Einige davon sind allerdings noch im Proposal Stadium. Wer die jeweiligen Spezifikationen bereits implementiert hat, ist auf der jeweiligen Wiki-Seite zu sehen. Eine vollständige Liste erhält man unter http://www.commonjs.org/specs/ und http://wiki.commonjs.org/

Logging

Die console dürften die meisten JavaScript Entwickler kennen und dient dem Loggen. Wie die zu loggende Nachricht dabei dargestellt wird, bleibt der jeweiligen Plattform überlassen.
http://wiki.commonjs.org/wiki/Console

HTTP Client

Der HTTP Client dient dazu, Informationen via HTTP abzufragen. Das Proposal B basiert auf dem für Web-Entwickler bekannten XMLHttpRequest.
http://wiki.commonjs.org/wiki/HTTP_Client

Unit-Test

Die Bereitstellung einer Standard Unit-Test Bibliothek soll dazu beitragen, dass mehr Entwickler Tests schreiben. Es umfasst auch die Entwicklung eines Tools zum Ausführen von Unit-Tests. Interessant ist dies für das CommonJS Projekt selbst, da für einige Spezifikationen Unit-Tests geschrieben werden. Eine Beispiel-Implementierung ist hier zu finden.
http://wiki.commonjs.org/wiki/Unit_Testing/1.0.1

Packages

Beschreibt ein Paket-Format um CommonJS Programme und Bibliotheken zu Verteilen. Ein solches Paket besteht unter anderem aus Modulen, Autor, Lizenz-Informationen, Abhängigkeiten, Repositories usw. Es bildet die Grundlage für eine einfache Lieferung, Installation und Verwaltung von CommonJS Komponenten.
http://wiki.commonjs.org/wiki/Packages

Promises

Promises dürfte einigen Web-Entwicklern aus Frameworks wie jQuery oder dojo bekannt sein. Sie erleichtern das Arbeiten mit asynchronen Aktionen.
http://wiki.commonjs.org/wiki/Promises

Filesystem

Bietet eine Dateisystem-API zur Manipulationen von Dateien, Verzeichnissen, Links, Pfade und mehr.
http://wiki.commonjs.org/wiki/Filesystem

i18n

In Sachen Internationalisierung gibt es bisher sehr wenig und jedes Framework/Plattform hat seine eigene Implementierung. Dies soll hierdurch entsprechend standardisiert werden.
http://wiki.commonjs.org/wiki/I18n

Fazit.


Die Idee, plattformübergreifende Spezifikationen zu definieren, finde ich super und notwendig. Die Ziele sind jedoch hoch gesteckt und schwer zu erreichen. Inwiefern sich dies in den nächsten Jahren weiter entwickelt und durchzusetzen wird, bleibt abzuwarten. Immerhin haben inzwischen einige Plattformen wie Node.js, Titanium Mobile aber auch Frameworks wie dojo bereits einige Spezifikationen implementiert. In einem Projekt konnten wir vom Modul-System bereits profitieren und so Code im Browser, Node.js und in Titanium Mobile wiederverwenden.
Vom Gefühl her sind die Spezifikationen eher für Serverbasierte Applikationen entwickelt und teilweise weniger für den Browser geeignet. Gerade was das Modul-System an geht. Hier bietet AMD einen Vorteil durch das asynchrone Laden. Es lohnt sich sicherlich dennoch, das Projekt im Auge zu behalten. Zum Schluss der Hinweiß, dass jeder selber mitmachen kann.

Avatar von Thomas Steur

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.