Auf Abwegen

Node.js abseits des Webservers
Kommentare

Mittlerweile gibt es kaum einen Webentwickler, der nicht schon zumindest von Node.js gehört hat. Die meisten Personen bringen mit serverseitigem JavaScript einen leichtgewichtigen Webserver in Verbindung, der in weniger als zehn Zeilen implementiert ist. Die Einsatzgebiete von JavaScript abseits des Browsers und sogar abseits jeglicher Webtechnologien sind jedoch vielfältig. Dieser Artikel zeigt, wie sich mit Node.js mächtige Kommandozeilenwerkzeuge auf Basis von JavaScript umsetzen lassen.

Beschäftigt man sich mehr mit dem Thema Node.js, wird deutlich, dass es sich dabei um eine Sammlung von Bibliotheken um die JavaScript Engine V8 von Google handelt. Dieser Kern wird um einige Module erweitert, die den Funktionsumfang von Node.js um häufig benötigte Features bereichern. Hierunter fallen im einfachsten Fall Schnittstellen zum Betriebssystem, über die sich auslesen lässt, auf welchem System Node.js ausgeführt wird, wie viel Speicher und wie viele Netzwerkkarten zur Verfügung stehen. Es gibt allerdings auch komplexere Module, die zum Beispiel den Zugriff auf das Dateisystem des Rechners ermöglichen. Dank dieser Module kann ein Entwickler vollwertige Applikationen auf einem Server entwickeln.

Neben diesem Kern besteht die Node.js-Distribution aus dem Node Package Manager, kurz NPM. Über den NPM stehen mehrere tausend weitere Module zur Verfügung. Diese sind nicht direkter Bestandteil von Node.js, ergänzen den Funktionsumfang allerdings weiter. NPM bietet beispielsweise Frameworks, die es einem Entwickler ermöglichen, eine vollwertige Webapplikation zu entwickeln oder Clients für verschiedene Datenbanken und vieles mehr.

Verwaltet werden die NPM-Pakete in einem zentralen Repository, das auf npmjs.org zu finden ist. Mithilfe des NPM-Kommandozeilenwerkzeugs lässt sich dieses Repository durchsuchen, Pakete daraus installieren oder bereits installierte Pakete aktualisieren. NPM kann allerdings auch dazu verwendet werden, um installierte Pakete wieder vom System zu entfernen. Dabei hilft der NPM auch beim Auflösen von Abhängigkeiten bei der Installation eines Paketes. Wird also zur Funktionsfähigkeit eines Paketes ein anderes benötigt, wird dieses automatisch aus dem Repository heruntergeladen und ebenfalls installiert.

NPM stellt jedoch nicht nur Bibliotheken zur Integration in eigene Applikationen zur Verfügung, sondern ermöglicht es auch, unabhängige Pakete zu installieren. Dadurch können Befehle direkt auf der Kommandozeile abgesetzt werden. Konkrete Beispiele hierfür sind Grunt, ein Build-Tool für JavaScript-Projekte, oder LESS, ein Compiler für Cascading Style Sheets.

Zur Installation von Paketen stehen grundsätzlich zwei Optionen zur Verfügung. Die Pakete können entweder lokal für das aktuelle Projekt oder global für das gesamte System installiert werden. Bei der lokalen Variante der Installation werden die Dateien des Moduls in einer Verzeichnisstruktur unterhalb des /node_modules/-Verzeichnisses im aktuellen Arbeitsverzeichnis abgelegt. Wird ein NPM-Paket global installiert, werden die Dateien in verschiedenen Verzeichnissen unterhalb von /usr/local/ abgelegt. Hierbei wird unterschieden zwischen den eigentlichen Moduldateien, die im /usr/local/lib/node_modules/-Verzeichnis zu finden sind, den Binärdateien unter /usr/local/bin/ und den Manual Pages unter /usr/local/share/man/.

Problemstellung

