EcmaScript-6-Module In TypeScript 1.5 Nutzen

EcmaScript-6-Module In TypeScript 1.5 Nutzen

In der Vergangenheit musste jede geladene Seite explizit auf sämtliche direkt oder indirekt benötigte Skript-Dateien verweisen. Da das nicht nur lästig, sondern auch Fehleranfällig ist, haben einige Teams zur Automatisierung dieser Aufgabe auf Build-Prozesse zurückgriffen. Diese sind u. a. in der Lage, Verweise auf JavaScript-Dateien automatisiert in die einzelnen Seiten einzufügen.

EcmaScript 6 bietet hierfür mit seinem Modul-System eine bessere Lösung. Es sieht vor, dass jede Datei ein eigenes Modul mit einem eigenen Namensraum repräsentiert. Somit sind die Zeiten vorbei, in denen Deklarationen allzu leicht den globalen Namensraum „verschmutzt“ haben. Auch die Notwendigkeit für sich selbst ausführende anonyme Funktionen wird somit umgangen. Dies führt letztendlich zu weniger Code und somit auch zu mehr Übersichtlichkeit. 

Ab TypeScript 1.5 kann man dieses neue Sprach-Feature nutzen. Durch eine Kompilierung in handelsübliches EcmaScript 5 lässt sich der damit entwickelte Code in jedem Browser ausführen.

EcmaScript-Module kompilieren

Für das Nachbilden von Modulen in EcmaScript 5 wurden in der Vergangenheit einige Muster beschrieben. Diese definieren Module als Funktionen, sodass sie einen eigenen Namensraum bekommen, und stellen die in anderen Modulen benötigten Konstrukte über eine Datenstruktur zur Verfügung. TypeScript 1.5 unterstützt vier dieser Muster. Dazu gehören die aus der Welt von NodeJS bekannten CommonJS-Module und die von Require.js unterstützte Asynchronous Module Definition (AMD). Daneben unterstützt es ein aus der Welt von SystemJS bekanntes Format sowie die junge Universal Module Definition (UMD), welche unter anderem das CommonJS-Format und AMD vereint.

Damit TypeScript Module nach EcmaScript 5 oder 3 kompilieren kann, ist das gewünschte Modul-Format bekanntzugeben. Bei Nutzung von Visual Studio 2015 erfolgt dies über die Projekteinstellungen (mehr dazu hier). Im betrachteten Fall fällt die Wahl auf das für den Einsatz in Browser konzipierte AMD.

Kommt Visual Studio nicht zum Einsatz, sondern ein direkter Aufruf des TypeScript-Kompilers oder ein Werkzeug, wie Visual Studio Code, kann das Entwicklungs-Team das gewünschte Modul-Format in der Datei tsconfig.json hinterlegen. Der TypeScript-Kompiler zieht die darin enthaltenen Einstellungen standardmäßig heran, wenn er sie im aktuellen Arbeitsverzeichnis findet. Bei Nutzung von Visual Studio Code ist das das Root-Verzeichnis der Anwendung. Ein Beispiel dafür findet sich nachfolgend. Es legt ein weiteres Mal das AMD-Format fest, definiert das Source-Maps zu erstellen sind und weist TypeScript an, nach EcmaScript 5 zu kompilieren.

{
    "compilerOptions": {
        "module": "amd",
        "sourceMap": true,
        "target": "ES5"
    } 
}

Konstrukte aus Modul exportieren

Möchte ein Modul anderen Modulen Klassen, Funktionen oder Variablen zur Verfügung stellen, muss es diese Konstrukte exportieren. Ein Beispiel dafür bietet das nächste Listing. Es zeigt den Inhalt einer Datei flug.ts, welche – wie ab EcmaScript 6 üblich – ein Modul bildet. Darin findet sich eine Klasse Flug mit einigen Eigenschaften. Abgesehen von den von TypeScript unterstützten Typ-Angaben handelt es sich dabei um reines EcmaScript 6. Damit die Klasse Flug auch außerhalb des Modules zur Verfügung steht, wurde es mit dem Schlüsselwort export markiert.

