- Node.js PR #53725 wurde gemergt und unterstützt nun experimentell die Ausführung von TypeScript-Dateien über das Flag
--experimental-strip-types
- Die Funktion entfernt Typsyntax aus TypeScript-Quellen und wandelt sie in JavaScript-Quellen um; während der Umwandlung wird keine Typprüfung durchgeführt
- Die erste Implementierung transformiert keine TypeScript-spezifischen Funktionen wie
Enum oder namespaces, sie ist also auf Code beschränkt, der allein durch das Entfernen von Typen zu JavaScript wird
- Für die Implementierung wurde
@swc/wasm-typescript gewählt; der Grund ist die Einfachheit, weil weder Rust noch Go zur Node.js-Toolchain hinzugefügt werden müssen
- In der Diskussion wurden die obligatorischen Dateiendungen bei ESM, die TypeScript-Praxis, dass
.js-Imports auf tatsächliche .ts-Dateien verweisen, sowie die Kosten von Sourcemaps und Positionen in Error Stacks als wichtigste Einschränkungen behandelt
Funktionsweise von --experimental-strip-types
- Wenn
--experimental-strip-types gesetzt ist, kann Node.js TypeScript-Dateien ausführen
- Node.js transpiliert den TypeScript-Quellcode in JavaScript-Quellcode
- Beim Transpilieren wird keine Typprüfung durchgeführt; Typinformationen werden verworfen
- Ein Beispiel:
const foo: string = "foo";
const foo = "foo";
Warum nur Type-Stripping gewählt wurde und anfängliche Einschränkungen
- Kern dieses PR ist das Entfernen von Typsyntax aus der TypeScript-Syntax
- Andere Runtimes wandeln auch TypeScript-spezifische Funktionen, die es in JavaScript nicht gibt, wie
enum, in JavaScript um
- Die erste Implementierung dieses PR führt solche Transformationen nicht durch
Enum kann nicht verwendet werden
namespaces können nicht verwendet werden
- Auch andere TypeScript-spezifische Funktionen werden nicht transformiert
- Zu Implementierungsdetails und Einschränkungen wird auf
typescript.md in den PR-Änderungen verwiesen
@swc/wasm-typescript als Implementierungswerkzeug gewählt
@swc/wasm-typescript ist ein kleines Paket und besteht aus einer wasm-Datei sowie einer JavaScript-Datei, die sie bindet
- Andere Tools hätten erfordert, Rust oder Go zur Node.js-Toolchain hinzuzufügen
- SWC wird auch in Deno für denselben Zweck verwendet, und der PR-Autor sieht es als bewährte Option
- Künftig könnte diese Funktion in der nativen Schicht implementiert werden
- Dieser PR verweist auf nodejs/loaders#217
Diskussion zu Import-Auflösung und Dateiendungen
- Imports ohne Dateiendung wie
import foo from "./foo" werden nicht unterstützt
- Node.js ESM hat die Regel mandatory file extensions, daher wird die fehlende Unterstützung für Imports ohne Dateiendung als beabsichtigtes Verhalten behandelt
- Es wurde angemerkt, dass in TypeScript-Modul-Codebasen häufig die Endung
.js in Import-Anweisungen verwendet wird, auch wenn die tatsächliche Datei .ts ist
- Beispiel:
import foo from './foo.js'
- Fälle, in denen es im tatsächlichen Dateisystem kein
./foo.js, sondern nur ./foo.ts gibt
- Der PR-Autor sieht dieses Problem als etwas, das in künftigen Iterationen behandelt werden kann; das Erraten von Endungen würde zusätzliche Dateisystemprüfungen und Heuristiken erfordern
- Wenn
./foo.js nicht existiert, ./foo.ts prüfen
- Wenn
./foo.ts nicht existiert, ./foo.js prüfen
- Diese Diskussion wird in nodejs/loaders#208 verfolgt
Quellpositionen, Sourcemaps und Error Stacks
- Im Review wurde die Meinung geäußert, dass kurzfristig korrekte Quellpositionen in Error Stacks nötig sind
- Eine mögliche Methode ist, beim Entfernen der Typen die Quellpositionen nicht zu verändern und so Sourcemap-Erzeugung und Mapping zu vermeiden
- Es wurde erwähnt, dass Sourcemaps Kosten bei Speicherverbrauch und Performance verursachen und nicht trivial sind
- Der PR-Autor meint, dass SWC bereits Sourcemaps unterstützt, sodass ein Flag zum Aktivieren hinzugefügt werden könnte
- Es könnte auch bei SWC angefragt werden, entfernte Syntax durch Leerzeichen zu ersetzen, damit die ursprünglichen Error Stacks unverändert bleiben
Verbleibende Designfragen vor der Stabilisierung
- TypeScript-Unterstützung teilt sich in zwei Achsen auf
- Entfernen von Typsyntax
- Anpassung der Auflösungsregeln, damit
.ts-Dateien akzeptiert werden
- Es wurde die Sorge geäußert, dass Änderungen an den Auflösungsregeln mit bestehenden TypeScript-Loadern kollidieren könnten
- Der PR-Autor antwortete, dass dieser PR keine Overrides der Auflösungsregeln enthält und Dateiendungen erforderlich sind
- Es wurde die Ansicht geäußert, dass
import './file.ts' über allowImportingTsExtensions unterstützt werden kann und dieser Ansatz auch zu Deno passt
- Wenn dieselbe Quelldatei direkt ausgeführt und außerdem mit einem Build-Tool JavaScript-Ausgabe erzeugt werden soll, kann die Verantwortung für das Ersetzen von
file.ts durch file.js beim Build-Tool liegen
Merge und Folgearbeiten
- Der PR wurde am 24. Juli 2024 in
nodejs:main gemergt
- Der Umfang der Änderung beträgt 8 Commits, 89 Dateien, 2.190 hinzugefügte und 25 gelöschte Zeilen
- Es wurden Labels wie
semver-minor, experimental, module, esm und notable-change vergeben
- Es gab eine Diskussion darüber, ob diese Funktion auf der TypeScript-Infoseite der Website erwähnt werden soll; ein zugehöriger Update-PR wurde ebenfalls verlinkt
- Als Folge-Issue wurde auch diskutiert, in die Release-Metadaten die TypeScript-Version aufzunehmen, die das Node-Binary für das Entfernen von Typen verwendet
1 Kommentare
Hacker-News-Kommentare
TypeScript-Types entfernen ist ohne TypeScript-Syntax nicht möglich. Type Removal ist keine Arbeit auf Token-Ebene, und die TypeScript-Syntax ändert sich ständig
Zum Beispiel wurde
foo < bar & baz > ( x )in TypeScript 1.5 als(foo (x))geparst, weilbar&bazkein gültiger Type-Ausdruck war. Nachdem der Intersections-Operator für Types hinzugekommen war, wurde es alsfoo<(bar & baz)>(x)geparst und auffoo(x)heruntergestripptWenn man aktuelle TypeScript-Features weiter nutzen will, muss man entweder nach JS kompilieren oder die Node-Version ständig aktuell halten, und für Teams, die lieber bei Node LTS bleiben, könnte das ein schwer akzeptabler Kompromiss sein
Im Core würde eine stabile Version liegen, aber wenn TypeScript neue Features oder Breaking Changes bringt oder Nutzer neue experimentelle Features verwenden wollen, könnten sie über NPM separat aktualisieren
So wären Nutzer nicht festgelegt, und gleichzeitig könnte während der dreijährigen Lebensdauer eines Node.js-Releases eine bestimmte TypeScript-Version unterstützt werden: https://github.com/nodejs/loaders/issues/217
Die letzte große Syntaxänderung war wahrscheinlich in 2.8 mit den Conditional Types, als an Type-Positionen eine ternäre
if-ähnliche Syntax hinzukam. Wenn man auch Type-Checking machen will, hat sich das Type-Modell seit 2.x stark verändert, aber die Syntax war insgesamt recht stabil, und die Innovationen von TypeScript lagen überwiegend beim Type-Modell und der Type-InferenceEs ist noch ein frühes Stage 1, aber es gibt auch einen Versuch, große Teile der TypeScript-Type-Syntax für den Zweck des Type Removal statt des Type-Checking zu standardisieren: https://github.com/tc39/proposal-type-annotations
Teams, die sich kaum für neue V8- oder Node-Features interessieren und bei LTS bleiben, haben wahrscheinlich auch weniger Interesse an den neuesten TypeScript-Features
Wenn Node.js TypeScript-Dateien direkt ausführen kann, könnte der TypeScript-Compiler statt für Type Removal und JavaScript-Transformation nur noch als Type-Checker dienen. Das wäre ähnlich wie in Python, wo der Type-Checker die Types prüft und stehen lässt, während der Interpreter Type Hints ignoriert
Interessanterweise sind in Python mehrere populäre Type-Checker entstanden, die dieselbe Type-Hint-Syntax nutzen, aber unterschiedliche Semantik haben. In JavaScript dagegen scheint TypeScript faktisch der einzige populäre Type-Checker geworden zu sein
Aus Python hört man auch von Fällen, in denen Types in den Source geschrieben werden, ohne sie zu prüfen, und eher wie eine bequemere Kommentar-Syntax verwendet werden. Wenn Node.js das Ignorieren von Types unterstützt, wäre so etwas auch in JavaScript möglich
Für mich persönlich war die größte Unannehmlichkeit bei JSDoc die Import-Syntax, aber das hat sich seit TypeScript 5.5 geändert und ist jetzt kein Problem mehr
Einige Bundler erledigen das allerdings schon mit inoffiziellen Compilern wie SWC, und Node testet SWC bereits für diese Funktion
Type Hints sind nicht nur einfache Kommentare, sondern auch Hinweise für IDEs, damit sie etwa bei Namen bessere Autovervollständigung anbieten können
Meiner Meinung nach sollte das Teil der Sprachspezifikation werden
Ich frage mich, wie das NPM-Ökosystem reagieren wird, wenn diese Funktion nicht mehr hinter einem Flag versteckt ist, sondern zum Standard wird. Ob Mitwirkende beim Veröffentlichen von NPM-Modulen weiterhin CJS- und ESM-Versionen bauen werden oder einfach nur
package.jsonmitengine: nodejs >= 25versehen und den Build-Schritt weglassen, ist unklar.Persönlich fände ich es sogar besser, wenn NPM-Module mit TS-Quellcode aufhören würden,
dist/.cjsauszuliefern, weil dann klarer wäre, wo man den Debugger undconsole.logeinfügen sollte. Auch aus Sicht von NPM-Mitwirkenden dürfte die Versuchung groß sein, den Build-Schritt wegzulassen.Wenn aber schon eine einzige Abhängigkeit TS-Dateien ausliefert, kann sich die Annahme „alle bekommen TS-Dateien“ schnell verbreiten. Sobald Node.js das aus dem optionalen experimentellen Flag herausnimmt, dürfte die gesamte Community implizit erwarten, dass Nutzer TS-Dateien akzeptieren.
Dann würden vermutlich innerhalb weniger Monate auch Firefox und Safari unter Druck geraten, das zu akzeptieren, und alle JS-Compiler hätten keine andere Wahl, als TS-Typannotationen zu verwerfen.
Für mich wäre auch das in Ordnung. Selbst auf der Seite der TS-Nutzer steckt man aktuell einen Transformationsschritt in NPM-Module, der TS-Code in JS und
.d.tsumwandelt, um irgendwelche hypothetischen JS-Nutzer zu unterstützen. Wenn Node aber.ts-Dateien akzeptiert, könnte man diesen Transformationsschritt unbemerkt entfernen. Deshalb frage ich mich, welche Schutzmechanismen es gibt, damit NPM-Publisher nicht die Auslieferung vonjs/d.ts-Dateien einstellen, ohne zu merken, dass dadurch etwas kaputtgeht.Dort kann man TypeScript-Code ohne Build-Schritt hochladen, und andere Entwickler sehen bei der Installation nicht transformiertes JavaScript, sondern den ursprünglichen TypeScript-Quellcode.
.ts-Dateien innode_moduleswird nicht unterstützt. Das ist eine der wichtigsten Einschränkungen, um das Ökosystem nicht zu zerstören..cjsund.mjsumgewandelt; vermutlich werden sie dann alle drei Versionen ausliefern.Bei neuen Greenfield-Projekten könnte man dagegen nur TypeScript ausliefern und auf die Umwandlung verzichten.
Korrektur: Laut dem Schwesterkommentar von satanacchio, der offenbar der PR-Autor und Mitglied des Technical Steering Committee von Node.js ist, gilt die TS-Unterstützung für Top-Level-Einträge und nicht für Bibliotheken in
node_modules..ts-Quellcode auf npm veröffentlichen, aber das TypeScript-Team war wegentsconfig-Problemen und Performance-Bedenken sehr dagegen. Trotzdem hoffe ich weiter darauf.Vielleicht wird Node eines Tages diese Typen in JS per Runtime-Reflection auslesen können.
Das wäre ein riesiger Vorteil. In Python kann man Typen reflektieren, deshalb können großartige Werkzeuge wie pydantic aus Typen Prüfungen erzeugen.
Mit nur einer einzigen standardisierten Schreibweise bekommt man Typprüfung, Runtime-Datenvalidierung, API-Erzeugung und API-Dokumentation.
In JS müssen Werkzeuge wie Zod derzeit im Grunde neu erfinden, was TypeScript bereits tut, etwa mit
const mySchema = z.string();.z.string()in Zod bietet nicht nur Type-Safety wie TypeScript. TypeScript liefert Typprüfungen zur Compile-Zeit, während Zod Runtime-Validierung und Parsing hinzufügt.z.string()machtmySchemazu einem funktionalen Schema, das parsen und validieren kann.mySchema.parse("some data")funktioniert,mySchema.parse(321)wirft eine Exception.Ich habe es dort eingesetzt, wo Runtime-Validierung und Prüfungen innerhalb des Prozesses nötig waren, und es hat ziemlich gut funktioniert; man kann auch Typen ableiten, etwa mit
const A = z.string(); type A = z.infer; // string.Anders gesagt: Wenn man in Zod zuerst den Typ definiert und daraus den Typ ableitet, bekommt man sowohl Compile-Zeit- als auch Runtime-Typprüfungen. Für kleine Codebasen, die sich schnell ändern, mag das übertrieben sein, aber in Situationen mit notwendiger In-Process-Validierung ist es ziemlich nützlich und der Zusatzaufwand hält sich in Grenzen.
Allerdings erlaubt TypeScript Plugins. Sie werden zwar nicht oft genutzt, können der Sprache aber zusätzliche Funktionen hinzufügen, die in das resultierende JavaScript transformiert werden.
Ein Plugin im Bereich Runtime-Typen ist Typia; damit lassen sich TypeScript-Typsignaturen zur Laufzeit in Guards wie
assert(myValue)verwenden. Es fängt diesen Funktionsaufruf ab und erzeugt im transformierten JavaScript exhaustiveif-Anweisungen, die die Beschaffenheit des übergebenen Werts prüfen.Ich glaube nicht, dass das in den nächsten 4 bis 6 Jahren Teil der Sprache wird, aber Bibliotheken, mit denen man so etwas heute schon tun kann, gibt es bereits.
Eigentlich braucht man ein soundes Typsystem, aber TS ist absichtlich nicht sound. Man braucht ein Typsystem, das keine schlechten Coding-Praktiken wie TS erlaubt und stattdessen ein Design erzwingt, mit dem sich Programme schneller machen lassen. Man braucht ein Hindley-Milner-Typsystem.
Wenn man ein Modul zu typisiertem Code machen will, sollte es reichen,
"use type"hinzuzufügen. Dieser Modus müsste problematische Dinge wie Type Coercion verbieten und leistungsfeindliche Muster wie das Verändern von Objektformen oder Werttypen oder das Erzeugen von Arrays mit beliebig gemischten Werten verhindern.Daten aus untypisierten Modulen würden konvertiert oder, falls das nicht möglich ist, einen Fehler auslösen; dadurch könnte der Compiler typisierten Code mit stärkeren Typgarantien und ohne Risiko eines Bailouts tiefgreifend optimieren.
ValueErrorist oder ob ein Objekt eine Instanz vonSomeClassist? Ich bin Anfänger in TS und dachte, außer Arrays, Objekten, Zahlen und Strings gebe es in JS keine anderen Typen, und Zod oder Type-Guard-Funktionen würden nur allgemeine Objekte im Sinne von „vertrau mir“ zurückgeben.Die Developer Experience von Bun ist in diesem Bereich auf einem ziemlich beispiellosen Niveau, und mittlerweile sind die meisten Anwendungsfälle abgedeckt oder Bun stürzt nicht mehr ab, wenn man Ausführungsskripte mit
bun runschreibtNode hingegen lässt sich nicht einmal so konfigurieren, dass bei Imports keine Erweiterungen erforderlich sind, und man kann
tscauch nicht dazu bringen, der Kompilierungsausgabe automatisch die Erweiterung.jshinzuzufügen, sodass man einen Bundler ergänzen mussNative TypeScript-Unterstützung wird diese kleinen Unannehmlichkeiten wohl deutlich beheben, aber selbst wenn sie stabil wird, ist schwer vorstellbar, dass Benutzererfahrung oder Performance mit Bun mithalten werden
Die Developer Experience und die Tools sind ebenfalls erstklassig. Deno kann auch Typprüfung durchführen und bündelt, soweit ich mich erinnere, TSC
Bun bekommt viel Aufmerksamkeit, aber für ernsthafte Arbeit ist Deno derzeit eindeutig die bessere Wahl. Trotzdem sind Buns Vision und Umsetzung beeindruckend, und das ist gut für Entwickler
bun installbeide Male fehlgeschlagen, sodass es nie wirklich funktioniert hat: https://github.com/search?q=repo%3Aoven-sh%2Fbun+bun+install...Ich habe mir seit Langem eine TypeScript-Runtime gewünscht und mag TypeScript wirklich sehr, aber es ist schon lustig, dass ich Java vor langer Zeit hinter mir gelassen habe und am Ende doch wieder etwas suche, das Java viel näher ist
Offenbar wollten wir am Ende einfach Java mit JIT, einem reichhaltigeren Typsystem und gradueller Typisierung. Das npm-Ökosystem hat zwar auch viele Nachteile, aber in diesem Ökosystem Bibliotheken zu verwenden, ist viel weniger belastend und macht mehr Spaß
Überraschenderweise vermittelt Rust manchmal ein ähnliches Gefühl, obwohl es am anderen Ende des Sprachspektrums liegt
Edit: JIT war nicht der richtige Ausdruck. Ich habe aus Bequemlichkeit JIT geschrieben, meinte aber die Unterschiede bei Startzeit und Laufzeit, wenn man etwas auf der JVM oder auf V8 ausführt. Java fühlt sich schwergewichtig an, während sich das JavaScript-Ökosystem viel agiler anfühlt
Ich sehe auch überhaupt nicht, dass TypeScript näher an Java wäre. Abgesehen vom irreführenden Namen „Javascript“ und der Unterstützung für imperative Programmierung haben die beiden meiner Meinung nach kaum Gemeinsamkeiten und unterscheiden sich sonst stark
Was ich aus Java allerdings vermisse, ist Runtime-Typreflexion. Im TypeScript-Ökosystem gibt es zahllose Workarounds dafür, aber ich finde sie alle ein wenig unschön
AoT wäre auch gut, und eine Single-Binary-Option wäre schön. Im Idealfall gäbe es keine Runtime, und clevere Eliminierung wäre möglich. Noch besser wäre es, wenn es im Browser auch per JIT laufen könnte
Ich wünsche mir außerdem Compiler- und Typprüfungs-Komfortfunktionen wie exhaustive Pattern Matching
Meine Lieblingsfunktion aus Deno kommt direkt in Node. Sehr schön
Vielleicht muss ich für Type Stripping künftig nicht mehr ständig esbuild installieren. Ich freue mich darauf, Skripte in TypeScript viel einfacher schreiben und verwenden zu können
In letzter Zeit habe ich für Einmalskripte eher Python bevorzugt, aber in Sachen Typen finde ich TypeScript persönlich besser als Python. Gerade größere Skripte, die man nach ein paar Monaten wieder ansieht, profitieren stark von Typen
ts-nodegibt es auch, aber ich persönlich bevorzugetsxhttps://github.com/privatenumber/tsx
Es war wirklich ein ereignisreicher Monat für Node. Erst kam in v22.5.0 node:sqlite dazu, und jetzt kommt auch noch TypeScript-Unterstützung. Mir gefällt die Richtung, in die sich Node bewegt
Ich bin der Autor des PR. Fragt mich alles
Ich habe schon vor langer Zeit angefangen, Node.js für Backend-Arbeit zu nutzen, und es schien viele Vorteile gegenüber dem Schreiben von Code in PHP zu haben, ohne dabei allzu viele der Probleme von Java mitzubringen.
Allerdings wirkte Node etwas grobschlächtig, fast wie eine Sprache, an die man dies und das anflanschen muss, um die Sprache zu bekommen, die man eigentlich will. Am Ende habe ich angefangen, Go zu nutzen; das war deutlich einfacher zu verwenden und machte das Programmieren dank Typsicherheit einfacher, auch wenn es manchmal viel ausführlicher ist.
TypeScript schien eine gute Option zu sein, war am Ende aber wieder nur ein weiterer Aufsatz. Ich bin mir nicht sicher, welchen Mehrwert man bekommt, wenn man TypeScript statt Go nutzt. Klare Typen sind gut, aber es löst nicht die anderen Probleme der Sprache, die in Go gelöst wurden. Deno löst davon ebenfalls einen Teil.
Der große Vorteil von Node gegenüber Go ist, dass man schnell Prototypen bauen kann, aber mit TypeScript scheint dieser Vorteil deutlich kleiner zu werden. Deshalb fällt es mir schwer zu beurteilen, ob das eine gute Entwicklung ist oder ob dadurch die Eigenschaften verloren gehen, die Node zu einer guten Wahl gemacht haben.
Wenn man von einem „Aufsatz“ spricht, liegt das Problem vielleicht weniger in der Sprache selbst als im Fehlen eines meinungsstarken Frameworks, das wie Django standardmäßig alles verwaltet. Ich arbeite gern mit Django, aber es ist etwas schwieriger, den vorgegebenen Weg zu verlassen.
cdktf, weil wir frühere Frontend-Erfahrung hatten, und es schien die logischere Wahl als Go zu sein.