This article is also available in English

Zehn Jahre nach der Veröffentlichung von TypeScript 0.8 ist die von Microsoft entwickelte Programmiersprache nicht mehr aus der Softwareentwicklung wegzudenken. Entwickler:innen schätzen an ihr vor allem, dass sie auch gängige JavaScript-Bibliotheken wie AngularJS oder jQuery unterstützt. Auch die Integration in diverse Build-Management-Tools wie Gradle oder Grunt ist mithilfe von Plugins ohne Weiteres möglich.

Mittlerweile steht TypeScript bei Version 4.6 und zu den genannten Vorteilen sind noch viele weitere gekommen. Nichtsdestotrotz beziehen sich laut dem „Popoularity of Programming Languages“-Index 2021 nur 1,91 % aller Suchanfragen für Tutorials auf TypeScript – bei JavaScript sind es immerhin 8,81 %, Spitzenreiter mit 29,66 % ist Python. Die Tendenz in der Beliebtheit von TypeScript mag zwar steigend sein, aber die Auswertung zeigt doch deutlich, dass die Sprache von vielen noch stiefmütterlich behandelt wird. Zeit, noch einmal auf die Anfänge und wichtigsten Vorteile zu blicken.

Das ursprüngliche Technologieduo HTML und HTTP hatte sich über die Jahre nicht nur beständig weiterentwickelt, es bekam auch noch zahlreiche weitere Sprachen, Protokolle und Schnittstellen zur Seite gestellt. Eine besondere Rolle spielt dabei JavaScript. In der modernen Entwicklung avancierte sie zur Lingua Franca. Nicht nur, weil sie die Basis für dynamische Applikationen ist, sondern auch, weil mit Node.js ein Äquivalent auf der Serverseite zur Verfügung steht. Mittlerweile übernimmt sie mithilfe von ausgeklügelten Werkzeugen nach und nach HTML und CSS, sodass Entwickler:innen alle anfallenden Aufgaben zentral mit dieser einen Sprache durchführen können.

Gleichzeitig ist JavaScript umstritten, denn das Sprachdesign ist historisch gewachsen und weist reihenweise Ungereimtheiten auf. Ein Objekt und ein Array addieren? Kein Problem für JavaScript! Das Ergebnis dieser Addition, die man wohl in dieser Form in keinem Mathematik-Lehrbuch vorfinden würde, ist ein String. Auch die prototypenbasierte Objektorientierung wirkt angesichts der sonst so dominanten klassenbasierten Sprachen etwas antiquiert. Doch das JavaScript-Designkomitee TC39 (oder genauer: ECMAScript), arbeitet unentwegt daran, die verquasten Strukturen aufzuräumen. Populäre Neuerungen sind z.B. Promises für vereinfachte asynchrone Programmierung oder const und let, womit sich der Scope von Variablen einfacher handhaben lässt.

Sämtliche Änderungen müssen jedoch monoton sein: Es handelt sich also stets um neue Features, da man im TC39 tunlichst vermeiden möchte, funktionierende Webseiten durch inkompatible Änderungen in das Abseits zu bringen. In der Konsequenz bedeutet das: Auch in zehn Jahren werden noch sinnlose Additionen gestattet sein. Hinter dieser flapsigen Aussage verbergen sich tatsächlich handfeste Probleme für die Softwarequalität. Doch gerade auf einer derart populären Plattform wie dem World Wide Web ist korrekt funktionierender JavaScript-Code unerlässlich. Immerhin wickeln die Menschen heutzutage fast alles darüber ab – Bankgeschäfte, Arzttermine oder Briefpost.

JavaScript als Zwischensprache

Seit einiger Zeit zeichnet sich daher ein Trend ab: Entwickler:innen schreiben ihren Code in einer anderen Sprache und nutzen dann einen Compiler, um diesen Code in JavaScript umzuwandeln. Solche Compiler werden oft Transpiler genannt. Für Browser ist es schließlich egal, ob der von ihnen ausgeführte JavaScript-Code handgeschrieben oder maschinell erzeugt ist. Leider lassen viele dieser Inkarnationen der „Sprache X“ die Frage unbeantwortet, wie sie es mit der Interoperabilität halten. Da es aber mittlerweile Millionen von fertigen JavaScript-Bibliotheken gibt, ist eine Integration mit bestehendem JavaScript-Code unumgänglich. Ansonsten müssten Entwickler:innen Abertausende Zeilen von Code immer wieder neu implementieren. Wie eine Erhebung der beiden Computerwissenschaftler Leo A. Meyerovic und Ariel Rabkin aus dem Jahr 2013 zeigt, sind verfügbare Open-Source-Bibliotheken einer der wichtigsten Gründe, warum sich Entwickler:innen für eine Programmiersprache entscheiden.