Aufbauend auf diesen Informationen lassen sich zahlreiche Kommandozeilenwerkzeuge umsetzen. Im Zuge dieses Artikels soll eine Applikation entwickelt werden, die es ermöglicht, eine Eingabedatei, die in einem bestimmten Format vorliegt, in eine Ausgabedatei mit einem anderen Format umzuwandeln.

Grundlage für diese Applikation ist die Anforderung, dass die Ergebnisse von statischer Codeanalyse für JavaScript in Jenkins visualisiert werden sollen. Für JavaScript existiert mit CLOC ein Tool, das Informationen über Lines of Code liefert. Gezählt werden Codezeilen, Kommentarzeilen und leere Zeilen. Diese Metriken werden für etliche Sprachen, unter anderem auch für JavaScript, erstellt. Das eigentliche Problem ist, dass es für die Continuous-Integration-Plattform Jenkins kein Plug-in zur Visualisierung der Lines-of-Code-Metriken von CLOC gibt. Mit SLOCCount existiert eine Alternative, die diese Metriken ebenfalls für verschiedene Sprachen sammelt, und für die auch ein Jenkins-Plug-in zur Visualisierung existiert. Allerdings wird JavaScript als Programmiersprache durch dieses Werkzeug nicht abgedeckt. Aus diesem Grund wird CLOC zur Datensammlung verwendet und die Ausgabe in das SLOCCount-Format gewandelt, das im Anschluss visualisiert wird.

[ header = Grundstruktur eines NPMs ]

Grundstruktur eines NPMs

NPM-Pakete sind im Normalfall über das öffentliche Repository auf npmjs.org erreichbar. Da dadurch die Öffentlichkeit auf den Quellcode zugreifen kann, empfiehlt es sich, ihn ebenfalls über ein öffentliches Versionskontrollsystem wie GitHub zu verwalten. Dies ermöglicht es anderen Interessierten, sich an der Entwicklung des NPM-Moduls zu beteiligen, eventuelle Fehler zu melden oder Verbesserungsvorschläge einzubringen.

Ähnlich wie Plug-ins für jQuery weisen auch NPM-Pakete eine einheitliche Grundstruktur auf. Es gibt allerdings Werkzeuge, die einen Entwickler bei der Erstellung dieses Grundgerüsts unterstützen. Zu diesem Zweck kann unter anderem Grunt verwendet werden. Grunt lässt sich sehr einfach über den NPM mit dem Befehl npm install -g grunt installieren. Den Ausgangspunkt für die Erstellung eines eigenen NPM-Pakets bildet ein leeres Verzeichnis. In dieses schreibt Grunt dann die Dateien des späteren Pakets. Die eigentliche Initialisierung des Pakets geschieht im Zielverzeichnis mit dem Kommandozeilenbefehl grunt init:node. Dieser startet einen interaktiven Prozess, in dessen Verlauf der Entwickler eine Reihe von Fragen zu beantworten hat. Im ersten Schritt wird der Name des Projekts festgelegt. Danach gibt der Entwickler eine ausführlichere Beschreibung und eine Versionsnummer an. Des Weiteren wird nach dem URL zum GitHub Repository, in dem der Code des NPM-Pakets liegt, gefragt, und es sollte eine Homepage für das Projekt und ein Issue Tracker festgelegt werden. Ein weiterer wichtiger Punkt ist die Lizenz, unter der das Paket später verfügbar sein soll. Grunt stellt zwei verschiedene Lizenzen bereit: MIT und GPL, von denen die MIT-Lizenz vorausgewählt ist. Für diese beiden Lizenzen stehen Lizenzdateien zur Verfügung. Entsprechend der Auswahl wird die Lizenzdatei im Rahmen der Initialisierung in das Projektverzeichnis kopiert. Der Entwickler kann auch jede beliebige andere Lizenz angeben, muss sich dann allerdings selbst darum kümmern, dass die Lizenzinformationen im Paket verfügbar sind.

