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:

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:
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.