An dieser Stelle setzt TypeScript an; eine Sprache, die erstmals 2012 von Microsoft vorgestellt worden ist und mittlerweile den Versionsstand 4.2 erreicht hat. Auf den ersten Blick handelt es sich genau um eine solche „Sprache X“, die ein Compiler in JavaScript überführt. Doch dann fällt unmittelbar auf, dass die Syntax von TypeScript der von JavaScript folgt. Genau genommen ist sogar jedes gültige JavaScript-Programm auch ein gültiges TypeScript-Programm. Bestehender Code lässt sich somit problemlos weiterverwenden. Aber warum sollten Entwickler:innen TypeScript dann benutzen, wenn es sowieso fast JavaScript ist? Die Antwort liegt im Namen: TypeScript kennt (optionale) Typannotationen, mit denen man Funktionen, Variablen und Klassen versehen kann. Zwar entfernt der Compiler sie wieder, aber nicht, ohne sie gründlich mit dem geschriebenen Code abzugleichen. Außerdem prüft er jede Operation, jeden Methodenaufruf und jeden Import. So quittiert er zum Beispiel unsere Addition von Objekt und Array mit der folgenden Meldung:

error TS2365: Operator '+' cannot be applied to types '{}' and 'any[]'

In der Praxis erscheint TypeScript wie eine natürliche Erweiterung von JavaScript. Wenn Entwickler:innen Sprachen wie Java gewohnt sind, fühlen sie sich direkt wie zu Hause. Das zeigt das Beispiel eines Zeichenprogramms im Browser. Zunächst würden Entwickler:innen dafür eine Schnittstelle für geometrische Formen anlegen. TypeScript bietet dafür das bekannte Schlüsselwort interface an, welches in JavaScript fehlt. Etwas ungewöhnlich: Typangaben stehen mit Doppelpunkt getrennt hinter Parametern und Methoden.

interface Shape {
  scale(factor: number): void;
  move(x: number, y: number): void;
  render(dom: HTMLCanvasElement): void;
  size(): [number, number];
}

Das hier definierte Interface bietet Methoden zur Manipulation der Form (scale und move), Rendering auf einem <canvas>-Element (render) und die Berechnung der Objektgröße (size) an. Letztere gibt ein Paar aus zwei Zahlen zurück, welches wie folgt benutzt werden kann:

const [width, height] = shape.size();

Obwohl die Interface-Deklaration genau so aussieht wie in vielen anderen objektorientierten Sprachen, geht TypeScript einen Sonderweg: beim Typchecking spielt es keine Rolle, aus welchem Interface eine Methode stammt, TypeScript prüft einzig und allein die Methodensignatur. Dazu ein Beispiel: Das obige Interface fasst TypeScript als kompatibel zu diesem hier auf, obwohl es dieses nicht erweitert.

interface Renderable {
  render(canvas: HTMLCanvasElement): void;
}

Dieses „strukturelle Typing“ ist das Pendant zum „Duck-Typing“ in JavaScript und erleichtert die Entwicklung ungemein. Programmierer:innen können so flexibler verschiedene Bibliotheken integrieren. Außerdem sind sie seltener zu „Glue-Code“ gezwungen. Implementieren lässt sich das Interface dann erwartungsgemäß:

class Rectangle implements Shape {
  private x: number;
  private y: number;
  private height: number;
  private width: number;

  constructor() {
    this.x = this.y = 0;
    this.height = this.width = 1;
  }

  scale(factor: number) {
    this.height *= factor;
    this.width *= factor;
  }

  // ...
}

Diese (unvollständige) Definition würde der Compiler übrigens monieren, weil einige Methoden nicht implementiert sind. Genau wie in Java ist die Mehrfachvererbung von Interfaces erlaubt. Es darf aber nur höchstens eine Superklasse geben.

Definitiv getypt

Was passiert, wenn ich in einem Projekt TypeScript- mit „rohem“ JavaScript-Code mischen möchte? Praktisch passiert das fast immer, wenn Entwickler:innen per npm fertige Pakete herunterladen, denn diese kommen fast immer als Bündel von JavaScript-Dateien. Der TypeScript-Compiler kann zwar zu einem gewissen Grad auch ungetypten Code analysieren und daraus Rückschlüsse ziehen, doch das funktioniert nur begrenzt: Wenn der Compiler nicht mehr weiterweiß, benutzt er automatisch den berühmt-berüchtigten any-Typ, der eine weitere Prüfung faktisch aushebelt. Praktisch stehen Entwickler:innen damit wieder am Anfang und haben nichts gewonnen. Um dieses Problem zu lösen, hat die Entwicklungsgemeinschaft das „DefinitelyTyped“-Repository aus der Taufe gehoben. Zum Redaktionsschluss gab es dort 7000 Pakete mit Typdefinitionen. Sie zu nutzen, ist denkbar einfach: Für React reicht beispielsweise der Aufruf