Sind die Linzenzinformationen festgelegt, sollte der Entwickler einige Informationen, wie Name, E-Mail-Adresse und Webseite, über sich selbst, also den Autor des Pakets, angeben. Ein sehr wichtiger Punkt ist die Angabe der Node.js-Version, unter der das Paket installiert werden kann. Schließlich muss der Eingangspunkt in das NPM spezifiziert werden, also die JavaScript-Datei, die Node.js als erstes lädt, wenn das Modul eingebunden wird, und ein Kommando mit dem die Tests für das Paket ausgeführt werden können.

Im einfachsten Fall besteht ein NPM-Paket aus einem Verzeichnis oder Tar-Archiv mit den Dateien des Pakets und einer package.json-Datei. Diese Datei enthält die Beschreibung des Pakets, die notwendig ist, um es auf npmjs.org zu publizieren und es danach mittels npm install installieren zu können. Mit den Informationen, die man über Grunt bei der Initialisierung gemacht hat, wird ein package.json für das neue Paket erstellt. Neben der package.json-Datei werden durch Grunt noch einige weitere hilfreiche Dateien erstellt. Darunter zählt neben der bereits erwähnten Lizenzdatei die zentrale Informationsdatei README.md. Sie wird beispielsweise auf GitHub direkt im Repository angezeigt, um dem Nutzer einen schnellen Einstieg in das Projekt zu ermöglichen.

Die empfohlene Dateistruktur sieht ein Verzeichnis /lib/ vor, in dem die eigentlichen Dateien des Pakets liegen. Parallel zu diesem Verzeichnis existiert ein Verzeichnis /test/, in dem die Tests für das Paket abgelegt werden sollten. Sind diese Voraussetzungen erfüllt und ist der Code der Anwendung fertig implementiert, kann das NPM publiziert und installiert werden. Da unser NPM-Paket allerdings auf der Kommandozeile verfügbar sein soll, sind noch ein paar Erweiterungen notwendig.

Grundstruktur eines NPMs auf der Kommandozeile

Damit das Paket als reguläre Anwendung auf der Kommandozeile verwendet werden kann, ist es notwendig, die Beschreibung des Pakets in der package.json um eine Direktive zu erweitern. Im Abschnitt „bin“ wird spezifiziert, wo die ausführbare Datei liegen soll. Dies ist im einfachsten Fall nur ein Key-Value-Paar mit einer Zeichenkette als Wert, die auf die entsprechende Datei relativ zum Basisverzeichnis des Pakets verweist:

"bin": "bin/" 

In diesem Abschnitt können eine oder mehrere Dateien angegeben werden. Sie müssen mit einem so genannten „Shebang“ beginnen. Dieser gibt an, in welcher Umgebung das Script ausgeführt werden soll. #!/usr/bin/env node bedeutet, dass die Anwendung mit Node.js ausgeführt werden soll. Im weiteren Verlauf kann JavaScript-Code dazu verwendet werden, um die Anwendung zu initialisieren und zu starten.

Das Script wird laut der Konvention für den Aufbau von NPM-Paketen in einem separaten /bin/-Verzeichnis innerhalb des Pakets abgelegt. Im Verlauf der Installation sorgt der Node Package Manager dafür, dass ein symbolischer Link erstellt wird. Für global installierte NPMs wird der Link unter /bin installiert. Bei der Standardinstallation ist das /usr/local/bin/npm/. Bei lokal installierten NPMs ist der Pfad entsprechend ./node_modules/.bin/.

[ header = Dateien in Paketen ignorieren – .npmignore ]

Dateien in Paketen ignorieren – .npmignore

NPM ist nicht nur ein Werkzeug zur Paketverwaltung unter Node.js, sondern wird von seinen Entwicklern als Entwicklungswerkzeug bezeichnet. Als solches stellt es auch während der Programmierung neuer Pakete etliche Hilfsmittel zur Verfügung. Häufig steht ein Entwickler vor dem Problem, dass während des Entwicklungsprozesses Dateien erstellt werden, die zwar notwendig für die Erstellung eines Pakets sind, jedoch nicht mit diesem Paket ausgeliefert werden sollen. NPM löst dieses Problem ähnlich wie bekannte Versionskontrollsysteme. Es besteht die Möglichkeit, eine Datei mit dem Namen .npmignore zu erstellen. Sie beinhaltet die Namen der Dateien und Verzeichnisse, die nicht Bestandteil des späteren Pakets werden sollen.

