In den letzten Jahren hat sich die Entwicklung von Web-Anwendungen in eine Richtung gedreht, in der immer größere Teile komplexer Softwaresysteme im Browser leben. Damit haben sich auch die Ansprüche an den Browser als Softwareplattform geändert, und die Kapselungs- und Abstraktionsmöglichkeiten der ehemals als HTML-Erweiterung angelegten Browser-Programmiersprache JavaScript genügen nicht mehr.

Inzwischen gibt es eine Reihe von Programmiersprachen, die mit unterschiedlichen Ansätzen an JavaScript andocken können. In einem Artikel über ClojureScript wurde in diesem Blog bereits eine Sprache aus der LISP-Familie vorgestellt. Im vorliegenden Artikel geht es um TypeScript, eine Alternative, die sich durch ein statisches, von C# inspiriertes Typsystem und niedrige Einsatzhürden auszeichnet.

TypeScript ist eine Obermenge von JavaScript, d.h. jedes JavaScript-Programm ist ein TypeScript-Programm. So kann der Compiler mit wenigen Stunden Aufwand selbst in ein großes Softwareprojekt eingeführt werden, (fast) ohne den Code zu ändern. Die Vorteile der statischen Typisierung kann man dann nach und nach intensiver nutzen. Die Neuerungen an TypeScript sind konservativ und orientieren sich an bestehenden Sprachen wie C# (das Projekt stammt aus dem Hause Microsoft) und Java.

Wie JavaScript hat TypeScript Funktionen erster Klasse (Funktionen sind Daten) und höherer Ordnung (Funkionen können andere Funkionen als Parameter nehmen), und kann damit der Familie der Funktionalen Programmiersprachen zugeordnet werden.

Der Compiler ist in TypeScript geschrieben, läuft in Node.js, und lässt sich genauso gut in CLI-basierte Entwicklungsumgebungen oder emacs integrieren wie in Eclipse oder VisualStudio. Es unterstützt die gängigen JavaScript-Modulsysteme und skaliert gut in großen Softwareprojekten. Ich will im Folgenden zunächst die Sprache vorstellen und versuchen, ein Gefühl zu dafür geben, was es heißt, in TypeScript zu denken. Ein Abschnitt über Entwicklungswerkzeuge und das Arbeiten mit TypeScript-Code folgt danach.

Dem eiligen Leser, der sowohl in JavaScript als auch in C#, Java o.ä. Routine hat, mag es genügen, die Code-Schnipsel zu lesen und den erklärenden Text auszulassen.

Fehler finden in JavaScript

TypeScript bietet einen Spielplatz (hier ohne SSL) im Netz an, auf dem Code-Schnipsel nach JavaScript übersetzt und ausgeführt werden können. Zum besseren Verständnis können Sie die folgenden Beispiele wärend des Lesens hier eingeben und mit den Übersetzungen zu vergleichen. Hier als erstes Beispiel ein einfaches „Hello-World“-Programm:

var msg = "Die Sonne scheint";
window.alert(msg)

So sollte das im Playground aussehen:

(Screenshot)

Zunächst fällt auf, wie durchlässig die Abstraktionsschicht zwischen Quell- und der Zielsprache ist. Jedes JavaScript-Programm ist ein TypeScript-Programm; Wenn man also einfachen JavaScript-Code in das TypeScript-Feld auf der linken Bildhälfte eingibt, tut der Compiler (fast) gar nichts. Auf der rechten Bildhälfte im generierten JavaScript-Code sieht man, dass in der zweiten Zeile ein Semikolon angehängt wurde; ansonsten bleibt der Code unverändert.

Dabei hat TypeScript bereits einiges geleistet: Für jeden Ausdruck im Quellcode wurde automatisch ein statischer Typ hergeleitet und die Korrektheit des gesamten Programms (bezüglich dieser Typen) festgestellt. Außerdem wurden die Typen in den Playground gereicht, wo man sie sich anzeigen lassen kann, in dem man mit der Maus auf einen Ausdruck zeigt (msg hat etwa den Typ string).

