Microsoft hat TypeScript veröffentlicht, laut offizieller Ansage eine Programmiersprache for application-scale JavaScript development. Meiner Wahrnehmung nach wurde TypeScript in der Webentwickler-Welt mit einer gewissen Skepsis aufgenommen, was ich spontan durchaus nachvollziehbar finde – immerhin ist es eine Technologie für das Web von Microsoft und das kollektive Gedächtnis der Webentwickler vergisst kleine Sünden wie den IE6 und andere Untaten nicht so schnell.

Aber da ich es in meinen HTML5-Seminaren immer öfter mit Entwicklern aus der Microsoft-Ecke zu tun habe, komme ich nicht umhin, mir TypeScript mal vorurteilsfrei reinzuziehen, denn irgendwer wird bestimmt mal danach fragen. Das Reinziehen ist gar nicht mal so trivial, denn leider versteckt die die offizielle Webseite viele nützliche Informationen zugunsten von eher Marketing-fokussierter Prosa (auf deren Inhalt sich dann auch die meisten der hämischen Kommentare beziehen). Ich habe es aber trotzdem mal anhand der Specs versucht. In diesem ersten Teil will ich versuchen, die wichtigsten Fakten rund um TypeScript herauszuarbeiten, mein Kommentar zu dem Ganzen folgt dann in einem zweiten Artikel.

Was ist TypeScript?

TypeScript ist ein Superset von JavaScript – jedes gültige JavaScript-Programm ist auch ein (syntaktisch) gültiges TypeScript-Programm. Die Erweiterungen von TS werden vom Compiler in stinknormales JavaScript umgewandelt, so dass man TypeScript mit jedem Browser der Welt oder auch mit Node.js benutzen kann, solange man es vorher kompiliert. Der Vergleich mit Googles ungleich ambitionierterem Dart ist also weniger zutreffend als z.B. mit CoffeeScript. Bei Dart ist die JavaScript-Kompilierung nur eine Krücke, bei TypeScript das Endziel.

Die Features, die TypeScript neu einführt, fallen in zwei Kategorien: einerseits gibt es ein optionales Typsystem, andererseits sind mehrere Funktionen aus ECMAScript 6 übernommen worden. Das Typsystem erlaubt es, Variablen fest einen Typ zuzuweisen. Diese Information können Compiler und IDEs benutzen, um Fehler zu schmeißen bzw. bessere Autovervollständigung anzubieten. Die ES6-Features ermöglichen es, so zu programmieren, als sei der neue JavaScript-Standard (bzw. Teile davon) bereits fertig und in allen Browsern zu finden. Das Endprodukt ist trotz all dieser Features immer komplett normales JS, so wie man es auch händisch schreiben würde. Selbst wenn man volles Rohr auf TS-Features setzt …

class Auto {
  private kilometer: number = 0;
  constructor(public modell, public baujahr){}
  public fahren(km: number){
    this.kilometer += km;
  }
  public getKilometer(){
    return this.kilometer;
  }
}

interface Fahrzeug {
  modell: string;
  baujahr: number;
  getKilometer: () => {};
}

var karre = new Auto('VW Käfer', 1984);
karre.fahren(20);

function describe(auto: Fahrzeug) {
  return "Ein " + auto.baujahr + "er " + auto.modell +
         " mit " + auto.getKilometer() + "km auf der Uhr";
}

… ist das Endprodukt so normales JavaScript, dass es selbst im IE6 laufen würde:

var Auto = (function () {
    function Auto(modell, baujahr) {
        this.modell = modell;
        this.baujahr = baujahr;
        this.kilometer = 0;
    }
    Auto.prototype.fahren = function (km) {
        this.kilometer += km;
    };
    Auto.prototype.getKilometer = function () {
        return this.kilometer;
    };
    return Auto;
})();
var karre = new Auto('VW Käfer', 1984);
karre.fahren(20);
function describe(auto) {
    return "Ein " + auto.baujahr + "er " + auto.modell + " mit " + auto.getKilometer() + "km auf der Uhr";
}

Was also ist Typescript? Es ist kein Dart, sondern eigentlich nur ein JavaScript-Dialekt, dessen Compiler ein Typsystem und einen ES6-Transpiler implementiert und dessen Endprodukt stinknormales JavaScript ist.

How to Typescript

Der TypeScript-Compiler ist (natürlich) in TypeScript geschrieben und fluppt daher in jeder handelsüblichen JavaScript-Umgebung. Als Open-Source-Software (Apache License) lässt sich das gute Stück entweder über CodePlex (eine Art Github für MS) oder via npm install -g typescript in Node.js beschaffen. Die Datei foo.ts kompiliert man dann durch den Aufruf tsc foo.ts und erhält dafür die Datei foo.js.