Da NPM in den meisten Fällen mit Git als Versionskontrollsystem verwendet wird, existiert ein Mechanismus, der es dem Entwickler erleichtert, Dateien sowohl aus dem Versionskontrollsystem als auch aus dem Paket auszunehmen. Existiert im Verzeichnis eine Datei mit dem Namen .gitignore, die dazu verwendet wird, zu verhindern, dass bestimmte Dateien im Versionskontrollsystem archiviert werden, werden die dort angegebenen Dateien ebenfalls nicht mit in das NPM-Paket aufgenommen. Sollen die dort aufgeführten Dateien trotzdem ins Paket gelangen, muss eine .npmignore-Datei erstellt werden, in der die Dateien nicht aufgelistet werden. Die .npmignore überschreibt also die Bedeutung von .gitignore.

Ein Einsatzgebiet für .npmignore ist das Entfernen des /node_modules/-Verzeichnisses aus dem Paket. Hintergrund dazu ist, dass die dort hinterlegten Abhängigkeiten nicht fester Bestandteil des Pakets sein, sondern durch die Abhängigkeitsauflösung von NPM installiert werden sollen. Das bietet gleich mehrere Vorteile: Zum einen wird Speicherplatz gespart, da der Quellcode der Pakete nicht gespeichert wird. Außerdem sorgt NPM dafür, dass immer die aktuellste Version beziehungsweise zumindest die aktuellsten Bugfix-Releases installiert werden, ohne dass sich der Entwickler selbst darum kümmern muss.

Abhängigkeiten

Wird eine Applikation komplexer, kommt es häufiger vor, dass andere Entwickler ein Teilproblem bereits gelöst haben. In solchen Fällen greift man meist auf etablierte Bibliotheken zurück. Dies gilt in gleichem Maße auch bei der Entwicklung von NPM-Paketen. Hier hat der Entwickler allerdings den Vorteil, auf mehr als 10 000 bestehende NPM-Pakete zurückgreifen zu können. Jedes auf npmjs.org existierende Paket kann als Abhängigkeit in ein anderes Paket eingebunden werden.

Die Verwaltung von Abhängigkeiten geschieht in der zentralen package.json-Datei. Zur Verwaltung der Abhängigkeiten existieren zwei verschiedene Direktiven, „dependencies“ und „devDependencies“:

"devDependencies": {
  "grunt": "~0.3.15"
},
"dependencies": {
  "xml2js": ">= 0.2.0"
}, 

Der Unterschied zwischen beiden Direktiven leitet sich bereits aus dem Namen ab. „dependencies“ geben an, welche NPM-Pakete zum Betrieb des Pakets benötigt werden. „devDependencies“ spezifizieren Pakete, die zur Entwicklung des Pakets erforderlich sind. Die Abhängigkeiten, die unter „dependencies“ aufgeführt sind, werden automatisch aufgelöst, sobald npm install aufgerufen wird. Pakete, die zur Entwicklung notwendig sind, können auf verschiedene Arten installiert werden. Entweder wird bei der Installation die Option –dev angegeben oder innerhalb des Pakets wird npm link aufgerufen.

Versionsangaben

Im Rahmen der package.json ist es an mehreren Stellen erforderlich, Versionsangaben zu machen. Das betrifft vor allem die Abschnitte „engine“, mit dem angegeben wird, mit welcher Version von Node.js das Paket eingesetzt werden kann, und „dependencies“.

