One thing to note is that it is impossible to strip types from TypeScript without a grammar of TypeScript. Stripping types is not a token-level operation, and the TypeScript grammar is changing all the time.
Consider for example: `foo < bar & baz > ( x )`. In TypeScript 1.5 this parsed as (foo<bar) & (baz > (x)) because bar&baz wasn’t a valid type expression yet. When the type intersection operator was added, the parse changed to foo<(bar & baz)>(x) which desugared to foo(x). I realise I’m going back in time here but it’s a nice simple example.
If you want to continue to use new TypeScript features you are going to need to keep compiling to JS, or else keep your node version up to date. For people who like to stick on node LTS releases this may be an unacceptable compromise.
It looks like the team has already considered this in one regard
> There is already a precedent for something that Node.js support, that can be upgraded seperately, its NPM.
Node bundles a version of npm that can upgraded separately, we could do the same with our TypeScript transpiler.
> We could create a package that we bundle but that can also be downloaded from NPM, keep a stable version in core, but if TypeScript releases new features that we don't support or breaking changes, or users want to use the new shiny experimental feature, they can upgrade it separately.
This ensures that users are not locked, but also provides support for a TypeScript version for the whole 3 years of the lifetime of Node.js release.
As long as Node understands to use the project-specific version of TypeScript (i.e., the one in node_modules or the PNP equivalent), that should be fine.
But it would be a step backward to need to globally upgrade TypeScript (as you do with npm), since some older projects will not be compatible with newer versions of TypeScript.
The syntax from the perspective of type stripping has been relatively stable for more versions of Typescript than it was unstable. You had to reach all the way back to 1.5 in part because it's been very stable since about 2.x. The last major shift in syntax was probably Conditional Types in 2.8 adding the ternary if operator in type positions. (The type model if you were to try to typecheck rather than just type-strip has changed a lot since 2.x, but syntax has been generally stable. That's where most of Typescript's innovation has been in the type model/type inferencing rather than in syntax.)
It's still just (early in the process) Stage 1, but the majority of Typescript's type syntax, for the purposes of type stripping (not type checking), is attempting to be somewhat standardized: https://github.com/tc39/proposal-type-annotations
You would also have to update your compiler. I guess you could phrase this as: you can't update your TS versions independently from your node.js version. But that's probably not an issue.
It’s an issue because node has a system of LTS releases, whereas TypeScript has quarterly updates, so the release cadence is different.
Updating node is much more fraught than updating TypeScript. For example, it may break any native code modules. That’s why users are directed to use the LTS and not the most recent release, so that there’s enough time for libraries to add support for the new version.
On the other hand, I usually adopt a new TypeScript version as soon as it comes out.
With a couple exceptions (like enums), you can strip the types out of TypeScript and end up with valid JS. What you could do is stabilize the grammar, and release new versions of TypeScript using the same grammar. Maybe you need a flag to use LTS grammar in your tsconfig.json file.
Mistake isn't the right word. It's just a tradeoff.
There was no perfect solution available, so a tradeoff was necessary. You can disagree with this particular tradeoff, but had they gone another way some people would disagree with that as well. To be a mistake there would have had to have been an option available that was clearly better at the time.
Anyway, the idea that TS 5 should be backwards compatible with TS 1 is probably a bad one. Personally, I think packages with wide usage break backwards compatibility far too easily -- it puts everyone who uses it on an upgrade treadmill, so it should be done very judiciously. But even I wouldn't argue that TS 1 should have been its final form.
I’ll flip this around… reusing comparison as angle brackets is the mistake. C++ ran into some issues too.
I think Rust made the really smart move of putting :: before any type parameters for functions. Go made the good move of using square brackets for type parameters.
You could argue that it was a C++ mistake. It makes parsing harder, but otherwise seems to work as expected, so I don't consider it a mistake, but you could at least argue that way.
But regardless if it was a mistake in C++, it's now a complete standard, used in C++, Java, C#, and other languages to denote type parameters.
I would argue that it would have been a mistake to break that standard. What would you have used, and in what way would that have been enough better to compensate for the increased difficulty in understanding TypeScript generics for users of almost every other popular language?
I think the kind of teams that always stay on top of the latest TypeScript version and use the latest language features are also more likely to always stay on top of the latest Node versions. In my experience TypeScript upgrades actually more often need migrations/fixes for new errors than Node upgrades.
Teams that don't care about latest V8 and Node features and always stay on LTS probably also care less about the latest and greatest TypeScript features.
I work on a large app that’s both client & server typescript called Notion.
We find Typescript much easier to upgrade than Node. New Node versions change performance characteristics of the app at runtime, and sometimes regress complex features like async hooks or have memory leaks. We tend to have multi-week rollout plans for new Node versions with side-by-side deploys to check metrics.
Typescript on the other hand someone can upgrade in a single PR, and once you get the types to check, you’re done and you merge. We just got to the latest TS version last week.
It's already the case for ECMAScript and I don't see why TypeScript should be treated differently when Node.js has to transpile it to JavaScript and among other things ensure that there are no regressions that would break existing code.
Unlike Python typing it's not only type erasure: enums, namespaces, decorators, access modifiers, helper functions and so on need to be transformed into their JavaScript equivalent.
To me beyond v4.4 or so, when it started being possible to create crazy recursive dependent types (the syntax was there since ~4.1 - it's just that the compiler complained), there weren't a lot of groundbreaking new features being added, so unless an external library requires a specific TS version to parse its type declarations, it doesn't change much.
If Node.js can run TypeScript files directly, then the TypeScript compiler won't need to strip types and convert to JavaScript - it could be used solely as a type checker. This would be similar to the situation in Python, where type checkers check types and leave them intact, and the Python interpreter just ignores them.
It's interesting, though, that this approach in Python has led to several (4?) different popular type checkers, which AFAIK all use the same type hint syntax but apply different semantics. However for JavaScript, TypeScript seems to have become the one-and-only popular type checker.
In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments. Support for ignoring types in Node.js would make that approach possible in JavaScript as well.
Flow (by Facebook) used to be fairly significant in the JavaScript several years ago, but right now it's somewhat clear that TypeScript has won rather handily.
Before that there was the closure compiler (Google) which had type annotations in comments.
The annotation syntax in comments was a little clunky but overall that project was ahead of it's time.
Now I believe even inside google that has been transpiled to typescript (or typescript is being transpiled to closure, I can't remember which - the point is that the typescript interface is what people are using for new code).
There was no real competition, Flow was a practical internal tool with 0 marketing budget. Typescript is typical MS 3E strategy with a huge budget. Needless to say, Flow is much more practical and less intrusive, but marketing budget captured all the newbie devs.
Flow tries to be sound and that makes it infinitely better than TS where the creators openly threw the idea of soundness out the window from the very beginning.
> In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments.
Note that there's IDEs that'll use type hints to improve autocomplete and the like too, so even when not checking types it can make sense to add them in some places.
You can have this now adding types with JSDoc and validating them with typescript without compiling, you get faster builds and code that works everywhere without magic or need to strip anything else than comments.
The biggest pain point of using JSDoc at least for me was the import syntax, this has changed since Typescript 5.5, and it's now not an issue anymore.
> If Node.js can run TypeScript files directly, then the TypeScript compiler won't need to strip types and convert to JavaScript
Node.JS isn't the only JS runtime. You'll still have to compile TS to JS for browsers until all the browsers can run TS directly. Although some bundlers already do that by using a non-official compiler, like SWC (the one Node's trying out for this feature).
> In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments.
It's not just comments. It's also, like the name "type hint" suggests, a hint for your IDE to display better autocomplete options.
> It's not just comments. It's also a hint for your IDE to display better autocomplete options.
Ah yes, autocomplete is another benefit of machine-readable type hints. OTOH there's an argument that another IDE feature, informational pop-ups, would be better if they paid more attention to comments and less to type hints:
> In Python, I've even heard of people writing types in source code but never checking them
This is my main approach. Type hints are wonderful for keeping code legible/sane without going into full static type enforcement which can become cumbersome for rapid development.
You can configure typescript to make typing optional. With that option set, you can literally rename .js files to .ts and everything "compiles" and just works. Adding this feature to nodejs means you don't even have to set up tsc if you don't want to.
But if I were putting in type hints like this, I'd still definitely want them to be statically checked. Its better to have no types at all than wrong types.
Exactly. And if you use a library that does lots of meta programming (like Django) then it's impossible to pass all type errors. Hopefully one day the type system will be powerful enough to write a Django project with passing tests.
I don't find TypeScript to be burdensome when rapidly iterating. Depending on how you've configured your dev environment you can just ignore type errors and still run the code.
Incidentally, this is how the ecmascript proposal for introducing types to JS would work by default. The runtime would ignore the types when running code. If you want type checking, you’d have to reach for external tooling.
If this feature ever becomes the default (ie not behind a flag) - how will the NPM ecosystem respond? Will contributors still bother to build CJS end EJS versions when publishing a NPM module, or just slap an 'engine: nodejs >= 25' on the package.json and stop bothering with the build step before pushing to NPM ?
I personally would very much prefer if NPM modules that have their original code in TS and are currently transpiling would stop shipping dist/.cjs so I unambiguously know where to put my debugger/console.log statements. And it would probably be very tempting to NPM contributors to not have to bother with a build step anymore.
But won't this start a ripple effect through NPM where everyone will start to assume very quickly 'everyone accepts TS files' - it only takes one of your dependencies for this effect to ripple through? It seems to me that nodejs can't move this outside an opt-in-experimental-flag without the whole community implicitly expecting all consumers to accept TS files before you know it. And if they do, it will be just months before Firefox and Safari will be force to accept it too, so all JS compilers will have to discard TS type annotations
Which I would personally be happy with - we're building transcompiling steps into NPM modules that convert the ts code into js and d.ts just to support some hypothetical JS user even though we're using TS on the including side. But if node accepts
.ts files we could just remove those transpiling steps without ever noticing it... so what's stopping NPM publishers from publishing js/d.ts files without noticing they broke anything?
The legendary Ryan dahl is actually working on solving the exact problem you described by creating a new package registry called JSR.
Essentially what it does is allow you to upload your typescript code without a build step so when other devs install it they can see the source code of the module in it's original typescript instead of transpiled JavaScript.
That's really cool. One of the benefits of the JS ecosystem is the ability to step through code and crack open your dependencies. Not sure if this would directly make this possible when running your projects/tests, but it at least sounds like a step in that direction.
For the old libraries I maintain that are typescript and transpiled into .cjs and .mjs for npm, I'll probably just start shipping all three versions.
For a new thing I was writing from scratch, yeah, I might just ship typescript and not bother transpiling.
[edit: Apparently not. TS is only for top-level things, not libraries in node_modules according to the sibling comment from satanacchio who I believe is the author of the PR that added TS support and a member of the Node.js Technical Steering Committee]
I would love to ship my source code (.ts) to npm. But Typescript team was very much against this, as there'll be tsconfig issues and other performance issues. But still fingers crossed.
Eventually, node might allow JS to introspect those types.
That would be a huge win. Right now in Python, great tools like pydantic exist because Python can introspect said types, and generate checks out of them.
This mean you can define simple types, and get:
- type checking
- run time data check
- api generation
- api document generation
Out of a single, standard notation.
Right now in JS, things like zod have to do:
const mySchema = z.string();
Which is basically reinventing what typescript is already doing.
That's not entirely true. `z.string()` in Zod offers more than just type safety akin to TypeScript. TypeScript provides compile-time type checking, while Zod adds runtime validation and parsing.
For those unfamiliar:
`z.string()` effectively converts `mySchema` into a functional schema capable of parsing and validation.
I've used it in places where you need runtime validation and in process verification - it works pretty well for that and you can extract the types from it via :
const A = z.string();
type A = z.infer<typeof A>; // string
Meaning if you define your types in zod first, and infer their types from that you get compile and runtime type checking.
---
It a bit of an overkill for nimble and fast code bases though - but works wonders for situations where in process proofing needs to be done, and in all honesty it isn't that big of a task to do this.
> Zod offers more than just type safety akin to TypeScript. TypeScript provides compile-time type checking, while Zod adds runtime validation and parsing.
Well of course it offers more, or you wouldn't be installing a library.
The problem is that even when you're expressing normal Typescript types, you have to use entirely different syntax. It's good that you can usually avoid double-definition, but it's still a big barrier that shouldn't be necessary.
A lot of the focus by the TypeScript team is focused on alignment with the JavaScript language these days and novel new features such as run time types have all but been dismissed or at the very least pushed behind the TC39 JavaScript types proposal. Much like using decorators on variables outside of class structures was.
Having said that, TypeScript allows plugins, these are very rarely used as they augment the language by introducing other features that are transformed into the resulting JavaScript files. One plugin that relates to your suggestion of run time types is called Typia, it permits you to use your TypeScript type signatures at runtime with guards like `assert<MyType>(myValue)` where it intercepts the function call to construct an exhaustive if statement in the transpiled JavaScript checking the nature of the passed variable.
So while I don't see it being a part of the language in the next four to six years, there are at least libraries out there already that allow you to do it today.
If JS ever adds type checking, I hope it doesn't choose Typescript.
We need a type system that is actually sound and TS is intentionally unsound. We need a type system that doesn't allow bad coding practices like TS does. We need a type system that enforces program design that allows programs to be fast. We need a Hindley Milner type system.
If you want a module to be typed, add a `"use type"`. This should disallow bad parts of the language like type coercion. It should disallow things that hurt performance like changing object shape/value type or making arrays of random collections of stuff. Incoming data from untyped modules would either coerce or throw errors if coercion can't be done at which point the compiler can deeply-optimize the typed code because it would have far stronger type guarantees and wouldn't have a risk of bailing out.
having written ocaml in production for a few years, i think soundness comes at a cost of dev ergonomics. at least with the type systems of today’s industry languages.
it blows my mind weekly how ergonomic and flexible typescript’s type system is. it allows me to write great apis for my team mates.
is it possible for the type checker to end up in an infinite loop or for a junior developer to abuse “as”? absolutely, but it doesn’t really matter in practice.
i wouldn’t want to run typescript in rockets or submarines tho!
Does this mean Node can know if an exception is subclass of ValueError or an object is instance of SomeClass? I'm a TS newb, I thought types outside of array, object, number, string arent present in JS and Zod and typeguard functions return plain objects with "trust me bro".
In JS, classes do retain runtime information. So the `instanceof` is a real runtime operator that works by checking the prototype chain of an object. So checking subclasses can be done at runtime.
However, in TS other type information is erased at compile time. So if you write
type Foo = "a" | "b";
the runtime code will see that just as a plain string.
You are right, they aren't. In the JavaScript languages which is what gets actually executed, there are no typescript types.
The parent commenter was talking about a way for nodejs to provide, via an API, the content of type annotations on fields/functions/variables like in python.
However, in python the type annotations are a property of the object at run time, whereas they are completely stripped before execution for typescript.
So I'm not sure how it would work except by changing the typescript philosophy of "not changing runtime execution"
Bun’s DX is pretty unprecedented in this space, and most of my use cases are now covered / not causing Bun to crash (when actually using run-scripts with `bun run`).
Meanwhile, I can’t configure node to not require extensions on import, nor have tsc configured to automatically add .js extensions to its compiled output, without adding on a bundler… although native TypeScript support would remedy this nit quite a bit, I can’t imagine the user experience (or performance) to match Bun’s when it reaches stable.
Extensions should be required. It's not possible to do path searches over the network like you can on local disk, and network-attached VMs, like browsers, are a very, very important runtime for JavaScript.
Fortunately, code is generally bundled for browsers to reduce the number of network requests and total size of downloads. And node has access to the filesystem, so it can do path searches just fine if it wants to support existing code.
I like Bun a lot, but Deno is (still) the more mature, stable, capable (e.g. stable workers, http2) and depending on the use-case more performant option (V8 > JSC). DX and tooling is top-notch. Deno can perform typchecking, btw. They bundle the TSC IIRC. Bun is the hype, but Deno is currently clearly the better option for serious endevours. Still, the vision and execution of Bun is impressive. Good for us devs.
Bun started with compatibility with NodeJS as a primary goal, whereas for Deno it took a while to be able to import npm stuff. (Of course there are fun WTF[0] errors with Bun, and I only tried Deno before the npm import feature landed.)
Jokes apart, Zig is moving forward a lot which is why it's not 1.0 yet, but it doesn't mean you can't write safe and performant applications right now.
Zig is also a rather simple and straightforward language (like C) and has powerful compile-time code generation (like C macros, but without the awful preprocessor).
You still get segmentation faults. My biggest complain with bun is not having enough safety.
If you use frameworks written for node memory usage is very high and performance is meh.
If you use frameworks written for bun they smoke anything on node.
I'd definitely move over, just to get rid of the whole TypeScript / cjs / esm crap, but:
1. frontend support is poor (next.js / solid.js - I can't run anything fully on bun)
3. I still need to rewrite my backend app from a node.js framework to a bun one
4. for backend development the javascript ecosystem is losing the crown: if I wanted something safe I'd just write it in Rust (TS allows any random developer to write crap with any in it and it validates), if I'm doing something AI related I'd probably need python anyway and fastapi is not half bad
I really enjoy typescript and have been yearning for a typescript runtime but I can't help but laugh that I left java all those years ago to finally seek something a lot closer to java.
I guess we all just wanted java with JIT, more feature rich type system and gradual typing. Also for all the shortcomings of npm ecosystem, it is a lot less daunting and more fun to be using libraries in this ecosystem.
And surprisingly even though rust is on a different end of the language spectrum but yet it offers a similar feel.
Edit: JIT was not the right terminology to use. I lazily wrote JIT. Apologies. What I meant to convey was the difference in startup times and run time between running something in JVM and V8. Java feels heavy but in javascript ecosystem it feels so nimble.
Java was literally the thing that made the term "JIT" popular, so I really don't know what you were going for here.
Also I just can't see how Typescript is in any way "closer" to Java - it's incredibly different IMHO. The only thing they have in common is probably the "Javascript" misnomer and the fact both support imperative programming, but that's it.
Typescript’s optional and unsound type system also does nothing for a JIT beyond what it could already do for JavaScript, you can’t do optimization if your types are unreliable. However, I really really like how Typescript’s type system super charges developer productivity (type errors via the compiler and feedback via the IDE), and don’t mind this part of the design at all.
Better for what? Quickly churning out short-lived code to get the next round of funding, definitely. Writing (and _supporting_) "serious" projects over the long term, which also require high performance and/or high scalability, and can rip through terabytes of data if needed, definitely not. (All IMHO from lots of personal experience.)
I'm very glad to use typescript over java, personally - the ergonomics are so much better! Especially if you stray away from the somewhat incomplete classes thing (type support for decorator arguments isn't great, for instance) and just focus on interfaces and functions.
One thing I miss that java has is runtime reflection of types though. Typescript's ecosystem has a million different ways to get around that and they're all a bit ugly imo.
I don't use them directly much, but template literal generic and contidiontal types is probably the closest a mainstream language has inched towards dependent types.
1. *Type Inference*: TypeScript can automatically infer types from context, reducing the need for explicit type declarations.
2. *Union and Intersection Types*: Allows combining multiple types, offering more flexibility in defining data structures.
3. *Literal Types*: TypeScript supports exact values as types (e.g., specific strings or numbers), which can be useful for more precise type-checking.
4. *Type Aliases*: You can create custom, reusable types, enhancing code clarity and maintainability.
5. *Interfaces and Structural Typing*: Interfaces allow for flexible contracts, and TypeScript uses structural typing, where the type compatibility is based on the shape of the data rather than explicit type declarations.
6. *Mapped and Conditional Types*: These allow for dynamic type creation and manipulation, making the type system more powerful and expressive.
7. *Optional Properties and Strict Null Checks*: These provide better handling of undefined and null values.
I'm somewhere here as well. Personally I think what I want is the stdlib (without the current legacy/ all but deprecated bits) and ecosystem of c# but with the ease and power of structural algebraic types. AoT is fine, with option for single binary. Ideally runtimeless with clever trimming. If it also ran jitted in the browser all the better.
I also want compiler/type checker niceties like exhaustive pattern matching.
Greater than 95% of the incompetence in JavaScript comes from two camps. The first of those are people who absolutely cannot program at all. The second of those are Java developers who were taught Java in school and it’s all they can do, so everything must look like Java.
The result of both tribes is pretending to do something they cannot do on their own. When you’re a pretender vanity becomes excessively important because everything is superficial, so you get layers of shit you don’t need that they cannot live without. Any attempts slice off the unnecessary bullshit always results in hyper emotional distress because people feel threatened when exposed. That right there is why I will never write JavaScript for employment ever again.
Java's type system was just very limited, gradual typing is a poor tradeoff most of the time. I used to think there were advantages to something like Python, but once I found Scala I never went back.
Yes, JIT was not the right terminology to use. I lazily wrote JIT. Apologies. What I meant to convey was the difference in startup times and run time between running something in JVM and V8. Java feels heavy but in javascript ecosystem it feels so nimble.
Gradual typing is the key. The problem with Java is that types are in your face way before you actually need them.
With TS you can prototype with JS and only after you know what you are looking for you can start to add types to find bugs and edge cases and want to get nice code completions for your stuff.
Gradual typing could still keep some static guarantees if the static part were sound, e.g. you couldn't assign a dynamic-typed integer to a string-typed variable without checking the type at runtime first; which TypeScript isn't.
Elixir's new type system does much better here, as it determines whether a function actually guards for the right type at runtime ("strong arrows") and propagates the guarantees, or lack thereof, accordingly.
The fact that Java forced you to write types, and then made everything implicitly nullable so that you still get NullPointerExceptions at runtime after writing out all those types, was probably a big reason why dynamically-typed languages became popular.
The type system is a big part of what made Java cumbersome. It's loosened up a little over the years. TS itself may allow partial typing, but when team/company policies are involved, you'll often end up being forced to type everything.
My favorite deno feature is coming to node directly. Awesome!
Maybe this means I don't always have to install esbuild to strip types - very excited how this will make writing scripts in TypeScript that much easier to use. I lately have been prefering Python for one off scripts, but I do think personally TypeScript > Python wrt types. And larger scripts really benefit from types especially when looking at them again after a few months.
Seconded again. While tsx usually just works ts-node almost never just works. tsx is perhaps unfortunately named though so it may confuse people at first since it has nothing to do with jsx syntax.
This seems very interesting approach to scripting. Does it basically provides with an alias to child_process.exec as $ and besides that I can write in the same way I'd do in Node?
> Node.js standard library requires additional hassle before using
I read the hassle as having to setup Node runtime in advance, but zx requires npm to install so I'm not sure.
Deno has so many other great features. Most web standard APIs are available in Deno, for example. It can do URL imports. It has a built in linter, formatter, and test framework. Built in documentation generator. A much better built in web server.
Node is copying many of these features to varying degrees of success. But Deno is evolving, too.
Kidding aside: You should really take an hour and check out the manual and std lib (https://jsr.io/@std). I was surprised how far Deno has come. A lot of pretty useful stuff you would otherwise need tons of NPM modules for.
It's been a really eventful month for Node. First they added node:sqlite in v22.5.0, and now TypeScript support is landing. I love the direction Node is heading in.
Consider for example: `foo < bar & baz > ( x )`. In TypeScript 1.5 this parsed as (foo<bar) & (baz > (x)) because bar&baz wasn’t a valid type expression yet. When the type intersection operator was added, the parse changed to foo<(bar & baz)>(x) which desugared to foo(x). I realise I’m going back in time here but it’s a nice simple example.
If you want to continue to use new TypeScript features you are going to need to keep compiling to JS, or else keep your node version up to date. For people who like to stick on node LTS releases this may be an unacceptable compromise.
> There is already a precedent for something that Node.js support, that can be upgraded seperately, its NPM. Node bundles a version of npm that can upgraded separately, we could do the same with our TypeScript transpiler.
> We could create a package that we bundle but that can also be downloaded from NPM, keep a stable version in core, but if TypeScript releases new features that we don't support or breaking changes, or users want to use the new shiny experimental feature, they can upgrade it separately. This ensures that users are not locked, but also provides support for a TypeScript version for the whole 3 years of the lifetime of Node.js release.
https://github.com/nodejs/loaders/issues/217
But it would be a step backward to need to globally upgrade TypeScript (as you do with npm), since some older projects will not be compatible with newer versions of TypeScript.
Ask me how I know. ;)
It's still just (early in the process) Stage 1, but the majority of Typescript's type syntax, for the purposes of type stripping (not type checking), is attempting to be somewhat standardized: https://github.com/tc39/proposal-type-annotations
Updating node is much more fraught than updating TypeScript. For example, it may break any native code modules. That’s why users are directed to use the LTS and not the most recent release, so that there’s enough time for libraries to add support for the new version.
On the other hand, I usually adopt a new TypeScript version as soon as it comes out.
With a couple exceptions (like enums), you can strip the types out of TypeScript and end up with valid JS. What you could do is stabilize the grammar, and release new versions of TypeScript using the same grammar. Maybe you need a flag to use LTS grammar in your tsconfig.json file.
There was no perfect solution available, so a tradeoff was necessary. You can disagree with this particular tradeoff, but had they gone another way some people would disagree with that as well. To be a mistake there would have had to have been an option available that was clearly better at the time.
Anyway, the idea that TS 5 should be backwards compatible with TS 1 is probably a bad one. Personally, I think packages with wide usage break backwards compatibility far too easily -- it puts everyone who uses it on an upgrade treadmill, so it should be done very judiciously. But even I wouldn't argue that TS 1 should have been its final form.
I think Rust made the really smart move of putting :: before any type parameters for functions. Go made the good move of using square brackets for type parameters.
You could argue that it was a C++ mistake. It makes parsing harder, but otherwise seems to work as expected, so I don't consider it a mistake, but you could at least argue that way.
But regardless if it was a mistake in C++, it's now a complete standard, used in C++, Java, C#, and other languages to denote type parameters.
I would argue that it would have been a mistake to break that standard. What would you have used, and in what way would that have been enough better to compensate for the increased difficulty in understanding TypeScript generics for users of almost every other popular language?
We find Typescript much easier to upgrade than Node. New Node versions change performance characteristics of the app at runtime, and sometimes regress complex features like async hooks or have memory leaks. We tend to have multi-week rollout plans for new Node versions with side-by-side deploys to check metrics.
Typescript on the other hand someone can upgrade in a single PR, and once you get the types to check, you’re done and you merge. We just got to the latest TS version last week.
Unlike Python typing it's not only type erasure: enums, namespaces, decorators, access modifiers, helper functions and so on need to be transformed into their JavaScript equivalent.
To me beyond v4.4 or so, when it started being possible to create crazy recursive dependent types (the syntax was there since ~4.1 - it's just that the compiler complained), there weren't a lot of groundbreaking new features being added, so unless an external library requires a specific TS version to parse its type declarations, it doesn't change much.
Some edge-cases involving those have bugfixes and ergonomy improvents I've run into on 5.x.
Dead Comment
It's interesting, though, that this approach in Python has led to several (4?) different popular type checkers, which AFAIK all use the same type hint syntax but apply different semantics. However for JavaScript, TypeScript seems to have become the one-and-only popular type checker.
In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments. Support for ignoring types in Node.js would make that approach possible in JavaScript as well.
Note that there's IDEs that'll use type hints to improve autocomplete and the like too, so even when not checking types it can make sense to add them in some places.
The biggest pain point of using JSDoc at least for me was the import syntax, this has changed since Typescript 5.5, and it's now not an issue anymore.
It's fine on toy projects, and somewhat I would say, for 99% of users that don't even know what a mapped or intersection type is.
Node.JS isn't the only JS runtime. You'll still have to compile TS to JS for browsers until all the browsers can run TS directly. Although some bundlers already do that by using a non-official compiler, like SWC (the one Node's trying out for this feature).
> In Python, I've even heard of people writing types in source code but never checking them, essentially using type hints as a more convenient syntax for comments.
It's not just comments. It's also, like the name "type hint" suggests, a hint for your IDE to display better autocomplete options.
Ah yes, autocomplete is another benefit of machine-readable type hints. OTOH there's an argument that another IDE feature, informational pop-ups, would be better if they paid more attention to comments and less to type hints:
https://discuss.python.org/t/a-more-useful-and-less-divisive...
I think this should be part of the language spec.
E: I thought it was JSDoc proposal. Ignore the comment.
This is my main approach. Type hints are wonderful for keeping code legible/sane without going into full static type enforcement which can become cumbersome for rapid development.
But if I were putting in type hints like this, I'd still definitely want them to be statically checked. Its better to have no types at all than wrong types.
I personally would very much prefer if NPM modules that have their original code in TS and are currently transpiling would stop shipping dist/.cjs so I unambiguously know where to put my debugger/console.log statements. And it would probably be very tempting to NPM contributors to not have to bother with a build step anymore.
But won't this start a ripple effect through NPM where everyone will start to assume very quickly 'everyone accepts TS files' - it only takes one of your dependencies for this effect to ripple through? It seems to me that nodejs can't move this outside an opt-in-experimental-flag without the whole community implicitly expecting all consumers to accept TS files before you know it. And if they do, it will be just months before Firefox and Safari will be force to accept it too, so all JS compilers will have to discard TS type annotations
Which I would personally be happy with - we're building transcompiling steps into NPM modules that convert the ts code into js and d.ts just to support some hypothetical JS user even though we're using TS on the including side. But if node accepts
.ts files we could just remove those transpiling steps without ever noticing it... so what's stopping NPM publishers from publishing js/d.ts files without noticing they broke anything?Essentially what it does is allow you to upload your typescript code without a build step so when other devs install it they can see the source code of the module in it's original typescript instead of transpiled JavaScript.
For a new thing I was writing from scratch, yeah, I might just ship typescript and not bother transpiling.
[edit: Apparently not. TS is only for top-level things, not libraries in node_modules according to the sibling comment from satanacchio who I believe is the author of the PR that added TS support and a member of the Node.js Technical Steering Committee]
That would be a huge win. Right now in Python, great tools like pydantic exist because Python can introspect said types, and generate checks out of them.
This mean you can define simple types, and get:
- type checking - run time data check - api generation - api document generation
Out of a single, standard notation.
Right now in JS, things like zod have to do:
Which is basically reinventing what typescript is already doing.For those unfamiliar:
`z.string()` effectively converts `mySchema` into a functional schema capable of parsing and validation.
For example:
`mySchema.parse("some data")` returns successfully.
`mySchema.parse(321)` throws an exception.
I've used it in places where you need runtime validation and in process verification - it works pretty well for that and you can extract the types from it via :
const A = z.string(); type A = z.infer<typeof A>; // string
Meaning if you define your types in zod first, and infer their types from that you get compile and runtime type checking.
---
It a bit of an overkill for nimble and fast code bases though - but works wonders for situations where in process proofing needs to be done, and in all honesty it isn't that big of a task to do this.
Well of course it offers more, or you wouldn't be installing a library.
The problem is that even when you're expressing normal Typescript types, you have to use entirely different syntax. It's good that you can usually avoid double-definition, but it's still a big barrier that shouldn't be necessary.
Having said that, TypeScript allows plugins, these are very rarely used as they augment the language by introducing other features that are transformed into the resulting JavaScript files. One plugin that relates to your suggestion of run time types is called Typia, it permits you to use your TypeScript type signatures at runtime with guards like `assert<MyType>(myValue)` where it intercepts the function call to construct an exhaustive if statement in the transpiled JavaScript checking the nature of the passed variable.
So while I don't see it being a part of the language in the next four to six years, there are at least libraries out there already that allow you to do it today.
We need a type system that is actually sound and TS is intentionally unsound. We need a type system that doesn't allow bad coding practices like TS does. We need a type system that enforces program design that allows programs to be fast. We need a Hindley Milner type system.
If you want a module to be typed, add a `"use type"`. This should disallow bad parts of the language like type coercion. It should disallow things that hurt performance like changing object shape/value type or making arrays of random collections of stuff. Incoming data from untyped modules would either coerce or throw errors if coercion can't be done at which point the compiler can deeply-optimize the typed code because it would have far stronger type guarantees and wouldn't have a risk of bailing out.
it blows my mind weekly how ergonomic and flexible typescript’s type system is. it allows me to write great apis for my team mates.
is it possible for the type checker to end up in an infinite loop or for a junior developer to abuse “as”? absolutely, but it doesn’t really matter in practice.
i wouldn’t want to run typescript in rockets or submarines tho!
However, in TS other type information is erased at compile time. So if you write
the runtime code will see that just as a plain string.The parent commenter was talking about a way for nodejs to provide, via an API, the content of type annotations on fields/functions/variables like in python.
However, in python the type annotations are a property of the object at run time, whereas they are completely stripped before execution for typescript.
So I'm not sure how it would work except by changing the typescript philosophy of "not changing runtime execution"
Meanwhile, I can’t configure node to not require extensions on import, nor have tsc configured to automatically add .js extensions to its compiled output, without adding on a bundler… although native TypeScript support would remedy this nit quite a bit, I can’t imagine the user experience (or performance) to match Bun’s when it reaches stable.
foo could mean foo/index.js, foo.js at the minimum. So you have 2x the lookups. Oh no, wait we also potentially have mjs, cjs, jsx, ts and tsx.
So 12 times the stat checking for each import.
Well except for Deno...
[0] https://github.com/oven-sh/bun/issues/11420
Jokes apart, Zig is moving forward a lot which is why it's not 1.0 yet, but it doesn't mean you can't write safe and performant applications right now.
Zig is also a rather simple and straightforward language (like C) and has powerful compile-time code generation (like C macros, but without the awful preprocessor).
If you use frameworks written for node memory usage is very high and performance is meh.
If you use frameworks written for bun they smoke anything on node.
I'd definitely move over, just to get rid of the whole TypeScript / cjs / esm crap, but:
1. frontend support is poor (next.js / solid.js - I can't run anything fully on bun)
3. I still need to rewrite my backend app from a node.js framework to a bun one
4. for backend development the javascript ecosystem is losing the crown: if I wanted something safe I'd just write it in Rust (TS allows any random developer to write crap with any in it and it validates), if I'm doing something AI related I'd probably need python anyway and fastapi is not half bad
Deleted Comment
However, the node:crypto module still doesn't work 100%. So, I can't use it yet.
It seems to be the default now:
I guess we all just wanted java with JIT, more feature rich type system and gradual typing. Also for all the shortcomings of npm ecosystem, it is a lot less daunting and more fun to be using libraries in this ecosystem.
And surprisingly even though rust is on a different end of the language spectrum but yet it offers a similar feel.
Edit: JIT was not the right terminology to use. I lazily wrote JIT. Apologies. What I meant to convey was the difference in startup times and run time between running something in JVM and V8. Java feels heavy but in javascript ecosystem it feels so nimble.
Java was literally the thing that made the term "JIT" popular, so I really don't know what you were going for here.
Also I just can't see how Typescript is in any way "closer" to Java - it's incredibly different IMHO. The only thing they have in common is probably the "Javascript" misnomer and the fact both support imperative programming, but that's it.
Dead Comment
One thing I miss that java has is runtime reflection of types though. Typescript's ecosystem has a million different ways to get around that and they're all a bit ugly imo.
Java has JIT. How is TypeSript type system feature-richer than the Java one?
https://www.typescriptlang.org/docs/handbook/2/template-lite... alone puts TS over anything that Java has.
Some examples of TypeScript power:
- SQL database in TypeScript types: https://github.com/codemix/ts-sql
- Statically typed raw SQL queries: https://github.com/andywer/squid?tab=readme-ov-file#tag-func...
- (Someone fill in your TS hackery for me)
Utility types, like Partial<T>, are basically impossible to represent in Java except with almost-duplicated classes.
I also want compiler/type checker niceties like exhaustive pattern matching.
Oh god no. What an abomination.
Greater than 95% of the incompetence in JavaScript comes from two camps. The first of those are people who absolutely cannot program at all. The second of those are Java developers who were taught Java in school and it’s all they can do, so everything must look like Java.
The result of both tribes is pretending to do something they cannot do on their own. When you’re a pretender vanity becomes excessively important because everything is superficial, so you get layers of shit you don’t need that they cannot live without. Any attempts slice off the unnecessary bullshit always results in hyper emotional distress because people feel threatened when exposed. That right there is why I will never write JavaScript for employment ever again.
With TS you can prototype with JS and only after you know what you are looking for you can start to add types to find bugs and edge cases and want to get nice code completions for your stuff.
God I wish they’d just integrate something lightweight like npm into JDK.
It is beyond me why you have to install third-party heavy weight tool just to manage dependencies.
AKA dynamic typing. Unless it's 100% static, it's dynamic
Elixir's new type system does much better here, as it determines whether a function actually guards for the right type at runtime ("strong arrows") and propagates the guarantees, or lack thereof, accordingly.
In Typescript you have far more freedom, and all the benefits of strong types.
Maybe this means I don't always have to install esbuild to strip types - very excited how this will make writing scripts in TypeScript that much easier to use. I lately have been prefering Python for one off scripts, but I do think personally TypeScript > Python wrt types. And larger scripts really benefit from types especially when looking at them again after a few months.
https://github.com/privatenumber/tsx
You cannot run tsx from a non-project cwd if you’re using tsconfig/paths.
And personally I find its maintainers relatively unpleasant to message with. Leaves “you’re plebs” aftertaste most of the times.
> Node.js standard library requires additional hassle before using
I read the hassle as having to setup Node runtime in advance, but zx requires npm to install so I'm not sure.
Node is copying many of these features to varying degrees of success. But Deno is evolving, too.
Kidding aside: You should really take an hour and check out the manual and std lib (https://jsr.io/@std). I was surprised how far Deno has come. A lot of pretty useful stuff you would otherwise need tons of NPM modules for.