tsgo und tsrs führen vier synthetische TypeScript-Testfälle mit derselben Quellrevision aus; Diagnosen und Exitcodes stimmen in allen vier Fällen überein.
Abbildung: Diagnosevergleich für vier synthetische Einzeldatei-Fixtures. Alle vier Ausgaben stimmen überein; dies ist kein Projekt-Kompatibilitäts- oder Performance-Benchmark.

Cloudflare stellte tsrs mit einer auffälligen Zahl vor: bis zu achtmal schneller und rund 70 Prozent weniger Speicher als TypeScript 7.0. In einem auf einen bestimmten Commit festgelegten tsrs-Bericht fand ich aber 16,87× für mui-docs und 13,55× für vscode in der Standardkonfiguration. Wie passt das zur Aussage mit 8×?

Die großen Performance-Benchmarks habe ich nicht selbst wiederholt. Ich habe stattdessen eine kleinere Frage überprüft: Die zueinander passenden Versionen von tsrs und tsgo (TypeScript in Go) liefen in einer temporären, isolierten GitHub-Umgebung gegen vier TypeScript-Beispiele. Die Diagnosen stimmten in allen vier Fällen überein. Das rechtfertigt weitere Tests, aber nicht den sofortigen Ersatz der Typprüfung in der produktiven CI.

Warum die Zahl der Checker im Benchmark zählt

Cloudflares Ankündigung fasst frühe Ergebnisse als bis zu 8× schneller und rund 70 Prozent weniger Speicher zusammen. Der fest gepinnte historische Benchmarkbericht von tsrs führt dagegen die einzelnen Messwerte auf:

Historischer Upstream-TesttsgotsrsCheckerFaktor laut Bericht
mui-docs, Standard12.66 s0.75 s4 vs 1616.87×
mui-docs, beide --checkers 412.69 s1.07 s4 vs 411.81×
mui-docs, beide --checkers 1612.36 s0.77 s16 vs 1616.07×
vscode, Standard11.10 s0.82 s4 vs 1613.55×
vscode, beide --checkers 166.73 s0.81 s16 vs 168.32×

Die Standardzeilen verwenden nicht dieselbe Anzahl Checker: tsgo vier, tsrs sechzehn. Selbst mit gleicher Anzahl – vier oder sechzehn – liegen die Faktoren in der Tabelle über acht. Das sind zwei unterschiedliche Aussagen: Der Beitrag nennt einen frühen zusammengefassten Messwert; RESULTS.md ist ein separater historischer Snapshot mit festen Versionen und Bedingungen. Acht ist keine obere Grenze für diese Tabelle, und sechzehn ist kein Leistungsversprechen für das eigene Projekt.

Warum die Abweichung so groß ist, kann ich nicht aus den Zahlen allein erklären. Im Bericht steht eine Depot-CI-Maschine depot-ubuntu-24.04-16 mit 16 vCPUs, 63 GB RAM und Linux x86_64. Laufzeiten und maximaler Speicher wurden in wechselnder Reihenfolge gemessen und als Median ausgewertet. tsgo verwendet typescript@7.0.2, tsrs basiert auf einem neueren TypeScript-7.1-dev-Commit und wurde als PGO-optimierter Release-Build erstellt. Gleiche Checker-Anzahlen bedeuten deshalb noch keine identischen Ausgangsbedingungen.

Auch die Speichermessungen hängen von Projekt und Einstellung ab. Bei mui-docs gibt der Bericht unter den Standards einen Faktor von 4,61× weniger Speicher an, bei sechzehn Checkern auf beiden Seiten 8,23× weniger. Die „rund 70 Prozent“ aus der Ankündigung sind ein Überblick über frühe Ergebnisse, keine Garantie für alle Projekte. Ich habe dazu den Upstream-Bericht geprüft, keine eigenen großen Benchmarks gerechnet.

Was die vier Diagnose-Tests wirklich abdecken

Verglichen wurden tsrs@0.18.0-ts7.1.0-dev.20260929 und die Go-Implementierung aus typescript@7.1.0-dev.20260930.4. Beide beziehen sich auf denselben TypeScript-Quellcommit b85298b6.

