Webentwicklung: Der neue TypeScript-Compiler in Go
(Bild: erzeugt mit KI durch iX)
Das TypeScript-Team portiert den Kern der Programmiersprache nach Go. Das macht die Sprache schneller und verbessert die Developer Experience.
TypeScript wurde einst aus der Not geboren, um die größten Schwächen von JavaScript auszugleichen. Seit Jahren ist es nun ein nahezu unverzichtbares Werkzeug in der Webentwicklung, das in seiner Entwicklung auf Feature-Ebene immer einen leichten Vorsprung zu JavaScript besitzt.
Microsoft hat kürzlich angekündigt, die Basis von TypeScript vollständig zu überarbeiten – also den Compiler, der TypeScript in JavaScript übersetzt, und weitere Komponenten, die für die Interaktion mit Tools und Entwicklungsumgebungen verantwortlich sind. Dabei hat Microsoft eine für viele überraschende Wahl getroffen. Die meisten haben damit gerechnet, dass, wenn jemand diesen Schritt geht, Rust als Programmiersprache zum Einsatz kommt, denn es gibt bereits populäre Rust-basierte Werkzeuge im JavaScript-Umfeld, etwa den Bundler Rspack.
Doch das TypeScript-Team mit Anders Hejlsberg als Lead Architect hat sich stattdessen für eine Reimplementierung in der Sprache Go entschieden. Das Ziel der nativen Implementierung in Go, die kompiliert wird – im Gegensatz zur Just-in-Time-Umwandlung der Skriptsprache TypeScript in Bytecode – ist eine Verbesserung der Ausführungszeit der TypeScript-Werkzeuge sowie eine spürbare Reduzierung des Arbeitsspeicherverbrauchs. Laut aktueller Benchmarks erzielt das neue TypeScript eine zehnfach schnellere Zeit beim Build verschiedener Projekte.
Dieser Artikel widmet sich den Fragen, warum diese Verbesserung so wichtig ist, wo die aktuellen Schwächen von TypeScript liegen und wie die Entwicklung des neuen Compilers, der unter dem Codenamen Corsa und unter der Version TypeScript 7 geführt wird, vonstatten geht.
Was ist TypeScript?
Im Jahr 2012 veröffentlichte Microsoft eine erste Version von TypeScript nach zwei Jahren Entwicklungszeit. Seit 2014 wird die Programmiersprache auf GitHub öffentlich einsehbar weiterentwickelt und unterliegt als Open-Source-Projekt der Apache-2.0-Lizenz. Mittlerweise hat TypeScript die Version 5.8 erreicht [1].
Im Kern ist TypeScript eine Programmiersprache, die JavaScript um Typsicherheit zur Kompilierungszeit ergänzt – mit den Zielen, besser größere Applikationen entwickeln zu können und eine Grundlage für bessere Entwicklungswerkzeuge zu schaffen.
Es gibt eine ganze Reihe von Gründen, die für den Einsatz von TypeScript statt JavaScript sowohl in Frontend- als auch in Backend-Projekten im Web sprechen:
- Statische Typisierung: JavaScript verfügt nur über ein schwaches Typsystem, das Laufzeitfehler begünstigt. Mit seiner weitestgehend optionalen statischen Typisierung bietet TypeScript einerseits große Flexibilität und hilft Entwicklerinnen und Entwicklern auf der anderen Seite dabei, potenzielle Fehler frühzeitig zu erkennen.
- Verbesserung der Codequalität: Gerade an Schnittstellen von Modulen in einer Applikation, aber auch bei Bibliotheken, erlaubt TypeScript eine strikte Definition von Schnittstellen, was die Wartbarkeit und Lesbarkeit des Codes deutlich verbessert. Das kommt vor allem größeren Projekten und wiederverwendbaren Bibliotheken zugute.
- Werkzeugunterstützung: Zahlreiche Werkzeuge im JavaScript-Ökosystem setzen auf TypeScript, um Entwicklern eine bessere Hilfestellung zu leisten. So können Entwicklungsumgebungen wie Visual Studio Code bei der Entwicklung einer Node.js-Applikation deutlich besser helfen, wenn die Typdefinitionen von Node.js installiert sind, insbesondere in Bezug auf die Autovervollständigung von Code und die Anzeige von Funktionssignaturen.
- Polyfills: TypeScript unterstützt moderne Features aus dem ECMAScript-Standard sehr früh und noch bevor die meisten Umgebungen wie Browser die Features flächendeckend integrieren. TypeScript kann verschiedene ECMAScript-Versionen als Ausgabe zu erzeugen und unterstützt damit auch ältere Umgebungen. Diese Möglichkeit reicht zurück bis zur Version ECMAScript 3 (diese Version des JavaScript-Standards wurde im Jahr 1999 veröffentlicht).
- Strukturierte Entwicklung: Zusätzlich zum Standard-JavaScript und der Typsicherheit führt TypeScript weitere Konstrukte wie Interfaces, Enums und Generics ein, die es erlauben, Datenstrukturen und Schnittstellen besser zu modellieren. Einige objektorientierte Designpatterns sind in nativem JavaScript nicht oder nur mit zusätzlichem Aufwand umsetzbar. Das Vorhandensein von Interfaces erleichtert hier die Arbeit erheblich.
- Strikte Umgebung: TypeScript ermöglicht es über Konfigurationsoptionen wie
strictNullChecks, häufig auftretende Fehler von vornherein zu vermeiden. - Framework- und Bibliotheksintegration: Viele Frameworks und Bibliotheken im JavaScript-Ökosystem sind entweder direkt in TypeScript umgesetzt oder bringen zumindest Typdefinitionen mit, sodass sie sich nahtlos in eine TypeScript-Umgebung integrieren und alle Vorteile ausspielen können.
Was bemerkenswert ist: TypeScript ist (noch) vollständig in TypeScript programmiert. Der Kern der Programmiersprache wird auch in Version 6 noch in TypeScript weitergeführt. Parallel dazu arbeitet das Team an Version 7, die dann die Go-Variante sein und die TypeScript-Implementierung ablösen wird, sobald die beiden Projekte eine Feature-Gleichheit erreicht haben. Der aktuelle Zeitplan des Entwicklungsteams sieht für Mitte des Jahres 2025 eine vollwertige Version des Kommandozeilen-Compilers vor, der in der Lage ist, die Typüberprüfung und Übersetzung von Quellcode durchzuführen.
Momentan funktioniert der TypeScript-Go-Compiler bereits nahezu vollständig für reinen TypeScript-Code. Was noch fehlt, ist die Unterstützung von JavaScript, JSDoc und JSX. Ende des Jahres 2025 wird eine Feature-vollständige Version erwartet, die Projekte bauen kann. Außerdem soll mit dem Language Service eines der wichtigsten Entwicklungsfeatures verfügbar sein.
Warum aber ist ein so drastischer Schritt wie ein Rewrite in einer anderen Programmiersprache überhaupt erforderlich? TypeScript spielt seine Vorteile vor allem in größeren Applikationen aus. Mit wachsender Codebasis wächst jedoch auch der Ressourcenbedarf von TypeScript beim Type Checking und beim Build. Das führt zum einen dazu, dass das Öffnen eines Projekts in der Entwicklungsumgebung lange dauert, da der Code hier durch den Language Service initial verarbeitet wird. Und auch der Build einer größeren Applikation kann sehr zeitintensiv werden. Zur reinen Laufzeit kommt noch ein weiteres, sogar noch größeres Problem durch den hohen Bedarf an Arbeitsspeicher. Dieser kann zu Speicherproblemen und im schlimmsten Fall zu Abstürzen durch Out-of-Memory-Fehlern führen.
(Bild: WD Ashari/Shutterstock.com)
Die enterJS 2025 [2] findet am 7. und 8. Mai in Mannheim statt. Die Konferenz bietet einen umfassenden Blick auf die JavaScript-gestützte Enterprise-Welt. Der Fokus liegt nicht nur auf den Programmiersprachen JavaScript und TypeScript selbst, sondern auch auf Frameworks und Tools, Accessibility, Praxisberichten, UI/UX und Security.
Highlights aus dem Programm:
- TypeScript – eine Anleitung zum Unglücklichsein [3]
- TypeScript-Funktionen dekorieren: Nicht nur zu Weihnachten! [4]
- Eigene KI-Agenten mit LangChain.js & LangGraph.js erstellen [5] (Workshop, 6. Mai)
Tickets sind im Online-Shop [6] erhältlich.
Details zum Rewrite
Der Rewrite von TypeScript in Go ist eigentlich kein Rewrite, sondern eine 1:1-Portierung des bestehenden Quellcodes. Damit will das Entwicklungsteam sicherstellen, dass die bestehenden Features in gleicher Form auch in der neuen Version verfügbar sind und sich vor allem auch bei Fehlermeldungen und Edge Cases gleich verhalten. Die JavaScript-Engine ist für den Einsatz im Browser gedacht und eignet sich weniger für CPU-intensive Aufgaben, wie sie im Zuge von TypeScripts Compile-Prozess anfallen. Außerdem sind eine echte Parallelisierbarkeit und der Einsatz von Shared Memory in JavaScript nur sehr eingeschränkt möglich.
Go bietet im Gegensatz zur Kombination aus TypeScript und JavaScript eine ganze Reihe von Vorteilen. So ist Go eine statisch kompilierte Sprache, deren Quellcode direkt in Maschinencode übersetzt wird. JavaScript ist dagegen eine Just-in-Time-Sprache, deren Quellcode die JavaScript-Engine erst bei der Ausführung in Bytecode umwandelt und anschließend interpretiert. Go verfügt über eine automatische Speicherverwaltung mit einem Garbage Collector, erlaubt Parallelisierung von Aufgaben und besitzt effiziente Möglichkeiten zum Datenaustausch wie Channels und Shared Memory.
Die beeindruckende zehnmal schnellere Performance der TypeScript-Go-Implementierung hat laut Anders Hejlsberg zwei Gründe. Zum einen ist der native Go-Code deutlich schneller im Vergleich zum JavaScript-Code der ursprünglichen Implementierung und zum anderen ist der Compiler mit der neuen Architektur in der Lage, bestimmte Aufgaben zu parallelisieren.
Dafür greift der neue Compiler in die Phasen des Verarbeitungsprozesses des Compilers ein. Die Verarbeitung einzelner Dateien durch Parser, Binder und Emitter kann der Compiler problemlos parallelisieren. Die Verantwortlichkeiten dieser drei Werkzeuge sind:
- Parser: Der Parser übernimmt den ersten Schritt im Compile-Prozess von TypeScript. Dazu liest er den TypeScript-Quellcode ein und wandelt ihn anhand der Regeln der Programmiersprache in eine Baumstruktur, den Abstract Syntax Tree (AST), um. Der AST steht für die syntaktischen Strukturen wie Funktionen, Klassen und Anweisungen.
- Binder: Der Binder verknüpft verwandte Deklarationen wie verschiedene Teile einer Interface-Definition, namensgleiche Funktionen und Module und nutzt dazu Symbole. Mit diesen Informationen kann das System Typinformationen über die Deklarationen sammeln und analysieren, um die Konsistenz und Korrektheit sicherzustellen.
- Emitter: Der Emitter stellt den letzten Schritt in der Verarbeitungskette dar und ist dafür verantwortlich, den vom Binder verarbeiteten und vom Checker geprüften Code in JavaScript-Code umzuwandeln.
Zwischen dem Binder und dem Emitter steht der Type Checker, der die Typkonsistenz des Quellcodes überprüft. Sind Typannotationen fehlerhaft oder werden Variablen oder Funktionen mit den falschen Typen genutzt, gibt er eine Fehlermeldung aus. Dieses Element des Compilers lässt sich nicht wie die übrigen Bestandteile parallelisieren. Stattdessen geht das Entwicklungsteam hier einen anderen Weg und verarbeitet den Quellcode mit mehreren Instanzen des Checkers. Jede Instanz ist dabei für einen Teil des Codes verantwortlich. Das führt zwar zu Überschneidungen bei der Überprüfung mit etwas Memory Overhead, beschleunigt den Prozess aber insgesamt.
Der erste Teil von TypeScript, den die Entwickler nach Go portiert haben, ist der Kommandozeilen-Compiler tsc, der den TypeScript-Code einer Applikation überprüft und in JavaScript übersetzt. Dieses Werkzeug kommt vor allem während des Build-Prozesses einer Applikation zum Einsatz.
TypeScript besteht jedoch nicht nur aus dem tsc. Eine der wichtigsten weiteren Komponenten ist der Language Service. Dieser Teil ist für die Komfort-Features in modernen Entwicklungsumgebungen mitverantwortlich. Der Language Service ist ein langlebiger Hintergrundprozess, der zu Beginn den gesamten Quellcode einliest und verarbeitet. Auf Basis der gesammelten Informationen stellt er die Grundlage für die Tooltips beim Hovern mit der Maus über Codestrukturen zur Verfügung, hilft bei der Navigation im Code und sorgt für das Highlighting von Fehlern im Code.
Für eine gute Developer Experience ist der Language Service von herausragender Bedeutung. Ein langsamer Verarbeitungsprozess schlägt sich in einem langwierigen Öffnen von Projekten in der IDE nieder, verzögerte Antwortzeiten schränken die Features der IDE ein und ein übermäßiger Speicherverbrauch verlangsamt die IDE und das Gesamtsystem.
In der Prioritätsreihenfolge bei der Portierung folgt der Language Service direkt nach dem Kommandozeilen-Compiler. Ein Prototyp des Language Service ist bereits jetzt für Visual Studio Code verfügbar. Zu den ersten umgesetzten Features gehören die Anzeige von Fehlern, die Unterstützung von Tooltips und das Springen zur Definition einer Variablen, Funktion oder eines Typs.
Neben dem Kommandozeilen-Compiler und dem Language Service arbeiten die Entwickler an einer Schnittstelle, über die andere Prozesse mittels Interprocess Communication mit dem Language Service kommunizieren und die Typinformationen abfragen können. Bei dieser Schnittstelle ist noch einiges an Konzeptarbeit offen. Fest steht, dass sie das LSP, das Language-Server-Protokoll, unterstützen soll.
TypeScript Corsa selbst ausprobieren
Das Team entwickelt die Go-Variante von TypeScript auf GitHub in einem eigenständigen Repository [7] mit dem Namen typescript-go. Es ist öffentlich verfügbar und ermöglicht es, die frühe Entwicklungsversion zu testen. Die aktuelle Version ist ausdrücklich nicht für den Produktivbetrieb geeignet und auch noch nicht vollständig Feature-kompatibel mit der ursprünglichen TypeScript-Version. Auf diese Weise erhofft sich das Entwicklungsteam frühes Feedback von der Community und bezieht auch die Maintainer von Werkzeugen, Bibliotheken und Frameworks zu einem sehr frühen Zeitpunkt mit ein.
Zum Zeitpunkt des Schreibens des Artikels muss typescript-go selbst gebaut werden. Dafür muss das Zielsystem einige Voraussetzungen erfüllen:
- Git: Für den Build von typescript-go ist der Quellcode des Projekts inklusive seiner git-submodules erforderlich. Dieser lässt sich zwar auch manuell herunterladen, einfacher ist jedoch die Verwendung von Git zum lokalen Klonen des Repositorys.
- Go: Zum Kompilieren empfiehlt die Dokumentation Go in der Version 1.24 oder höher.
- js: Die JavaScript-Abhängigkeiten werden mit npm verwaltet. Deshalb sollten auf dem Zielsystem Node.js und npm in einer aktuellen Version installiert sein.
- hereby: Das Projekt nutzt das npm-Paket hereby als Task Runner, um beispielsweise den Build zu starten oder die Tests auszuführen.
Als Basis für die folgenden Tests dient das offizielle Docker-Image von Go. Der Vorteil ist, dass mit diesem Setup keine zusätzliche Software auf dem Zielsystem erforderlich ist und die Experimente reproduzierbar sind. Einzige Voraussetzung hierfür ist eine Container-Runtime wie beispielsweise Docker.
Die Befehle aus Listing 1 sorgen dafür, dass eine gebaute und ausführbare Version von typescript-go im Container vorhanden ist. Die Version von typescript-go ist in der Lage, TypeScript-Projekte zu bauen, um sie anschließend auszuführen.
docker run -it --rm golang:bookworm
apt update && apt install -y nodejs npm
git clone --recurse-submodules https://github.com/microsoft/typescript-go.git
npm ci
npx hereby build
Listing 1: Bauen von ts-go in einem Container
Die Befehlsfolge startet einen Container auf Basis des Go-Images und installiert Node.js. Anschließend klont der git-Befehl das typescript-go-Repository mit allen zugehörigen Submodulen. npm ci steht für clean install und sorgt dafür, dass alle npm-Abhängigkeiten installiert werden. Der Aufruf von npx hereby build startet den Build-Task, der in der hereby-Konfiguration definiert ist, und baut damit typescript-go.
Nach dieser Prozedur ist im built/local-Verzeichnis das Kommando tsgo verfügbar. Es verhält sich standardmäßig wie der TypeScript-Compiler auf der Kommandozeile mit der Option --diagnostics. Es kompiliert den TypeScript-Quellcode anhand einer vorhandenen TypeScript-Konfiguration und gibt zusätzliche Diagnoseinformationen wie die Laufzeit der einzelnen Phasen oder den Speicherverbrauch aus.
Als ersten Testlauf soll der typescript-go-Compiler den Code aus Listing 2 übersetzen.
function add(a: number, b: number): number {
return a + b;
}
const result = add(1, 2);
console.log(result);
Listing 2: TypeScript-Quellcode für die Übersetzung
Voraussetzung für einen funktionierenden Build ist das Vorhandensein einer passenden tsconfig.json-Datei. Ander als der originale tsc ist die Go-Variante noch nicht in der Lage, eine solche Konfiguration zu generieren. Ein Aufruf von npx tsc --init nutzt die TypeScript-Variante, um die Voraussetzung zu erfüllen. Ein anschließender Aufruf von built/local/tsgo übersetzt den Code des Beispiels in JavaScript und macht ihn ausführbar. Schon bei diesem einfachen Beispiel zeigt sich ein deutlicher Unterschied zwischen der TypeScript- und der Go-Variante des Compilers.
Tabelle 1 stellt die Ergebnisse gegenüber. Sie zeigt für die ursprüngliche TypeScript-Variante sowie für tsgo im Single-Threaded-Modus und mit Parallelisierung, wie viel Speicher der Compile-Vorgang benötigte. Zudem listet sie die Zeit auf, die die einzelnen Phasen in Anspruch genommen haben, sowie die Gesamtzeit des Compile-Vorgangs.
| tsc | tsgo -singleThreaded | tsgo | |
| Verbrauchter Speicher | 54,411K | 18,536K | 19,494K |
| Parse-Zeit | 0,10s | 0,025s | 0,019s |
| Bind-Zeit | 0,06s | 0,008s | 0,006s |
| Check-Zeit | 0,01s | 0,001s | 0,002s |
| Emit-Zeit | 0,00s | 0,00s | 0,00s |
| Gesamtzeit | 0,17s | 0,034s | 0,027s |
Tabelle 1: Compile-Prozess einer einfachen Datei
Dieses Bild setzt sich fort, nicht nur bei kleinen TypeScript-Applikationen wie einer einfachen Nest.js-Applikation, die mit der Nest-CLI initialisiert wurde, sondern auch bei umfangreichen Codebasen wie Visual Studio Code, das in TypeScript geschrieben ist.
Tabelle 2 enthält die Ergebnisse des Compile-Vorgangs von date-fns, einer TypeScript-Bibliothek für Datums- und Zeit-Handling mit insgesamt 334.000 Zeilen. Für die TypeScript-, die Single-Threaded- und die Multi-Threaded-Variante schlüsselt die Tabelle den Speicherverbrauch sowie die Laufzeiten auf.
| tsc | tsgo -singleThreaded | tsgo | |
| Verbrauchter Speicher | 526,871K | 270,145K | 291,984K |
| Parse-Zeit | 0,79s | 0,246s | 0,164s |
| Bind-Zeit | 0,24s | 0,057s | 0,048s |
| Check-Zeit | 1,35s | 0,317s | 0,216s |
| Emit-Zeit | 0,36s | 0,184s | 0,096s |
| Gesamtzeit | 2,75s | 0,804s | 0,523s |
Tabelle 2: Compile-Prozess von date-fns
Die Optimierungen des neuen Compilers wirken sich also sowohl auf die Laufzeit als auch auf den Speicherverbrauch aus. Das Kommando tsgo -singleThreaded schaltet die Parallelisierung der Aufgaben im Compiler ab, sodass nur die Verbesserungen durch den nativen Go-Code sichtbar werden. Ohne diese Option parallelisiert der Compiler die Verarbeitung des Quellcodes und erreicht dadurch noch bessere Verarbeitungszeiten. Dies geht jedoch auf Kosten eines leicht erhöhten Speicherverbrauchs.
Go öffnet Türen für KI-Integration
Mit der Portierung des TypeScript-Compilers von TypeScript auf Go adressiert das TypeScript-Entwicklungsteam einige Probleme, die gerade bei einer großen Codebasis in einem Projekt entstehen können. Die ursprüngliche Wahl von TypeScript bot eine Reihe von Vorteilen bei der Verarbeitung von TypeScript-Code, was jedoch zu Nachteilen bei der Skalierbarkeit und Performance führte. Die Entscheidung für Go wurde aufgrund der besonderen Eignung der Programmiersprache hinsichtlich komplexer Datenstrukturen, guter Speicherverwaltung und Parallelisierbarkeit getroffen.
Mit der Portierung und der Arbeit an einer verbesserten Schnittstelle zu anderen Prozessen eröffnen sich mit zukünftigen TypeScript-Versionen auch neue Möglichkeiten der KI-Integration, da KI-gestützte Werkzeuge über diese API besser mit den Typinformationen des Quellcodes interagieren können. Anders Hejlsberg skizziert im Interview [8] beispielsweise die Möglichkeit beim Refactoring, dass ein Large Language Model (LLM) während des Refactoring-Prozesses gleich einen passenden Funktionsnamen vorschlagen könnte.
Zwar wird bis zum finalen Release der Go-Portierung von TypeScript noch etwas Zeit vergehen, doch der Code im typescript-go-Repository soll kontinuierlich weiterentwickelt und um weitere Features ergänzt werden und sich so der Fertigstellung immer weiter annähern.
(mai [9])
URL dieses Artikels:
https://www.heise.de/-10355589
Links in diesem Artikel:
[1] https://www.heise.de/news/TypeScript-5-8-verbessert-die-Interoperabilitaet-zwischen-CommonJS-und-ECMAScript-10301548.html
[2] https://enterjs.de/index.php?wt_mc=intern.academy.dpunkt.konf_dpunkt_vo_enterJS.empfehlung-ho.link.link
[3] https://enterjs.de/veranstaltung-78876-0-typescript--eine-anleitung-zum-ungluecklichsein.html?wt_mc=intern.academy.dpunkt.konf_dpunkt_vo_enterJS.empfehlung-ho.link.link
[4] https://enterjs.de/veranstaltung-82655-0-typescript-funktionen-dekorieren-nicht-nur-zu-weihnachten.html?wt_mc=intern.academy.dpunkt.konf_dpunkt_vo_enterJS.empfehlung-ho.link.link
[5] https://enterjs.de/veranstaltung-42559-0-hands-on-workshop-eigene-ki-agenten-mit-langchain.js-%26-langgraph.js.html?wt_mc=intern.academy.dpunkt.konf_dpunkt_vo_enterJS.empfehlung-ho.link.link
[6] https://enterjs.de/tickets.php?wt_mc=intern.academy.dpunkt.konf_dpunkt_vo_enterJS.empfehlung-ho.link.link
[7] https://github.com/microsoft/typescript-go
[8] https://www.youtube.com/watch?v=NrEW7F2WCNA
[9] mailto:mai@heise.de
Copyright © 2025 Heise Medien