Repository navigation
Roadmap for experimental TypeScript support #217
Description
Intro
Since the previous discussion was successful in gathering feedback from the community, and I believe there is some consensus that this feature is something that the project wants to add, I want to summarize what are going to be the next steps and some technical solutions.
To be honest I'm very glad for the amount of feedback I received, it helped me to change my view on some aspects that I previously ignored.
Before I go into technical implementation, this is what the goals of this feature should be in my opinion:
Caution
These are long term goals and next steps, they might not reflect the current status.
- Make sure Node.js can support TypeScript with the stability guarantees that has always offered.
- Avoid breaking the ecosystem, this means creating a new "flavor" that only works on Node.js.
- Performance.
- Keeping it simple, we don't support everything, we want to user land tools to work in sync with Node, we provide the foundations to build on top.
- Type stripping is the way to go.
Step 1: Initial implementation
The initial implementation is the proof of concept that I have created to gather feedback and consensus from the project collaborators.
It's very far from being perfect but it establishes some of the points we want to move forward with.
Current limitations:
- No support forTypeScript features that require transformation (Enums, namespace, etc...).
- No
.jsextension for.tsfiles. - No running TypeScript in
node_modules. - No source maps, but not needed because we perform whitespacing (replace removed code with white space).
Important
Why no support for TypeScript features such as enums, namespaces, decorators, etc...?????
- In the initial implementation I decided to start with the smallest subset possible.
- Adding transformation means supporting source-maps so is more work.
- Supporting features means adding more instability to the TypeScript syntax we support, without having a way to stay up to date with new features. As a general rule of thumb, the more you support the more likely to have breaking changes. This is a great challenge to overcome because we need to support transformations and new features but respect Node stability guarantees
Step 2: Decoupling
- Amaro as standalone loader feat: create amaro loader amaro#47
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.
Getting started
Create a new package called amaro that wraps swc with the current implementation. This package, which is released on npm, offers the same api that is currently used by Node, and it can be upgraded separately. The first challenge would be setup the project, make sure it can be upgraded by running npm install amaro@vX.Y.Z
Increase features
-
Enable support for TypeScript features that require transformation (Enums, mamespaces), now that we are decoupled we can expand on the amount of feature we support.
Caution
Currently there is consensus that Node.js should NOT run TypeScript files inside `node_modules.
It is not supported to avoid package maintainers to release TS only package.
Thanks @joyeecheung and @legendecas for the idea 💡
Step 3: Make it fast
With the project up and running, we can now start thinking about performance.
We could vendor SWC in Rust, build our own wasm, or compile it to static libraries that we could use in core, c++ rust FFI, etc...
I'm not a performance expert, but I think it is possible to optimize the interaction between Node and SWC and make it performant, without impacting the Node build process.
This is the phase where we measure the performance and make it usable in production without performance penalties.
Step 4: Add more features
- Amaro as Loader feat: create amaro loader amaro#47
- Rewrite .ts => to .js supported by TS 5.7 Rewrite relative import extensions with flag microsoft/TypeScript#59767
Wont Fix
- I strongly believe we should not support
.jsextension for.tsfiles.
The reason is that the compiler/bundler should be responsible to resolve the correct extension at compile time.
At runtime, the extension must be correct, it doesn't make sense to add overhead in production when it can be solved during development. Discussion happening here: Import specifiers in--experimental-strip-types#214.
This is also supported by TypeScript from 5.7 Rewrite relative import extensions with flag microsoft/TypeScript#59767 - Support
tsconfigdirectly
This would mean to run with typescript to support the latest flavors etc... Compilation and type checking should be done by user land tools during development.
Activity
module: add --experimental-strip-types
GeoffreyBooth commentedon Jul 24, 2024
I think this looks great; can we just replace the contents of the
.mdfile in #210 with what you’ve written here? And then we can land that PR.silverwind commentedon Jul 25, 2024
Is there a definitive list of features that are not supported here? I'm looking to adapt my eslint config to match.
marco-ippolito commentedon Jul 25, 2024
it's documented here https://github.com/nodejs/node/blob/main/doc/api/typescript.md but you should expect it to change in the future
jsejcksn commentedon Jul 26, 2024
There's a Getting Started article on the Node.js website — Node.js with TypeScript (source) — that might benefit from an update including information about this feature (once the time is right). I suppose a linked issue should be created in that repository, but wasn't sure whether it should be included somewhere in the list here, too.
shawnmcknight commentedon Jul 26, 2024
I understand the argument against encouraging package maintainers from releasing TS only packages. However, one additional thing to consider here is how node operates with internal packages in a monorepo. I'm not familiar with how other package managers work in this regard today, but
pnpmwill make links for internalworkspaceprotocol packages asnode_modulesreferences in the dependent package. Ifnode_modulessupport is not available, that would still then require a build step for any internal dependencies. I'm not sure how you could differentiate between these internal dependencies versus published packages, but being able to bypass a build step for internal packages would be extremely beneficial for monorepos structured this way.jakebailey commentedon Jul 26, 2024
I would think that this detail wouldn't matter because the
realpathof these paths would leavenode_modules. However, I'm not 100% certain how one would set up a monorepo with TS support via this flag anyway; you'd need to point things like export maps to TS files, making those export maps broken if packages are ever published, unless you go the "live types" route with a monorepo-local custom condition.shawnmcknight commentedon Jul 26, 2024
This might just work out of the box then. I haven't tested with the new experimental flag yet, but it would be awesome if it just worked without any other considerations. 👍
In my use case, I wasn't thinking of publishing because the repo in question isn't published but is our internal backend runtime. With respect to things like export maps, converting them at build-time is actually how we make these internal packages work today. The
exportsand/ormainproperties inpackage.jsonare referencing the.tsfiles in the source. This allows for internal type checking to use the dependencies'.tsfiles and forts-nodeto operate against the source file even in the dependencies. At build time, theexportsand/ormainproperties are converted fromsrc/*.tstodist/*.jsso the dependent packages are then using the transpiled JS.I'm unsure if this is a common use case in the rest of the world, but I'm optimistic based on the
realpathcomment above that this might just work without any additional overhead.Daniel15 commentedon Jul 27, 2024
Is the idea to eventually support other typing systems (for example, Flow, Hegel, or whatever comes next after TypeScript) like https://github.com/tc39/proposal-type-annotations proposes, or is this feature focused solely on TypeScript?
If it's only going to be for TypeScript, wouldn't it be better for the
--experimental-strip-typesflag to include typescript or ts in its name?module: add --experimental-strip-types
module: add --experimental-strip-types
alshdavid commentedon Aug 13, 2024
My understanding is that the inclusion of TypeScript support in Nodejs is to simplify the development cycle - where distributed code should still be transpiled to JavaScript before consumption (npm, production, etc).
Nodejs-valid TypeScript requires the
.tsextension to be supplied in the import specifier. The issue is that there is no easy way to transpile imports with.tsextensions to.js.The official TypeScript compiler does not support transforming imports due to the difficulty of handling dynamic imports.
SWC has a plugin that can rewrite import extensions, but it skips dynamic imports and is annoying to set up. Ideally, this additional tooling would be avoided as it diminishes the benefit of using built-in TS support over a loader.
Can we get guidance on how to properly transpile Nodejs-valid TypeScript to valid JavaScript for distribution?
marco-ippolito commentedon Aug 14, 2024
Some discussion going on here #214.
I think we still need to figure out with @nodejs/typescript
1 - A recomended tsconfig
2 - How to solve the extension resolution during compilation
3 - add to amaro a way to perform the extension resolution wheh used as external loader
27 remaining items
karlhorky commentedon Oct 13, 2024
Edit: Copied to dedicated issue nodejs/typescript#14
I know that as a user I won't have much sway here, but I think I'm not alone in thinking that files in
node_modulesshould also be allowed type stripping.A few thoughts:
1) Node.js Alone Won't Prevent TS npm Publishing
There is a lot of movement in the "execute TypeScript, don't build it" ecosystem already, for example with the advent of many ecosystem projects such as:
ts-node,tsm,tsxPublishing
.tsfiles to npm and other registries is already a growing pattern, which drastically reduces the effort and complexity of publishing a TypeScript package.I think as these projects evolve and more people get to know about this possibility, there will be a greatly increased interest in this possibility.
It's my opinion that this banning of
node_modules/*/*.tsin Node.js alone won't be able to hold back the energy in the ecosystem, especially with working alternatives. (not to mention users circumventing this by using any of the other projects, which do not have this limitation)2) User Experience
My first experience with
node --experimental-strip-typeswas creating an internal checking tool which was its own package, without a build step.I immediately fell flat on my face with this, because of the banning of
node_modules/*/*.tsfiles.Even though I was already somewhat aware of the discussions and objections behind this, for my use case it felt like an arbitrary limitation imposed on my project.
I imagine that as type stripping becomes more widespread, this will be a common complaint from users.
3) Node.js Features Available Everywhere
@GeoffreyBooth wrote about supporting a Node.js feature everywhere:
This resonates with me - it seems like supporting a Node.js feature in as many places as possible will be the best for users and cause the least amount of support complaints.
Banning
node_modules/*/*.tsto try to avoid a pattern which the ecosystem may adopt seems like a weak reason. (not to mention that the ecosystem may end up adopting this pattern anyway by finding cowpaths around this)4) Identifying and Addressing Downsides
Downsides as mentioned by Ryan and Daniel on the TypeScript team and vocal
node_modules/*/*.tsopponents such as Matteo are important and should be considered.But it feels like also:
This being done with a dispassionate, neutral stance, not trying to prove any one viewpoint.
In reading through the publicly-available documents, I don't think this has been done yet.
5) At Least Allow Users to Choose
Even if careful enumeration of problems and solutions leads to the final conclusion that the tradeoff is not worth it for Node.js, I think it would be good to allow users to choose for themselves and disable this
node_modules/*/*.tsbanning with a flag, eg:karlhorky commentedon Oct 13, 2024
Edit: Copied to dedicated issue nodejs/typescript#14
6) Similarities to
.tsRewriting LimitationOh one more thing, more related of a feeling than fact-based:
This
node_modules/*/*.tsbanning feels like it has a similar vibe to the.ts->.jsrewriting decision in TypeScript, which was overturned after years of doubling-down on.jsimports (ironically heavily inspired by Node.js type stripping).robpalme commentedon Oct 13, 2024
Hello @karlhorky - thanks for the thoughtful input. @marco-ippolito mentioned in the most recent Node TS meeting that we haven't received much feedback on
--experimental-strip-typesyet, so your real-life experience is useful to hear.Whilst this restriction is trivial to lift in terms of code changes in Node, the implications and opinions on doing so are significant. I'd suggest re-posting it as a dedicated issue on the https://github.com/nodejs/typescript repo to avoid thread-explosion here.
karlhorky commentedon Oct 13, 2024
@robpalme thanks for the feedback! Copied to a dedicated issue here and edited my posts above:
node_modules/*/*.tstypescript#14alshdavid commentedon Oct 14, 2024
Sorry for bumping an old ticket, but in light of monorepo support currently only available through third party loaders (like
tsx), it would be super helpful if Nodejs Workers would inherit--loaderloaders from the parent process. This is a problem I am currently facingallisonkarlitskaya commentedon Oct 14, 2024
Is there somewhere where feedback is being solicited? I've played around with it a bit and found it to be a great replacement for
tsxin the projects where we use it. The only downside, honestly, is the big scary warning and our concerns about depending on "too new" of a node version for our users.marco-ippolito commentedon Oct 14, 2024
@allisonkarlitskaya feedback is being received through issues/discussion is the nodejs/typescript repo
thernstig commentedon Oct 28, 2024
I am unsure if this comment is fitting here, but I see mentions of swc in the original post. Would it not be better to use https://oxc.rs/ and its parser as it is 3x faster than swc?
marco-ippolito commentedon Jan 3, 2025
All the items on the roadmap are completed. The feature landed and it's on its way to become stable. I think we can close this issue.
akirarika commentedon Mar 26, 2025
As a package developer, the packages I've been releasing have been directly to .ts files, and since I've been using vite or bun, I didn't realize it was a problem until I switched to Node.js and got a frustrating error
karlhorky commentedon Mar 26, 2025
@akirarika you may want to take a look at the proposal to allow TypeScript type stripping in
node_modules/*/*.ts:node_modules/*/*.tstypescript#14w3nl commentedon Mar 26, 2025
I think Node have to be aware what to support about the types.
The type annotations, like there is a proposal from TC39?
Or real Typescript support?
I think a big difference is that Ecmascript don't have breaking changes, and are all regulated by TC39.
Typescript is maintained by Microsoft, and has sometimes breaking changes, how does Node.js handle this?
When the roadmap is going to full Typescript support, does every Node version has also a specific Typescript version? And can you set a Typescript version? Like e.g. you have now a NPM version?
Or are we going to speed up e.g. the type annotations, that this will become a standard, that we can use in Javascript. (browser+Node.js), and the Typescript tools also can validate it, like it can do with JSDoc in JS+TS or type annotations that is now only available in TS.
And if you write Typescript code, the compile don't have to strip e.g. the types, because Typescript van follow the TC39 standard.
Now the flag is strip-types, that sounds logic for me, more generic, suggests not only Typescript.
But a lot of times I read about Typescript in documentation and other messages.
This experiment is great, and I hope with all feedback we can make the path more clear what Node.js is going to support.
JakobJingleheimer commentedon Mar 26, 2025
@w3nl I think you'll find the type-stripping PRs, blog posts, Learn articles, etc helpful as those questions have been answered/addressed 🙂