Da an die Angabe von Versionsnummern eine Reihe von Anforderungen gestellt werden, existieren verschiedenste Möglichkeiten, Versionen zu spezifizieren. Die einfachste Variante ist die Angabe einer Version, der ein Vergleichsoperator vorangestellt wird. Verfügbar sind dabei „=“, „“, „=“. Damit lassen sich bereits die meisten Fälle abbilden. Am häufigsten muss man als Entwickler eine Minimalversion eines Pakets festlegen. Das geschieht am Beispiel von Node.js als Engine über die Angabe „>=0.6.0“, was bedeutet, dass Node.js mindestens die Versionsnummer 0.6.0 aufweisen muss. Schwieriger wird der Fall, wenn eine Version explizit erforderlich ist. Hier hat sich als Konvention durchgesetzt, die letzte Stelle, also das Patch-Level, durch ein „x“ zu ersetzen. Das führt dazu, dass diese letzte Stelle ignoriert wird und Patches dadurch weiterhin angewandt werden können.

Weitere Möglichkeiten der Angabe von Versionen sind verschiedene Varianten von Versionsspannen. So kann durch Voranstellen einer Tilde angegeben werden, dass mindestens die Version, nicht aber die nächst höhere Major-Version des Pakets verwendet werden soll. Des Weiteren kann auch eine explizite Spanne in Form von „0.1.0 – 1.2.1“ definiert werden. In diesem Fall bedeutet das, mindestens Version 0.1.0 und höchstens Version 1.2.1.

[ header = Grundstruktur einer eigenen Library ]

Grundstruktur einer eigenen Library

Nachdem nun die wichtigsten Fragen zur Struktur eines NPM-Pakets geklärt sind, widmen wir uns nun der eigentlichen Arbeit, nämlich der Implementierung der Funktionalität des Pakets. Für jedes Modul steht ein globales module-Objekt zur Verfügung. Es wird durch das Modulsystem erstellt und dient zur internen Verwaltung der verschiedenen Module. Über das exports-Objekt innerhalb von module kann der eigene Code verfügbar gemacht werden. Zu beachten ist hierbei, dass die Funktion oder das Objekt, das zurückgegeben werden soll, direkt zugewiesen werden muss und dies nicht asynchron per Callback erfolgen darf. Alternativ zu module.exports kann auch nur exports verwendet werden. Beide Objekte haben die gleiche Funktion.

Für den Konverter werden zuerst die abhängigen Pakete benötigt. Zu diesem Zweck werden die entsprechenden Pakete mittels require eingebunden und einer Variablen zugewiesen. Dadurch sind sie im gesamten Modul verfügbar. Einerseits wird zum Auslesen von Dateien beziehungsweise zum Schreiben das Dateisystem Modul benötigt und zum anderen ein XML-Parser. Das Dateisystem Modul wird mittels require(‚fs‘) eingebunden. Als XML-Parser kommt „xml2js“ zum Einsatz, der als NPM-Paket verfügbar ist. Damit das Paket einwandfrei funktionieren kann, muss das NPM-Paket als Dependency in die package.json-Datei eingebunden werden. Dabei ist darauf zu achten, dass mindestens die Version 0.2.0 benötigt wird.

Danach kann dann eine Funktion definiert werden, die die Schablone für die späteren Instanzen darstellt. Die notwendigen Funktionalitäten können dann über Funktionen in den Prototyp der Funktion eingefügt werden. Der letzte Schritt besteht dann darin, dass die Funktion über module.exports verfügbar gemacht werden.

var fs = require('fs'),
  xml2js = require('xml2js');

var cloc2sloc = function () {
  this.inputFile;
  this.outputFile;
};

module.exports = cloc2sloc; 

NPM Testing

Wird ein NPM mithilfe von Grunt erstellt, wird standardmäßig die Infrastruktur für Unit Tests vorbereitet. Für Node.js stehen mehrere Unit-Test-Frameworks zur Verfügung. In diesem Fall kommt nodeunit zum Einsatz. Dieses Werkzeug wird im Zuge der Installation von Grunt als Abhängigkeit auf dem System installiert und steht zur Verfügung. Nach der Initialisierung sind also alle Vorbereitungen für eine testgetriebene Entwicklung getroffen.