Eine alternative Schreibweise findet man im Kommentar am Ende des Listings. Sie verzichtet zunächst auf das Schlüsselwort export und deklariert somit die Klasse als modul-intern. Eine alleinstehende export-Anweisung definiert im Anschluss daran, welche internen Konstrukte zu veröffentlichen sind. Mehrere Konstrukte können dabei Komma-getrennt angegeben werden. Auf diese Weise lassen sich sämtliche Exporte am Ende einer Datei gesammelt definieren.

// flug.ts
export class Flug {
    id: number;
    fnr: string;
    datum: Date;
    abflugort: string;
    zielort: string;
    plaetze: number;
    plaetzeFrei: number;
}

// Alternative
//   class Flug { […] }
//   export { Flug };

Exportiere Konstrukte importieren

Um exportierte Konstrukte in anderen Modulen zu nutzen, sind diese dort zu importieren. Hierzu sieht EcmaScript 6 das Schlüsselwort import vor. Ein Beispiel dafür bietet Listing 3, welches den Inhalt einer Datei flugCalculator.ts zeigt. Zunächst nutzt es eine import-Anweisung, um die in Listing 2 exportiere Klasse Flug zu importieren. Der Name dieser Klasse findet sich in geschweiften Klammern; der Name der Datei, welche Flug exportiert, findet sich innerhalb der auf das Schlüsselwort from folgenden Zeichenkette. Dateiendungen werden weggelassen.

Dateien in anderen Ordnern sind durch die Angabe von Pfaden zu referenzieren. Beispielsweise verweist 'entities/Flug' auf die Datei Flug im Ordner entities. Damit die Suche nach einer Datei relativ zur aktuellen Datei und nicht relativ zum Root der Web-Anwendung erfolgt, ist ein Pfad mit einem Punkt einzuleiten: './entities/Flug'.

// flugCalculator.ts
import { Flug } from 'flug';

export class FlugCalculator {

    static basePrice = 300;

    calcPrice(flug: Flug) {
        var p = flug.plaetze / flug.plaetzeFrei;
        if (p >= 0.75) return FlugCalculator.basePrice / 2;
        return FlugCalculator.basePrice;
    } 

}

Module laden

Für das Laden der Module benötigt die aufgerufene Seite einen sogenannten Module Loader. Beispiele dafür sind RequireJS und SystemJS. Die hier betrachteten Beispiele nutzten letzteren, zumal dieser alle gängigen Modul-Formate unterstützt. Es empfiehlt sich, SystemJS gemeinsam mit dem Package Manager JSPM zu nutzen. Dieser bezieht JavaScript-Bibliotheken, wie jQuery oder AngularJS, in Form von Modulen. Dazu greift er auf verschiedene Online-Repositories, wie GitHub, zu. Zum Installieren von JSPM nutzt das Entwicklungs-Team den Package-Manager von NodeJS: npm install -g jspm. Ein Aufruf von jspm init im Root der Anwendung richtet eine Konfigurationsdatei ein und versorgt das Projekt mit SystemJS.

Nach dem Einbinden von SystemJS kann die Seite das gewünschte Modul mit System.import laden:

<script src="jspm_packages/system.js"></script>
<script>System.import('app').catch(console.error.bind(console));</script>

Das hier geladene Modul app findet sich der Vollständigkeit halber im nächsten Listing. Es referenziert die beiden anderen zuvor betrachteten Module und arbeitet mit den davon bereitgestellten Klassen.

Die Funktion System.import nimmt den Namen des gewünschten Moduls entgegen und liefert einen Promise retour. Für den Fehlerfall empfiehlt es sich, mittels catch einen Error-Handler zu registrieren. Das hier diskutierte Beispiel zieht dazu die Funktion console.error, welche einen Fehler in die JavaScript-Konsole schreibt, heran.

In der Regel reicht es, über den Module Loader der Wahl eine einzige Datei zu laden. Diese kann über import-Anweisungen auf andere Module referenzieren. Der Module Loader folgt diesen Anweisungen und lädt den gesamten dadurch beschriebenen Abhängigkeits-Graph, sprich sämtliche direkt und indirekt referenzierten Module.

import { FlugCalculator } from 'flugCalculator';
import { Flug } from 'flug';

var fc = new FlugCalculator();
var f = new Flug();
f.id = 17; f.plaetze = 100; f.plaetzeFrei = 90;

var preis = fc.calcPrice(f);
alert(preis);