Sprache nach Problemtyp auswählen: Lektionen aus der Umsetzung von TypeScript in Go
Was die Wahl von Go als nativer TypeScript-Kompilierer über das Passen von Werkzeugen zu den jeweiligen Anforderungen, die Bedeutung der Geschwindigkeit von Werkzeugen sowie das schrittweise Portieren großer Codebasen lehrt.
Fast eine Minute auf die Autocomplete-Funktion in einem großen TypeScript-Monorepo warten ist ein bekanntes Problem, und Mobile-Entwickler erleben denselben Ärger, wenn Xcode ein großes Projekt indiziert. Das TypeScript-Team hat genau dieses Problem angegangen, und die Art und Weise, wie es es gelöst hat, ist eine nützliche Fallstudie dafür, technologische Entscheidungen auf Grundlage von Fakten statt aus Modetrends zu treffen. Dieser Artikel blickt über die Benchmark-Meldungen hinaus und zeigt die ingenieurtechnischen Überlegungen, die Sie auf Ihre eigenen Tools und Apps anwenden können – egal ob Sie TypeScript, Swift oder beides einsetzen.
Was Microsoft angekündigt hat
Im März 2025 kündigte Anders Hejlsberg, der sowohl TypeScript als auch C# entwickelt hat, an, dass Microsoft eine native Portierung des TypeScript-Kompilators sowie seiner Sprachwerkzeuge erstellt. Viele nahmen an, das Ziel wäre Rust oder vielleicht C++. Es war Go.
was die Go-Umgestaltung ohne Änderungen am Code bewirkt separat behandelt. Der Rest dieses Artikels konzentriert sich auf die Entscheidung selbst.
Warum Go statt Rust
Das Team suchte nach der Sprache niedrigster Ebene, die trotzdem native Code auf allen Plattformen erzeugen konnte, die TypeScript unterstützen muss, und gleichzeitig eine gute eingebaute Konkurrenzfähigkeit bot. Go entsprach dieser Beschreibung.
Zwei weitere Faktoren waren wichtig. Erstens die Arbeitslast: Die Typüberprüfung eines großen Projekts besteht größtenteils darin, viele Dateien parallel zu überprüfen und anschließend die Ergebnisse zusammenzufassen – Goroutinen entsprechen diesem Muster sehr direkt. Zweitens der vorhandene Code: Der Compiler basiert auf einer riesigen JavaScript-Codebasis, die von einer Garbage Collection sowie einem eher funktionalen Stil abhängt. Go verfügt ebenfalls über eine Garbage Collection und eine ähnliche Struktur, sodass der Code nahezu unverändert übersetzt werden konnte. Ein Umstieg auf Rust hätte einen viel umfangreicheren Neuentwurf hinsichtlich Eigentumsverhältnissen und Lebenszeiten erfordert, was für ein großes Team mit einer enormen Codebasis einen hohen Aufwand bedeutet hätte.
Einfach ausgedrückt wählte das Team nicht die modischste Sprache. Es wählte stattdessen diejenige, die zu seinem Konkurrenzmodell passte und seine Entwickler produktiv hielt. Das ist ein architektonisches Urteil – derselbe Art von Entscheidung, die man trifft, wenn man zwischen UIKit und SwiftUI oder zwischen Combine und herkömmlichem async/await für eine bestimmte Funktion entscheidet.
Dieselbe Idee in Swift
Swift-Entwickler sind bereits früher auf dieses Problem gestoßen. Strukturierte Konkurrenz wurde in Swift genau deshalb hinzugefügt, um aufwändige Aufgaben parallel auszuführen, ohne die Korrektheit zu gefährden. Der folgende Codeausschnitt veranschaulicht das Konzept „mehrere Dateien gleichzeitig überprüfen und anschließend zusammenführen“ mithilfe einer Task-Gruppe: Jede Datei erhält ihre eigene Unteraufgabe, und die übergeordnete Aufgabe sammelt die Ergebnisse ein, sobald die Unteraufgaben abgeschlossen sind. Beachten Sie, dass es sich um Swift handelt, obwohl damit das Muster veranschaulicht wird, das der Go-Compiler mit Goroutinen verwendet.
// Swift concurrency: parallel "type-checking" of files, similar in spirit
// to how Go's goroutines let TypeScript check many files at once
func checkFiles(_ paths: [String]) async -> [Diagnostic] {
await withTaskGroup(of: [Diagnostic].self) { group in
for path in paths {
group.addTask {
await checkSingleFile(path) // runs concurrently
}
}
var results: [Diagnostic] = []
for await diagnostics in group {
results.append(contentsOf: diagnostics)
}
return results
}
}
Mehrere Punkte sind erwähnenswert. withTaskGroup stellt sicher, dass jede Unteraufgabe vor dem Rückgang der Funktion abgeschlossen wird, sodass keine Arbeit außerhalb des Scope bleibt. Die Ergebnisse kommen in der Reihenfolge ihrer Abschluss, nicht in der Eingabereihenfolge – daher müssen Sie sie ggf. nachträglich sortieren, wenn die Reihenfolge der Diagnosen wichtig ist. Zudem ist die Parallelisierung nur sicher, weil jede Überprüfung unabhängig ist; genau diese Eigenschaft macht die Typüberprüfung zu einem guten Fit für Go’s Modell.
Falls Sie Swift 6 verwendet haben, haben Sie bereits diesen Kompromiss eingegangen: Sie zahlen im Voraus für strenge Konkurrenz- und Sicherheitsprüfungen, erhalten aber im Gegenzug schnellere Kompiliervorgänge sowie stabileres Laufzeitverhalten. Microsoft hat mit seinem Compiler einen ähnlichen Ansatz gewählt.
Lektionen, die auch auf Ihre eigenen Projekte übertragen werden können
- Die Geschwindigkeit der Entwicklungstools ist eine Produktmerkmal. Langsame Kompiliervorgänge und träge Autocomplete-Funktionen verursachen jeden Tag eine weitgehend unsichtbare Belastung für alle Entwickler in einem Team. Arbeiten an der Leistung von Build-Pipelines, Editoren und Indexierungsmechanismen sind genauso wichtig wie Arbeiten an der Leistung der fertiggestellten Anwendung.
- Lassen Sie das Problem – nicht die Trends – das Tool bestimmen. Egal, ob Sie sich für MVVM, SwiftData oder Combine entscheiden – die richtige Wahl ergibt sich aus der tatsächlichen Datenflussstruktur in Ihrem System und nicht daraus, was gerade in den sozialen Medien beliebt ist.
- Überarbeitungen können schrittweise erfolgen. Das TypeScript-Team portierte den bestehenden Compiler so getreu wie möglich, anstatt ihn neu zu entwerfen, sodass die neue Version dieselben Ergebnisse liefert wie die alte. Das ist ein starker Argument dagegen, eine ganze Anwendungsarchitektur in einem einzigen Sprint neu aufzubauen.
Wann sollten Sie diesem Beispiel nicht folgen? Wenn Ihr aktueller Stack nicht das Engpassproblem ist, bringt ein Umstieg nur Risiken mit sich. Messen Sie zunächst und stellen Sie sicher, dass der langsame Teil tatsächlich in der Schicht liegt, die Sie ersetzen möchten.
Erkennen der Warnsignale
Die Symptome, die zu diesem Projekt führten, treten in jeder großen Codebasis auf: verlangsamte Typinferenz, verzögerte Editor-Feedbacks sowie stetig wachsende Build-Graphen. Die Lösung ist nicht immer ein neues Framework. Manchmal bedeutet es, genauer hinzuschauen, was Ihre Tools im Hintergrund tatsächlich tun, und den Teil zu ändern, der zu viel Arbeit leistet.
Kernpunkte
- Go wurde gewählt, weil seine native Kompilierung, der Garbage Collector sowie die auf Goroutinen basierende Konkurrenzfähigkeit sowohl zur Typüberprüfung als auch zur Struktur des bestehenden Codes passten.
Verwandte Artikel
- Modern JavaScript's Real Complexity Comes From Tooling, Not the Language — In diesem Artikel wird erklärt, wie Kernfunktionen von JavaScript wie async/await und optional chaining den Code vereinfachen, während übermäßige Werkzeuge und Abhängigkeiten unnötige Komplexität verursachen.
- Inside TypeScript 7's Go Rewrite: Speed Gains Without Code Changes — Erfahren Sie, wie der auf Go basierende Compiler von TypeScript 7 eine 8-12-fach schnellere Kompilierung ermöglicht, warum der Architekturwechsel funktioniert und wie bestehende Projekte sicher aktualisiert werden können.
- From One Design to Many Screens: A Saner Splash Screen Asset Workflow — Warum Splash-Screens mehr Zeit in Anspruch nehmen, als ihr Design verdient, wie man einen solchen Bildschirm erstellt, der bei jedem Seitenverhältnis funktioniert, und was ein fokussierter browserbasierter Generator leisten sollte.
- Entscheidungen bei der TypeScript 7-Aktualisierung: Parallel Checkers, Watch Mode und API-Lücken — Welche Änderungen treten auf, wenn man von TypeScript 6 zur für Go entwickelten TypeScript 7 wechselt, wie die neuen Parallelisierungsflaggen funktionieren und welche Toolchains vor einer Aktualisierung warten sollten.
- Von der Kotlin-Programmierung zur Steuerung von Agenten: Die neue Rolle des Mobile Engineers — Wie die Programmierung von Agenten die Arbeit eines Android-Engineers auf Spezifikationen, Kontexte, Architekturbeschränkungen und Überprüfungen ausrichtet und welche Grundlagen wichtiger sind als je zuvor.