In einem ersten Schritt wird ein separates Verzeichnis /test/ erstellt. In diesem befindet sich bereits eine Datei, die einen ersten Testfall enthält. Diese Datei trägt standardmäßig den Namen des Pakets mit dem Postfix „_test“. Die Tests können dann durch den Aufruf grunt test im Basisverzeichnis des Pakets ausgeführt werden.

Der Aufbau der Tests gestaltet sich relativ einfach. Die Grundstruktur bildet ein exports-Objekt. Innerhalb dieses Objekts besteht die Möglichkeit über die setUp-Methode Initialisierungsaufgaben und mit tearDown Aufräumarbeiten durchzuführen. setUp wird jeweils vor jedem Test und tearDown nach jedem Test aufgerufen. Die Tests selbst bestehen aus Key-Value-Paaren, wobei der Key die Beschreibung des Tests darstellt und der Value eine Funktion, die einen Parameter „test“ erhält, über den innerhalb des Tests die verschiedenen Assertions wie beispielsweise equal zur Verfügung stehen.

Zu beachten ist, dass die einzelnen Tests stets mit test.done() abgeschlossen werden müssen, da nodeunit ansonsten eine Fehlermeldung ausgibt und keinen der vorhandenen Tests ausführt (Listing 1).

'check output header': function(test) {
  var outputFile = __dirname + '/../data/output.sc',
  cloc2sloc = new Cloc2sloc();
  if (fs.existsSync(outputFile)) {
    fs.unlinkSync(outputFile);
  }
  cloc2sloc.setOutputFile(outputFile);
  cloc2sloc.writeHeader(cloc2sloc.getHeader());
  var expected = 'Creating filelist for sourcenCategorizing files.nComputing results.nnn';
  var actual = fs.readFileSync(outputFile);
  test.equal(actual, expected);
  test.done();
}, 

Implementierung der Logik

Nachdem jetzt sämtliche Voraussetzungen erfüllt sind und das Grundgerüst für das Paket steht, kann man als Entwickler eines NPM-Pakets mit der eigentlichen Arbeit, dem Programmieren der Logik des Pakets, beginnen. Der erste Teil der zu lösenden Aufgabe besteht darin, die Quelldatei vollständig auszulesen. Zu diesem Zweck soll sowohl konfigurierbar sein, aus welcher Datei die Daten gelesen werden sollen, als auch in welche Datei geschrieben wird. Dies lässt sich am einfachsten jeweils über eine Setter-Methode realisieren. Wird die Logik testgetrieben entwickelt, wird stets zuerst ein Test für die zu entwickelnde Logik implementiert. Danach werden die Tests mittels grunt test im Basisverzeichnis ausgeführt, was zu einem Fehlschlag mindestens eines Tests führt. Schließlich wird die Logik umgesetzt, sodass die Tests erfolgreich durchlaufen. So kann in kleinen Inkrementen das gesamte Paket erstellt werden.

Existenz einer Datei prüfen

Eine potenzielle Fehlerquelle im Paket ist die Eingabedatei. Hier stellt sich zuerst die Frage, was passiert, wenn die Datei nicht existiert. Dieser Fall stellt eine Ausnahmesituation dar, was das klassische Einsatzgebiet von Exceptions ist. Auch dieser Fall sollte durch einen Test abgedeckt werden. nodeunit stellt hierfür die Assertion throws zur Verfügung, die dafür sorgt, dass ein Test abbricht, wenn keine Exception in einem Codeblock geworfen wird. Damit der Test funktioniert, muss der potenziell fehlerhafte Codeblock in eine Funktion gekapselt werden.

Zur Prüfung auf die Existenz der Datei empfiehlt sich die Setter-Methode der Input-Datei. Hier kommt zum ersten Mal das fs von Node.js zum Einsatz. Es stellt die beiden Methoden exists und existsSync zur Verfügung. Der Einfachheit halber wird in diesem Fall die synchrone Form der Methode verwendet, da die Prüfung auf Existenz einer Datei in den seltensten Fällen lange dauert und man dadurch Probleme durch Asynchronität der Aufrufe umgeht.