Als Tools gibt es neben dem Plugin für Visual Studio 2012 auch Erweiterungen für Vim, Emacs und Sublime Text 2. Darüber hinaus existiert auch eine Online-Sandbox.

Das Typsystem

JavaScript ist eine Sprache mit einem schwachen, dynamischen Typsystem. Das lässt sich komödiantisch ausschlachten (ab 01:20), hat aber primär den Effekt, dass ein JS-Programmierer dem Interpreter weder vorbeten muss, welche Datentypen gewisse Objekte haben noch sich manuell um deren ggf. nötige Umwandlung kümmern muss. Die JS-Engine erkennt die Art der verwendeten Datentypen selbstständig (Duck Typing) und wenn zwei Objekte unterschiedlichen Typs aufeinander treffen, wird einer der beiden Unfallgegner automatisch konvertiert:

// Das sieht wie ein String aus
var s = "Hallo Welt";

// Das sieht wie eine Zahl aus
var n = 1337;

// String plus Zahl geht nicht, also wird die Zahl
// gemäß der JS-Regeln vor der Operation umgewandelt
var x = s + n;  // Ergibt den String "Hallo Welt1337"

Dieses Feature lässt allerdings nicht wenigen Programmieren die Haare zu Berge stehen. Einerseits befürchten sie, dass bei versehentlichen Typumwandlungen schwer zu diagnostizierende Bugs entstehen und sie hätten es lieber, wenn bei der Kollision zweier Typen ein Fehler gemeldet wird. Andererseits soll ein dynamisches Typsystem auch die Entwicklung von IDEs mit intelligenter Autovervollständigung und anderen fortschrittlichen Funktionen schwerer machen. Als Antwort auf diese Bedenken führt TypeScript ein statisches Typsystem ein, das alle Wünsche der Haare-zu-Berge-Fraktion erfüllt.

In TypeScript-Code kann bei Variablen angegeben werden, welchen Typ sie haben sollen:

// Ein String
var s: string = "Hallo Welt";

// Eine Zahl
var n: number = 1337;

Diese Typannotationen sind optional. Fehlen sie, findet der TS-Compiler selbst heraus, welchen Datentyp ein Objekt hat (Typinferenz). Sinn des Ganzen ist, dass Tools (und Entwickler) sofort erkennen können, welche Sorte von Objekt in einer Variable vorkommen sollte und der Compiler kann Alarm schlagen, wenn diese Regeln mißachtet werden:

// Der Compiler meldet: "Cannot convert 'number' to 'string'"
var s: string;
s = 42;

// Der Compiler meldet: "Supplied parameters do not match any signature of call target"
function sagHallo(name: string){
  return "Hallo " + name;
}
sagHallo(42);

Das Ganze funktioniert einerseits mit den aus JS bekannten Primitiven (String, Number usw.), kann aber auch auf Objekte angewendet werden. So lässt sich zum Beispiel ein Array definieren, das nur Strings enthalten darf:

var arr: string[] = [];

Für Eigenbau-Objekte lässt sich ein Interface definieren, dass festlegt, wie ein Objekt aufgebaut sein muss:

interface Fahrzeug {
  modell: string;
  baujahr: number;
}

function describe(auto: Fahrzeug) {
  return "Ein " + auto.baujahr + "er " + auto.modell;
}

describe({
  modell: 'Käfer',
  baujahr: 1970
});

Auch Funktionen können als Typ angegeben werden … mitsamt der Argumente, die die Funktion übergeben bekommen soll. Das erscheint besonders bei Callbacks sinnvoll:

function fireCb(callback: (i: number) => {}){
  for(var i = 0; i < 5; i++){
    callback(i);
  }
}

// Klappt
fireCb(function(i){
  console.log(i);
});

// Klappt nicht, da das Argument "foo" nicht gewünscht ist
// Supplied parameters do not match any signature of call target:
// Call signatures of types '(i: any,foo: any) => any' and '(i: number) => {}' are incompatible:
// Call signature expects 1 or fewer parameters
fireCb(function(i, foo){
  console.log(i);
});

In diesem Code fällt der etwas befremdlich aussehende Schnipsel (i: number) => {} ins Auge. Dabei handelt es sich um eine neue Schreibweise für Funktionen, die in ECMAScript 6 eingeführt werden soll und die in Typescript bereits unterstützt wird.

Die ES6-Features

