- scriptc kompiliert normales TypeScript zu kleinen nativen Binaries, die ohne Node, V8 oder JavaScript-Engine laufen, und behält dabei die Typprüfung des echten TypeScript-Compilers sowie Kompatibilität zum Node-Verhalten bei
- Je nach Codestruktur wird entschieden, ob statische Kompilierung möglich ist; standardmäßig wird nativer Code erzeugt, und nur bei Wahl von
--dynamicführt quickjs-ng JavaScript aus npm-Paketen und Code mitany-Typ aus - Unterstützt Klassen, Generics,
async/await, Exceptions, reguläre Ausdrücke sowie Node-Server-APIs,fetchund npm-Abhängigkeiten; nicht unterstützte Syntax wird mit Fehlercode, Code-Frame und Korrekturhinweis abgelehnt - Über 800 Programme werden sowohl mit Node als auch als native Binaries ausgeführt und Ausgabe sowie Exit-Code verglichen; AddressSanitizer und Referenzzählungs-Audits prüfen auf Speicherfehler
- Messungen auf Apple-M-Serien zeigen eine Startzeit von etwa 2,4 ms, statische Binaries von 170–200 KB und typischerweise 1–4 MB RSS; mit dynamischem Modus und eingebetteten Abhängigkeiten liegt das Binary bei etwa 3 MB
Statisches Kompilierungsmodell
- Es wird bestehendes TypeScript ohne eigenen Dialekt oder Annotationen verwendet; die Prüfstrenge aus
tsconfig.jsonund die echtees2025-Bibliothek von TypeScript werden angewendet- Wenn im Projekt
@types/nodevorhanden ist, wird es in die Typprüfung einbezogen - Erreichbarer Code ohne Lowering liefert präzise Diagnosen und bricht die Kompilierung ab
- Wenn im Projekt
scriptc coveragezeigt die Anzahl der analysierten Statements, den statisch kompilierbaren Anteil, Blocker und Fehlercodes- Im Beispiel werden 99 %, also 4.451 von 4.481 Statements, statisch kompiliert
- Die Ausführungsweise ist explizit in drei Stufen unterteilt
- Statische Kompilierung: der Standardmodus, der ohne JavaScript-Engine in nativen Code übersetzt
- Dynamische Ausführung: Wird
--dynamicangegeben, wird quickjs-ng mit etwa 620 KB eingebunden, um JavaScript aus npm-Paketen und Code mitany-Typ auszuführen- Alle Werte, die in statischen Code übergehen, werden zur Laufzeit geprüft
- Stimmen deklarierter Typ und Wert nicht überein, wird ein abfangbarer
TypeErrorgeworfen, ohne Speicher zu beschädigen
- Ablehnung: Nicht verarbeitbarer Code liefert Fehlercode, Code-Frame und meist Korrekturhinweise; er wird nicht stillschweigend falsch kompiliert
Unterstütztes TypeScript und Standardbibliothek
- Bei den Sprachfeatures werden Klassen mit einfacher Vererbung und dynamischem Dispatch, Closures, Monomorphisierung von Generics, diskriminierte Unions, Destructuring, Spread, Template-Literale, Getter/Setter und Iteratoren unterstützt
- Dynamischer Dispatch wird devirtualisiert, wenn die Sicherheit nachgewiesen ist
- Diskriminierte Unions werden über Tag-Werte mit TypeScripts Narrowing verarbeitet
async/awaitwird mit Stackpool-Fibern und JavaScript-konformer Scheduling-Logik implementiert- Exceptions und
finallysowie optionale, Standard- und Rest-Parameter werden unterstützt
- Reguläre Ausdrücke verwenden denselben ECMAScript-kompatiblen Bytecode-Interpreter, den QuickJS nutzt, und werden nur in Binaries gelinkt, die reguläre Ausdrücke verwenden
- Die Standardbibliothek enthält Strings mit UTF-16-Semantik sowie Arrays, Map und Set mit denselben Reihenfolge- und Identitätsregeln wie JavaScript
- Typkonvertierungen von
JSONdurchlaufen Laufzeitprüfungen - Auch
Math, Typed Arrays,Bufferund eineError-Hierarchie mit typisiertemcatchwerden bereitgestellt
- Typkonvertierungen von
Node- und Web-APIs
- Bei den Node-APIs werden
fs,path,process,child_process,os,crypto,url/URL,zlib, Timer und Signal-Handler unterstütztfsbietet synchrone und Promise-APIschild_processunterstützt Pipe-Streams- Die Event-Loop hat keine externen Abhängigkeiten
- Der Server-Stack umfasst
net,http,https,tls,dgram,dns,fs.watchundreadline; echte Proxy-Server lassen sich kompilieren- Für TLS wird das enthaltene mbedTLS verwendet
- Teile der WHATWG-Web-APIs wie
fetch, Streams,HeadersundAbortSignalsind auf demselben nativen Netzwerk- und TLS-Stack implementiert- Unterstützt werden Redirects, gzip,
AbortSignal.timeoutund Fehlerursachen im Node-Stil - Es werden weder libcurl noch System-HTTP-Abhängigkeiten verwendet
- Unterstützt werden Redirects, gzip,
npm-Abhängigkeiten und dynamische Ausführung
- Im Modus
--dynamicwird Nodes Modulauflösung verwendet, und die Typprüfung basiert auf den vom Paket bereitgestellten.d.ts-Dateien - JavaScript aus npm-Paketen wird beim Build in das Binary eingebettet, sodass zur Laufzeit kein
node_modulesgelesen wird scriptc coverage --dynamiczeigt, ob jedes Statement im statischen oder dynamischen Bereich ausgeführt wird, sowie verbleibende Blocker- Die JavaScript-Engine wird nur eingebunden, wenn der dynamische Modus explizit gewählt wird; dadurch wächst die Binary-Größe nicht unbemerkt
Korrektheit und Speichersicherheit
- Differenztests führen mehr als 800 Programme jeweils mit Node und als natives Binary aus und vergleichen stdout, stderr und Exit-Code bytegenau
- Zahlenausgaben folgen der kürzesten Roundtrip-Darstellung und werden durch Fuzzing mit einer Million
double-Werten gegen Node validiert - Server werden getestet, indem echte Client-Treiber mit beiden Implementierungen verbunden werden
- Zahlenausgaben folgen der kürzesten Roundtrip-Darstellung und werden durch Fuzzing mit einer Million
- Die gesamte Testsuite wird erneut unter AddressSanitizer und Referenzzählungs-Audit ausgeführt; bei Leaks oder Use-after-free schlägt der Build fehl
- Absichtlich von Node abweichende Verhaltensweisen gibt es einige Dutzend, hauptsächlich bei Timing-Interna und Eigenschaften von Error-Objekten
- Jede Abweichung ist dokumentiert und nummeriert; versteckte Unterschiede werden nicht zugelassen
Leistungsmerkmale
- Auf Apple-M-Serien wird anhand derselben Aufgaben und bytegenau gleicher Ausgabe wie bei Node, Go, Rust und Zig gemessen
- Die Startzeit liegt bei etwa 2,4 ms, kürzer als Node mit etwa 47 ms, ähnlich wie Zig und vor Go sowie Rust
- Die Größe statischer Binaries beträgt 170–200 KB; mit
--dynamicund eingebetteten Abhängigkeiten etwa 3 MB- Als Vergleich werden Go-Binaries mit etwa 2 MB und Node SEA mit 60–100 MB angegeben
- Der typische Speicherverbrauch liegt bei 1–4 MB RSS, während Node 67–116 MB benötigt
- Die Runtime konkurriert in den meisten Aufgaben mit Systemsprachen und behält dabei die auf JavaScript abgestimmte
f64-Semantik bei- Integer-Inferenz und Ownership-Analyse stehen auf der Roadmap
Explizite Auswege
comptime(() => ...)führt TypeScript zur Build-Zeit in einer isolierten VM im Compiler aus und legt das Ergebnis als Literal im Binary ab--ffiverbindet TypeScript-Deklarationen, die nur Signaturen enthalten, direkt mit C-ABI-Aufrufen und linkt die im Manifest deklarierten Archive, Objekte und Systembibliotheken- Die Grenze ist explizit und enthält Längeninformationen
- Details finden sich im Native FFI guide
- Geprüfte Type Assertions wie
JSON.parse(...) as Configfügen Laufzeit-Validierungscode ein- Bei fehlgeschlagener Validierung wird eine Exception mit fehlerhaftem Pfad sowie erwartetem und tatsächlichem Typ geworfen, etwa
expected number at $.port, got string
- Bei fehlgeschlagener Validierung wird eine Exception mit fehlerhaftem Pfad sowie erwartetem und tatsächlichem Typ geworfen, etwa
Compiler-Struktur
- Der Ablauf ist
TypeScript → tsc-Parsing und Typprüfung → Lowering → typed IR → C → clang → native ausführbare Datei packages/compilerenthält ein auf der tsc-API basierendes Frontend, IR-Validierung und -Serialisierung sowie LLVM- und C-Backends- Nur IR wird als Schnittstelle zwischen Frontend und Backend verwendet
- LLVM ist der Standard-Codegenerator; für Programme außerhalb des unterstützten Bereichs wird ein transparenter Fallback-Pfad genutzt
- C bleibt als dauerhaftes Referenz-Backend erhalten und erzeugt mit
--backend clesbare Ergebnisse mit Quellzeileninformationen
packages/runtimeimplementiert Werte mit Referenzzählung und einen Cycle Collector, Stackpool-Fiber, einekqueue-Event-Loop, den Server-Stack und JavaScript-kompatible Zahlenausgabe- Per Feature-Linking wird nur tatsächlich genutzte Funktionalität in das Binary aufgenommen
packages/clistellt die Befehlescriptc build,scriptc runundscriptc coveragebereit
Installation und Entwicklung
- Installation per
npm install -g scriptc; clang wird benötigt - Die Hauptplattform ist macOS arm64, Linux- und Windows-Binaries werden cross-kompiliert
- Jede Plattform wird über einen eigenen Differenztest-Pfad validiert
- Die Entwicklung beginnt mit
pnpm install && pnpm buildpnpm testführt die Differenztestsuite und Diagnose-Snapshots ausSCRIPTC_SAN=1 pnpm testführt dieselben Tests unter ASan und Referenzzählungs-Audit auspnpm scriptc build x.ts --emit-irbewahrt das erzeugte C undx.ir.jsonauf
- Alle Features werden zusammen mit Differenztests hinzugefügt; für einen Merge müssen sowohl normale Tests als auch Speichersicherheitstests bestehen
1 Kommentare
Hacker-News-Kommentare
Vercel scheint etwa einmal im Monat ein aufmerksamkeitsstarkes Projekt zu veröffentlichen, um Zuverlässigkeit und Präsenz zu wahren. Es wirkt nicht so, als würde irgendein ernsthaftes Unternehmen oder Projekt scriptc nutzen.
Respekt an die Mitwirkenden, aber der Code wirkt stark so, als sei er mit Claude erzeugt worden, und dass Claude nicht als Mitwirkender aufgeführt ist, macht es noch verdächtiger.
Projekt / zugehöriger HN-Beitrag
Porffor verfolgt dasselbe Ziel schon seit einiger Zeit. Der Entwickler CanadaHonk ist extrem stark, aber das Projekt besteht immer noch nur etwa 68 % von Test262.
Wenn ich den Projektumfang nicht missverstanden habe, ist die Art, wie Vercel so schnell so weit gekommen sein will, ziemlich verdächtig.
Ein typisches Vercel-Projekt. Seit 5 Tagen öffentlich, komplett Vibe Coding, aus keinem ersichtlichen Grund 1.500 Sterne bekommen, löst aber niemandes Problem und wird vermutlich spätestens in ein paar Monaten nicht mehr gepflegt.
Statt nur zu kritisieren, habe ich es direkt auf mehrere lokale Projekte angewendet, aber bei allen gab es Hunderte Fehler bei der Analyse des Codeumfangs, sodass es praktisch unbrauchbar war.
Wenn man alles ohne externe Bibliotheken von Grund auf schreibt, lässt es sich vielleicht zu einem Binärprogramm kompilieren, aber dann gibt es keinen Grund, nicht gleich Sprachen wie Rust, Go, Zig, D, C, V, Ada, C++, Nim, Swift, Kotlin Native oder Haskell zu verwenden, die von Anfang an für echte Kompilierung entworfen wurden.
Der Vorteil von TypeScript ist nicht nur die Ausdrucksstärke, sondern auch die Kompatibilität mit dem riesigen npm-Ökosystem. Die meisten Pakete definieren mit Typdeklarationen nur die Schnittstelle und liefern den eigentlichen Code als JavaScript aus, daher braucht man in der Praxis eine JavaScript-Engine, um Pakete zu verwenden.
Wenn man ohnehin komplett neu anfängt und gar keine npm-Pakete verwenden will, ist AssemblyScript die bessere Wahl. Node rät ausdrücklich davon ab, Pakete in TypeScript zu veröffentlichen, weil TypeScript selbst zwischen Minor-Versionen nicht abwärtskompatibel ist und Compiler-Einstellungen zwischen Paketen ebenfalls nicht portabel sind.
Mit einem solchen Projekt kann man auf Diensten wie HN immer wieder auf der Startseite landen. Es ist eine Wachstumsstrategie: Tokens investieren, ein plausibel wirkendes Projekt bauen, das niemand will, durch die Veröffentlichung Reichweite gewinnen und das Ganze wiederholen.
In 12 Monaten könnten 90 % der Open-Source-Projekte Vibe-Coding-Ergebnisse sein, die nur interessant aussehen und keine echten Nutzer haben. Inzwischen kann man sogar vollständige Compiler leicht erzeugen, aber entscheidend sind langfristige Wartung und Community, und nur mit einem aufmerksamkeitsstarken Titel bleiben Nutzer nicht.
Wenn Vercel es ernst meint, sollte es tatsächliche Kosten und Risiken auf sich nehmen und das intern für die eigene experimentelle Runtime einsetzen.
Das ist ein wirklich hervorragendes Problemfeld. Ich habe ähnliche Arbeit an einem Compiler gemacht, der mit AI Runtime-Code optimiert, angewandt auf Zod: zod-compiler
Er kompiliert Zod-Schemas zur Build-Zeit in einfache Ketten boolescher Operationen und macht sie 2- bis 74-mal schneller, ohne den Code zu ändern; das Plugin ersetzt Zod-Aufrufe durch kompiliertes Parsing. Die meisten Optimierungen wurden von Claude in über 100 Iterationen geschrieben.
Wie bei scriptc muss man die Korrektheit nicht subjektiv beurteilen, weil man die Ergebnisse mit echtem Zod vergleichen kann. Das lässt sich überall anwenden, wo es eine Referenzimplementierung und Benchmarks gibt, etwa bei Compilern, Serialisierungs-Tools, Formatierern oder Query-Planern.
Ich habe mit Claude scriptc- und Node-Benchmarks ausgeführt. Selbst im günstigsten Byte-Array-Ergebnis ist scriptc nach dedizierter Optimierung etwa 7,5-mal langsamer als Node 24.
Dafür startet die ausführbare Datei 12-mal schneller (1,5 ms gegenüber 18,6 ms), nutzt 72-mal weniger Speicher (2,5 MiB gegenüber 181 MiB) und wird zu einer einzelnen 370KB-Datei ohne Runtime-Abhängigkeiten.
Es ist gut, dass anerkannt wird, dass kleine, schnelle native ausführbare Dateien gebraucht werden, aber wenn man sich ansieht, was Java über Jahrzehnte durchlaufen hat, bin ich bei der Praxistauglichkeit skeptisch. GCJ aus den 1990ern war technisch ordentlich, hatte aber keine Unterstützung im Ökosystem.
Später hat GraalVM Native das Problem umfassender angegangen, und wichtige Bibliotheken und Frameworks haben begonnen, an der Kompatibilität zu arbeiten, aber selbst heute ist es sehr schwierig, auch nur einfache bestehende Anwendungen vollständig nativ auszuführen. Versuche wie scriptc sind willkommen, aber bis zur praktischen Nutzbarkeit dürfte es ein langer, steiniger Weg sein.
Ein Grund, warum Excelsior verschwunden ist, dürfte sein, dass GraalVM und OpenJ9 kostenlos verfügbar sind. PTC und Aicas laufen dank einer unbeachteten Zielgruppe aus Embedded- und Echtzeitkunden offenbar weiterhin gut.
Wenn die Entwicklung fortgesetzt wird, hat es das Potenzial, ein Erfolg auf dem Niveau von .NET AOT zu werden. Es ist erst seit ein paar Tagen öffentlich, also probiere ich es derzeit nur leicht aus, aber wenn es nicht aufgegeben wird und weiter reift, könnte es dem Ökosystem erheblich helfen.
Auch AI-generierter Code hat wie menschlich geschriebener Code eine große Qualitätsspanne. Bei wichtiger Software sollte man ihn mit denselben Standards erzeugen und den gesamten Code überprüfen wie bei selbst geschriebenem Code; wenn man das tut, ist es ein hervorragender Ansatz. Projekte mit zu wenig Review sind schwerer zu übernehmen, weil Qualität und Verantwortungsbewusstsein der Entwickler geringer sein könnten.
Online-Diskussionen kippen oft in die Extreme „komplett mit AI erzeugt“ und „AI nie verwenden“, aber in der Realität ist eine Mitte, die durchdachte Urteile beschleunigt, vernünftig. Andere Software meidet man eher, weil niedrige Qualität oder Vernachlässigung zu befürchten sind.