[ header = Ausgabe-Header schreiben ]

Ausgabe-Header schreiben

Das Ausgabeformat sieht vor, dass gewisse Statusinformationen über die Abarbeitung der analysierten Quelldateien zuerst in die Ausgabedatei geschrieben werden. Da es sich hierbei lediglich um statische Inhalte handelt, ist es kein Problem, sie als String vorzuhalten und diesen dann in die Ausgabedatei zu schreiben.

Für den Test muss in erster Linie dafür gesorgt werden, dass eine eventuell existierende Ausgabedatei vor Ausführung des Tests gelöscht wird. Dazu verwendet man eine Kombination aus fs.existsSync, um zu prüfen, ob die Datei existiert, und fs.unlinkSync, um die Datei – falls existent – zu entfernen. Danach wird die Ausgabedatei gesetzt und der Header geschrieben. Anschließend vergleicht man den erwarteten Wert mit dem tatsächlichen Dateiinhalt, der mittels fs.readFileSync ausgelesen wurde.

Das Schreiben des eigentlichen Headers erreicht man durch die Verwendung der Methode appendFileSync aus dem Dateisystemmodul von Node.js. Diese Methode erhält neben der Zieldatei den String, der in diese Datei geschrieben werden soll. Der Schreibvorgang erfolgt synchron, sodass kein Callback benötigt wird.

XML-Datei auslesen

Nachdem die statischen Inhalte bereits in die Zieldatei geschrieben wurden, müssen als Nächstes die dynamischen Inhalte behandelt werden. Zu diesem Zweck muss die Eingabedatei gelesen und die Werte in das Ausgabeformat gewandelt werden. Da sämtliche Zeilen das gleiche Format aufweisen, sollte im ersten Schritt eine Hilfsmethode implementiert werden, die diese Formatierung übernimmt. Die ausgelesenen XML-Daten liegen als Objekt vor, sodass die Hilfsfunktion ein Objekt mit einem festen Satz von Eigenschaften erhält und diese nur noch korrekt in einen String wandeln muss.

Der Test für diese Methode ist trivial, es wird lediglich ein Eingabeobjekt benötigt, welches das korrekte Format aufweist, und ein String mit dem erwarteten Ergebnis. Danach kann die Methode zur Umwandlung der Eingabe in das korrekte Ausgabeformat aufgerufen und das Resultat mit dem erwarteten Ergebnis verglichen werden.

Die Implementierung der Methode gestaltet sich sogar noch etwas einfacher, da lediglich die Eigenschaften des Eingabeobjekts in die korrekte Reihenfolge gebracht und durch Tabs getrennt werden müssen. Die einzige Besonderheit ist, dass aus Platzgründen aus dem String „JavaScript“ die Zeichenkette „JS“ gemacht wird. Ein Problem, das es noch zu lösen gilt, ist das Auslesen der Werte aus der Eingabedatei und das Schreiben der Werte in die Ausgabedatei.

Das Einlesen der Eingabedatei besteht aus einer Kombination von fs.readFile und dem Parsen des Dateiinhalts. Um die XML-Daten in ein JavaScript-Objekt zu wandeln, mit dem weiter gearbeitet werden kann, wird der xml2js-Parser verwendet. Dieser steht als NPM zur Verfügung und wurde bereits als Abhängigkeit in die package.json-Datei eingetragen, sodass er mittels require(‚xml2js‘) als Paket zur Verfügung steht. Sind die Inhalte ausgelesen, wird das Buffer-Objekt in einen String umgewandelt, der wiederum als Eingabe für den XML-Parser dient. Der Parser wandelt den String in ein gültiges JavaScript-Objekt um, das als Eingabe für die zuvor implementierte Transformationsfunktion dient. Die Ausgabe kann dann mit fs.appendFileSync an die Ausgabedatei angehängt werden.