ECMAScript 6 ist die kommende Ausbaustufe von JavaScript (zu der ich rein zufällig eine Artikelserie parat habe) und befindet sich in einem Zustand höchster Unfertigkeit. Ungeachtet dessen gibt es bereits einige Programme, die ES6-Code in heute schon funktionierendes JavaScript übersetzen – die sogenannten Transpiler. Auch in TypeScript steckt ein solcher Transpiler und manches ES6-Feature kann hier schon benutzt werden.

Die etwas komisch aussehende Funktionssystax entspringt (das ist kein Witz) primär dem Wunsch, nicht mehr das lange Wort function ausschreiben zu müssen. Und wenn man schon dabei ist, könnte man die neue Form doch auch gleich mit einem besonderen Scope-Binding ausstatten. So ging man also hin, borgte sich von CoffeeScript den fat arrow und schon war man fertig:

var fn = () => {
  console.log(this.foo);
};

// Entspricht (und kompiliert in TypeScript zu):

var _this = this;
var fn = function () {
  console.log(_this.foo);
};

Ebenfalls in ES6 geplant und in Typescript umgesetzt ist ein Klassensystem, das zwar recht simpel ist, aber grundsätzlich alles an Bord hat, was man so erwarten würde:

class Auto {
  private kilometer: number = 0;
  constructor(public modell: string, public baujahr: number){}
  public fahren(km: number){
    this.kilometer += km;
  }
  public getKilometer(){
    return this.kilometer;
  }
}

var karre = new Auto('VW Käfer', 1984);
karre.fahren(20);
var tacho = karre.getKilometer(); // 20

Der Code, den der Typescript-Compiler hieraus erzeugt, ist eine relativ simple Constructorfunktion nebst Prototype, bei dem die private-Properties nicht besonders (z.b. durch Closure-Versteckspiele) geschützt sind:

var Auto = (function () {
    function Auto(modell, baujahr) {
        this.modell = modell;
        this.baujahr = baujahr;
        this.kilometer = 0;
    }
    Auto.prototype.fahren = function (km) {
        this.kilometer += km;
    };
    Auto.prototype.getKilometer = function () {
        return this.kilometer;
    };
    return Auto;
})();

Allein der Compiler verhindert, dass ein direkter Zugriff auf private Objekte wie karre.kilometer erfolgt.

Wer Klassen haben will, möchte diese vermutlich auch in Modulen organisieren natürlich haben ES6 und TypeScript entsprechende Funktionalität an Bord:

// modul.ts
export class Auto {
  private kilometer: number = 0;
  constructor(public modell: string, public baujahr: number){}
  public fahren(km: number){
    this.kilometer += km;
  }
  public getKilometer(){
    return this.kilometer;
  }
}

// main.ts
import fahrzeug = module("modul");
var karre = new fahrzeug.Auto('VW Auto', 1984);
karre.fahren(20);

Der Compiler baut aus modul.ts wahlweise CommonJS-Module für den Einsatz in Node.js und anderen serverseitigen Umgebungen …

var Auto = (function () {
    function Auto(modell, baujahr) {
        this.modell = modell;
        this.baujahr = baujahr;
        this.kilometer = 0;
    }
    Auto.prototype.fahren = function (km) {
        this.kilometer += km;
    };
    Auto.prototype.getKilometer = function () {
        return this.kilometer;
    };
    return Auto;
})();
exports.Auto = Auto;

… oder AMD-Module für RequireJS und Konsorten:

define(["require", "exports"], function(require, exports) {
    var Auto = (function () {
        function Auto(modell, baujahr) {
            this.modell = modell;
            this.baujahr = baujahr;
            this.kilometer = 0;
        }
        Auto.prototype.fahren = function (km) {
            this.kilometer += km;
        };
        Auto.prototype.getKilometer = function () {
            return this.kilometer;
        };
        return Auto;
    })();
    exports.Auto = Auto;
});

Wenn eines Tages ES6-Module wie geplant Einzug in die JavaScript-Engines dieser Welt finden, wird diese Übersetzung dann vermutlich überflüssig.

Fazit

Zusammengefasst ist TypeScript also ein JavaScript-Superset, das JS um Features aus der ECMAScript-Zukunft sowie ein eigenes Typsystem erweitert. Da all diese Features ausschließlich im Compiler stattfinden und das Endprodukt normales JavaScript ist, sind keine Browser-Updates oder Polyfill-Scripts nötig um mit TS loszulegen.

Die Preisfrage ist nun: braucht man das? Wenn ja, wer braucht das? Will man das überhaupt in der JavaScript-Welt herumgeistern sehen? Und wird TypeScript eine Nutzerschaft finden oder in der Bedeutungslosigkeit versinken? Meine Meinung dazu gibt es die Tage in Teil 2.