Die Vorbereitung begann mit einem handfesten Fehler: Ich behandelte @typescript/typescript, den Workspace-Namen im TypeScript-Quellrepository, wie ein bei npm veröffentlichtes Paket. npm antwortete mit E404 – der Compiler lief noch gar nicht. Das veröffentlichte Root-Paket heißt typescript. In diesem Nightly heißt die CLI noch tsc, und @typescript/typescript-linux-x64 stellt die native Go-Version für Linux x64 bereit. Erst nach der Korrektur begann die Kompilierung.

Alle vier Fixtures nutzen dieselbe tsconfig.json. Ich setze types: [] explizit, damit globale @types-Pakete aus der Umgebung nicht heimlich hinzukommen; skipLibCheck: false lässt die Prüfung von Deklarationsdateien eingeschaltet. Das ist die verwendete Konfiguration, statt nur von „strict mode“ zu sprechen:

{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "module": "esnext",
    "noEmit": true,
    "skipLibCheck": false,
    "types": []
  },
  "include": ["src/*.ts"]
}

In jedem Fixture-Verzeichnis liegt genau eine src/test.ts mit folgendem Inhalt:

FixtureVollständige src/test.tsErgebnis tsgo / tsrs
valid (Gültige Typen)export const answer: number = 42;0 Diagnosen, Exit 0 / 0
type-error (Falsche Typzuweisung)export const answer: number = "wrong";TS2322 · Exit 2 / 2
module-missing (Fehlendes Modul)import {missing} from "./unavailable.js"; export const answer = missing;TS2307 · Exit 2 / 2
generic-constraint (Generische Einschränkung)type OnlyText<T extends string> = {value:T}; export type Bad = OnlyText<number>;TS2344 · Exit 2 / 2

Beide Seiten wurden mit --noEmit --pretty false --checkers 4 gestartet. Ich verglich Dateien, Zeilen- und Spaltenpositionen, Fehlercodes, Texte, Reihenfolge und Prozess-Exitcodes. Alle vier Fälle stimmten überein. Da jedes Fixture nur eine Quelldatei besitzt, ist die Option --checkers 4 hier keine Messung echter Vierfachparallelität.

Gleiche Fehleranzahl bedeutet nicht gleiche Diagnose

Die Kompatibilitätsangaben zur fixierten Version melden 13.458 übereinstimmende Fehler-Baselines von 13.462 sowie alle 12.779 .types- und .symbols-Baselines. Gleiche Summen allein reichen aber nicht.

Im Upstream-Bericht ist drizzle-orm ein gutes Gegenbeispiel: Mit sechzehn Checkern auf beiden Seiten wurden jeweils 10.846 Fehler gemeldet, deren Positionen jedoch unterschiedlich waren. Der Bericht bezeichnet das als tsrs-Bug. Diesen Fall habe ich nicht selbst reproduziert, sondern in der fixierten Quelle nachgelesen. Für CI und Code-Reviews muss ein Fehler auf die richtige Datei und Zeile zeigen.

Es gibt weitere Versionsgrenzen: --init und --showConfig sind noch nicht unterstützt, --locale übersetzt keine Diagnosen. Die Content-Mapper für .astro, .vue und .svelte funktionieren für CLI/Build, nicht aber für den Language Server. Vorgefertigte Pakete für Windows oder Intel-macOS fehlen ebenfalls. Mein Versuch umfasste nur die CLI in einer isolierten GitHub-Linux-Umgebung; IDE-Plugins, LSP und diese Systeme habe ich nicht getestet. Wer mit astro check oder Live-Diagnosen arbeitet, sollte zuerst die Kompatibilitätstabelle der passenden Version lesen. Bei Problemen nach einem Astro-Upgrade gibt es von mir auch eine Notiz zur TypeScript-7-Kompatibilität.

Was ich im eigenen Projekt vergleichen würde

Ich würde in einem wegwerfbaren Git-Worktree oder separaten Branch anfangen, nicht im täglichen Arbeitsverzeichnis. So lässt sich ein erfolgloser Test einfach verwerfen.