Auch beim Debugging leistet der Compiler bereits gute Dienste, ohne auf Sprach-Erweiterungen zuzugreifen. Wenn man den folgenden Code in den Playground eingibt:

var msg = {
    header: "Die Sonne scheint",
    details: [
        "Aussicht positiv",
        "schwacher Wind aus verschiedenen Richtungen"
    ];
};
window.alert(msg.haeder);

… werden zwei Stellen rot unterstrichen, und durch Berührung mit der Maus kann man sich die Fehler anzeigen lassen:

',' expected.

Und, nachdem das anstößige ; entfernt oder durch ein , ersetzt ist, der Buchstabendreher in header:

The property 'haeder' does not exist on value of type
'{ header: string; details: string[]; }'.

Für den Entwickleralltag bedeutet das eine enorme Verbesserung: Statt den Code in den Browser oder in node-js zu laden und eine Unit-Testsuite auszuführen, um Tipp- und Syntaxfehler zu finden, kann man nun im Editor auf Knopfdruck auf den nächsten Tippfehler gesetzt werden. (Dazu später mehr.)

Typen zur Dokumentation von JavaScript-Code

Ein Typsystem ist eine Meta-Sprache, in der Aussagen über die Struktur einer darunterliegenden Programmiersprache gemacht werden können. Das Typsystem von TypeScript ist also eine Sprache, in der man über JavaScript reden kann.

Wie wir bisher gesehen haben, kann der TypeScript-Compiler dies bis zu einem gewissen grad tun, ohne uns damit zu behelligen (es sei denn natürlich, es gibt Typfehler zu berichten). In der Praxis interessant wird es aber erst, wenn wir die Typ-Sprache in Form von Annotationen verwenden können, um dem Compiler unseren Code genauer zu erklären. Dadurch erhält man nicht nur die Garantie, dass zur Laufzeit eine große Klasse von Fehlern nicht mehr auftreten kann, sondern man bekommt quasi kostenlos maschinell verifizierte Quellcode-Dokumentation.

Typ-Annotationen in TypeScript können an jeder Stelle stehen, an der ein Name definiert wird, und werden mit : eingeleitet:

var msg : string = "Die Sonne scheint";
window.alert(msg);

Anders als die JavaScript-Typen, die zur Laufzeit mit typeof abgefragt werden können, existieren die Typ-Annotationen von TypeScript nur in der Übersetzungsphase, wo sie der Korrektheitsanalyse dienen. Der JavaScript-Code enthält keine Spur mehr von : string:

var msg = "Die Sonne scheint";
window.alert(msg);

(Wenn ich im zweiten Teil dieses Artikels auf Interfaces und Klassen eingegangen bin, wird klar werden, warum man TypeScript-Typen zur Laufzeit behalten möchte.)

Neben den einfachen Basistypen string, number, und boolean, gibt es Typen für Funktionen und (natürlich) Objekte und Arrays:

var f : (number, string) => string;
f = function(msgNumber, msgHeader) {
    return msgNumber.toString() + ": " + msgHeader;
};

var msg : {
    header : string;
    details : string[];
} = {
    header: "Die Sonne scheint",
    details: [
        "Aussicht positiv",
        "schwacher Wind aus verschiedenen Richtungen"
    ]
};

Der aufmerksame Leser hat vielleicht bemerkt, dass die Implementierung von f in ein eigenes Statement gerutscht ist. Das liegt daran, dass der Typ ((number, string) => string) im Gegensatz zu string nicht in Typ-Annotationen direkt verwendet werden kann. Das ist eine seltsame Ausnahme und ein Bruch mit der Idee der funktionalen Programmierung, aber glücklicherweise gibt es eine spezielle Schreibweise, die ohnehin besser zu lesen ist:

var f = (msgNumber : number, msgHeader : string) : string => {
    return msgNumber.toString() + ": " + msgHeader;
}

Im zweiten Teil dieses Artikels werden wir mehr über Funktionen, Typen, Klassen und Module lernen.

An dieser Stelle sei nur noch angemerkt, dass auch die etwas klobige Typ-Annotation von msg im letzten Beispiel eleganter ausgedrückt werden kann. Objekt-Typen kann man nämlich wie JavaScript-Ausdrücke einem Namen zuweisen:

interface IMsg {
    header : string;
    details : string[];
}

var msg : IMsg = {
    header: "Die Sonne scheint",
    details: [
        "Aussicht positiv",
        "schwacher Wind aus verschiedenen Richtungen"
    ]
};

Installation und Benutzung

Um den TypeScript-Compiler unter Linux zu installieren, benötigt man node und npm. Beides ist in allen verbreiteten Distributionen enthalten. Wer sicher gehen will, dass er die aktuellste Version hat, kann direkt von der Website installieren:

$ curl -O http://nodejs.org/dist/v0.10.29/node-v0.10.29.tar.gz
$ tar xvpzf node-v0.10.29.tar.gz
$ cd node-v0.10.29
$ ./configure --prefix=$HOME/opt  # (oder ein Pfad Ihrer Wahl)
$ make
$ make install
$ node --version

Danach ist die Installation von TypeScript ein Kinderspiel:

$ npm install -g typescript
$ tsc --version

TypeScript wird in vielen IDEs unterstützt (insbesondere Eclipse und VisualStudio). Aber auch wer emacs, vi, gmake etc. als IDE verwendet, findet sich schnell zurecht:

$ cat > code.ts
var msg : string = "Immer noch gutes Wetter.";
console.log(msg);
^D
$ tsc code.ts
$ node code.js
Immer noch gutes Wetter.

Der entstehende JavaScript-Code kann nun genau wie der von Hand geschriebene in den Browser eingebunden werden.

tsc hat ein eigenes Format für Zeilen- und Spaltennummern in Fehlermeldungen. Wer die einzeiligen Fehlmeldungsbandwürmer nicht mag oder seine IDE schon auf den de-Facto-Standard von gcc eingerichtet hat, kann sich mit perl die Fehlermeldung umformatieren:

$ tsc code.ts
/mnt/slig-sda3/home/ghcjs/code.ts(1,5): error TS2011: Cannot convert 'string' to 'number'.
$ tsc code.ts 2>&1 | perl -pe 's/^(\S+)\((\d+),(\d+)\):(.*)$/$1:$2:$3:\n$4\n/'
/mnt/slig-sda3/home/ghcjs/code.ts:1:5:
 error TS2011: Cannot convert 'string' to 'number'.

tsc lässt sich übrigens auch im „watch“-Modus betreiben:

$ tsc -w code.ts

Nun können Sie code.ts im Editor bearbeiten, und bei jedem Schreiben wird tsc die Datei neu übersetzen und ggf. Fehler ausgeben:

Recompiling (
    /mnt/slig-sda3/home/ghcjs/code.ts
    /mnt/slig-sda3/home/ghcjs/tmp/lib/node_modules/typescript/bin/lib.d.ts):
/mnt/slig-sda3/home/ghcjs/code.ts(1,5): error TS2011: Cannot convert 'string' to 'number'.

Recompiling (
    /mnt/slig-sda3/home/ghcjs/code.ts
    /mnt/slig-sda3/home/ghcjs/tmp/lib/node_modules/typescript/bin/lib.d.ts):

Stay tuned…

Auf einige unbehandelte Themen möchte ich zum Abschluss nur kurz verlinken:

  • sourceMaps, mit denen der Browser den TypeScript-Quellcode im debugger anzeigen kann, während er im Hintergrund durch die korrespondierenden Zeilen im generierten JavaScript-Code läuft;
  • Integration mit den üblichen Modulsystemen requirejs/amd und commonjs;
  • eine Bibliothek von Interfaces mit einer langen Liste von JavaScript-Bibliotheken und Frameworks, mit denen Typfehler in Aufrufen in diese Bibliotheken beim Übersetzen abgefangen werden können;
  • tslint für weitergehende Coding-Policy-Checks.

Der zweite Teil dieses Artikels erscheint in den nächsten Wochen. Dort werde ich auf fortgeschrittenere und Abstraktionswerkzeuge in TypeScript eingehen und sprachliche Feinheiten und Designentscheidungen diskutieren.