npm install react @types/react

Damit können Entwickler:innen sowohl die React-Bibliothek als auch die zugehörigen Typen herunterladen. Der TypeScript-Compiler findet sie dann automatisch und bindet sie in den Checking-Prozess mit ein. Technisch gesehen handelt es sich bei diesen Paketen um Dateien im .d.ts-Format, also TypeScript-Quelltext ohne Implementierung von Methoden, nur bestehend aus reinen Typdeklarationen. Eine ganze Reihe von weiteren Bibliotheken liefert diese .d.ts-Dateien direkt aus, ohne den Umweg über DefinitelyTyped. Für die meisten Pakete stehen die Typdefinitionen auf eine dieser beiden Arten zur Verfügung. Entwickler:innen können sich in der Praxis immer die Typchecks des Compiler zunutze machen kann.

Ganz nebenbei implementiert TypeScript neue Features von JavaScript, die das TC39-Komitee noch finalisiert. Zum Beispiel hielt die asynchrone Programmierung mit den Keywords async und await offiziell mit ES2017 Einzug in der Sprache. TypeScript unterstützte das schon 2015. Behutsam eingesetzt, können Entwickler:innen diesen Vorsprung nutzen, um schneller und solider neue Features im eigenen Code zu entwickeln. Damit erhalten sie einen echten Wettbewerbsvorteil.

Auch in aktuellen Compiler-Versionen können Entwickler:innen das sogenannte Target konfigurieren und dabei einen ES-Jahrgang bis zurück zu ES3 (anno 1999) auswählen.

TypeScript kann also neben Typechecking auch noch Aufgaben der Transpiler wie Babel oder Sucrase übernehmen und sorgt damit dafür, dass sich moderner Code auch auf älteren Browsern ausführen lässt. Zu beachten ist allerdings, dass sich nicht alle neueren JavaScript-Features – wie etwa Symbole – eins zu eins auf ältere Versionen abbilden lassen. Das Risiko wächst mit der Distanz zwischen Quellcode- und Target-Version.

Entwicklung mit TypeScript

Seine Hauptvorteile kann TypeScript aber auch bereits in der schnöden Entwicklung von Applikationen mit „Vanilla JS“ ausspielen – wenn Entwickler:innen also eine Webseite per Server-Side Rendering und ohne Frontend-Frameworks bauen. Moderne Browser bringen zahlreiche, mächtige APIs mit, die fast alle Bedürfnisse abdecken. Gepaart mit einem Editor wie VS Code kann TypeScript dafür mit gutem Tooling-Support aufwarten, zum Beispiel mit einer Code Completion.

Ein geradezu lebensrettendes Feature ist, dass TypeScript auch bestimmte „magische Strings“ bzw. „magische Konstanten“ erkennt. Wollen Entwickler:innen zum Beispiel per fetch eine Ressource zur Laufzeit abfragen, können sie zahlreiche Konfigurationsoptionen per String übergeben. Dank des Typcheckers brauchen sie sich nicht mehr zu merken, ob es mode: "same-origin" oder mode: "sameorigin" heißt. (Ersteres ist korrekt, letzteres wird im Editor rot unterkringelt.)

Ebenso kann TypeScript auch mit JSX – dem HTML-Dialekt von React – umgehen und prüft, ob die HTML-Attribute richtig geschrieben sind. Auch über das Typsystem und weitere Features von TypeScript ließe sich noch sehr viel schreiben. An dieser Stelle sei nur in Kürze angemerkt: Es deckt so gut wie alle gängigen Programmiermuster in JavaScript ab – etwa konditionale Typen, Überladung oder Proxy-Objekte – ab.

Fazit

TypeScript gibt es schon seit fast einem Jahrzehnt und es hat seitdem kontinuierlich an Bedeutung gewonnen. Viele Frameworks wie React oder Svelte unterstützen nicht nur eine breite Auswahl von Bibliotheken, sondern integrieren auch TypeScript. Manche nutzen es sogar als Primärsprache, etwa Angular.

Programmierer:innen unterstützt der Compiler durchgängig im Entwicklungsprozess, ohne dass sie sich in eine komplett neue Umgebung eingewöhnen müssen. Allerdings erkaufen sie sich diese Hilfe durch etwas komplexeres Tooling: TypeScript zieht einen Rattenschwanz von weiteren Paketen mit sich, zum Beispiel DefinitelyTyped. Doch der Aufwand lohnt sich, denn jeder Bug, der sich vermeiden lässt, spart Geld. Ausprobieren lässt sich TypeScript übrigens auch ganz ohne Installation im Playground.