Das nächste Skript verhindert eine leicht zu übersehende Verwechslung: Gleiche Exitcodes bedeuten nicht automatisch gleiche Diagnosetexte. set +e ist Absicht. Die bewusst falschen Beispiele sollen mit Nicht-Null-Status enden; wir müssen beide Programme durchlaufen lassen, Exitcodes sichern und anschließend Ausgaben vergleichen. tsc ist hier die CLI der Go-Implementierung im fixierten TypeScript-Nightly, /tmp/tsgo.out nur der Protokolldateiname.

pnpm add -D --save-exact tsrs@0.18.0-ts7.1.0-dev.20260929 typescript@7.1.0-dev.20260930.4

set +e
pnpm exec tsc -p . --noEmit --pretty false --checkers 4 > /tmp/tsgo.out 2>&1
tsgo_status=$?
pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 > /tmp/tsrs.out 2>&1
tsrs_status=$?
diff -u /tmp/tsgo.out /tmp/tsrs.out
diff_status=$?
set -e
printf 'tsgo_exit=%s tsrs_exit=%s diff_exit=%s\n' "$tsgo_status" "$tsrs_status" "$diff_status"
if [ "$tsgo_status" -ne "$tsrs_status" ] || [ "$diff_status" -ne 0 ]; then
  echo 'DIAGNOSTIC_DIFF_OR_EXIT_MISMATCH: inspect /tmp/tsgo.out and /tmp/tsrs.out'
  exit 1
fi

Wenn der erste Vergleich abweicht, bewahre ich die Originalausgaben auf. Dann lassen sich --checkerAssignment go (Dateiverteilung und Checker-Historie wie bei tsgo) oder --noLazyMembers (ohne die lazy-member-Optimierung von tsrs) separat ausprobieren. Wer sie schon beim ersten Lauf aktiviert, sieht das abweichende Standardverhalten möglicherweise nicht mehr. Weitere Details: Anleitung und Standardwert-Unterschiede.

Das Skript oben misst weder Laufzeit noch Speicher. Eine vollständige Performance-Messreihe habe ich diesmal nicht durchgeführt. Für einen ersten Einzelwert lässt sich GNU time unter Linux verwenden, um Sekunden und Peak RSS zu erfassen. Unter macOS kann man gnu-time installieren und die erste Zeile auf TIME_BIN=gtime ändern.

TIME_BIN=/usr/bin/time

set +e
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsgo-bench.metrics \
  pnpm exec tsc -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsgo-bench.out 2>&1
tsgo_status=$?
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsrs-bench.metrics \
  pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsrs-bench.out 2>&1
tsrs_status=$?
set -e

printf 'tsgo_exit=%s ' "$tsgo_status"; cat /tmp/tsgo-bench.metrics
printf 'tsrs_exit=%s ' "$tsrs_status"; cat /tmp/tsrs-bench.metrics

Aus diesen Kommandos entsteht nur ein Messpaar; Hintergrundlast kann es stark beeinflussen. Für eine belastbarere Aussage würde ich auf demselben Runner mindestens fünf Paare in wechselnder Reihenfolge ausführen, Lockfile, Compiler-Versionen, tsconfig und Checker-Anzahl festhalten und Mediane samt Peak RSS vergleichen. Kaltstart und Warm-Cache-Läufe sollten getrennt dokumentiert werden. Den Betriebssystem-Page-Cache habe ich nicht geleert und würde das deshalb nicht als vollständigen Kaltcache-Test bezeichnen. GNU time zeigt %M gewöhnlich in KiB an – plattformübergreifend müssen die Einheiten zuerst geprüft werden.

Ich würde tsrs zunächst nur als zusätzlichen, nicht blockierenden CI-Job laufen lassen. Das bisherige tsc und die Framework-Prüfung bleiben Pflicht. Erst wenn Diagnoseunterschiede geklärt sind, die Performance-Messungen stabil werden und ein echter PR-/Release-Zyklus samt Rollback erfolgreich nachvollzogen wurde, lohnt die Debatte über einen Wechsel. Vier kleine Beispiele beweisen nur, dass beide Compiler bei diesen Eingaben dieselben Diagnosen liefern.

Versuchsdatum: 22. Oktober 2026