Das Schreiben der Ausgabedatei lässt sich recht gut in eine separate Funktion auslagern. Sie erhält als Parameter ein Array von Eingabeobjekten, die durch die Transformationsfunktion umgewandelt und an die Ausgabedatei angehängt werden. Dadurch lässt sich dieser Prozess auch recht problemlos testen.

Ein Punkt, der bei der Verwendung von Callbacks zu beachten ist, ist die Bindung der Callbacks an den korrekten Kontext. Der ECMAScript-Standard sieht für diesen Fall die bind-Funktion vor. Jedes Funktionsobjekt weist diese Methode auf. Als Argument wird ihr ein Objekt übergeben, in dessen Kontext die Funktion ausgeführt werden soll.

Einstiegspunkt in die Applikation

Nachdem die Anwendung backendseitig fertig implementiert ist, fehlt lediglich der Einstiegspunkt über die Kommandozeile. Wie bereits beschrieben, handelt es sich hierbei um eine JavaScript-Datei, deren erste Zeile der Shell angibt, in welcher Umgebung dieses Script zu interpretieren ist.

Außerdem soll erzwungen werden, dass die Ein- und Ausgabedatei als Kommandozeilenparameter an die Anwendung übergeben werden. In Node.js steht über das globale process-Objekt eine Vielzahl von Informationen und Methoden zur Steuerung des Verhaltens zur Verfügung. Unter anderem gibt es mit der argv-Eigenschaft ein Array von Kommandozeilenargumenten. Das erste Element ist „node“, das zweite ist der Dateiname des aktuellen Scripts und die folgenden enthalten dann die Kommandozeilenargumente.

Dieses Array von Kommandozeilenargumenten lässt sich aufsplitten und anhand von Pattern-Matching kann die jeweilige Ein- und Ausgabedatei bestimmt werden.

NPM veröffentlichen

Der letzte Schritt in der Erstellung eines NPM-Pakets besteht darin, dieses Paket der Öffentlichkeit über npmjs.org zugänglich zu machen. Einzige Voraussetzung zur Publizierung ist ein Nutzeraccount auf npmjs.org. Dieser ist allerdings mit nur wenigen Schritten von der Kommandozeile aus erstellt.

Mit dem Kommando npm useradd wird ein interaktiver Modus gestartet, mit dessen Hilfe man in wenigen Schritten einen Nutzeraccount einrichten kann. Hierfür muss man lediglich seinen Benutzernamen, seine E-Mail-Adresse und ein Passwort angeben. Falls der Benutzeraccount bereits besteht, wird er mit dem lokalen Rechner verbunden, andernfalls wird ein neuer Account erstellt. Um das Paket schließlich zu publizieren, gibt man auf der Kommandozeile im Verzeichnis mit der package.json-Datei den Befehl npm publish ein. Er sorgt dafür, dass die Dateien korrekt in das Repository unter geladen und registriert werden.

Ausblick

Nach diesen Schritten kann das Paket mittels npm install -g installiert werden. Danach ist ein Kommandozeilenbefehl mit dem gleichen Namen wie das Paket global verfügbar und man kann seine JavaScript-Logik auf der Kommandozeile nutzen. Dieses sehr einfache Beispiel hat gezeigt, dass man mit nur wenigen Schritten sehr vielseitige Werkzeuge mit Node.js umsetzen kann – und dass Node.js auch abseits der üblichen Webtechnologien eingesetzt werden kann.

Auch die Verfügbaren Werkzeuge für Node.js wie beispielsweise das Unit-Test-Framework nodeunit oder das Build-Tool Grunt erlauben es Entwicklern, professionelle Applikationen mit serverseitigem JavaScript zu implementieren. Mit einem offenen Paketmanager wie NPM und einer sehr aktiven Community auf GitHub können Pakete auf einfache Art anderen Entwicklern zur Verfügung gestellt werden. Diese haben dann die Möglichkeit, sich aktiv an der Weiterentwicklung zu beteiligen, was nur förderlich für die Qualität der Pakete sein kann.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -