Skip to content

Roadmap for experimental TypeScript support #217

Closed

Description

@marco-ippolito

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 .js extension for .ts files.
  • 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

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

  • Support for transformation behind a flag

  • 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


Wont Fix

  • I strongly believe we should not support .js extension for .ts files.
    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 tsconfig directly
    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

  1. GeoffreyBooth commented on Jul 24, 2024

    @GeoffreyBooth
    Member

    I think this looks great; can we just replace the contents of the .md file in #210 with what you’ve written here? And then we can land that PR.

  2. silverwind commented on Jul 25, 2024

    @silverwind

    No support forTypeScript features that require transformation (Enums, namespace, etc...).

    Is there a definitive list of features that are not supported here? I'm looking to adapt my eslint config to match.

  3. marco-ippolito commented on Jul 25, 2024

    @marco-ippolito
    MemberAuthor

    No support forTypeScript features that require transformation (Enums, namespace, etc...).

    Is there a definitive list of features that are not supported here? I'm looking to adapt my eslint config to match.

    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

  4. jsejcksn commented on Jul 26, 2024

    @jsejcksn

    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.

  5. shawnmcknight commented on Jul 26, 2024

    @shawnmcknight
    • Enable running TypeScript files in node_modules

    Caution

    Currently there is no consensus whether Node.js should run TypeScript files inside `node_modules. Currently it is not supported to avoid package maintainers to release TS only package. I'm personally in favor of supporting it.

    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 pnpm will make links for internal workspace protocol packages as node_modules references in the dependent package. If node_modules support 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.

  6. jakebailey commented on Jul 26, 2024

    @jakebailey
    Member

    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.

    I would think that this detail wouldn't matter because the realpath of these paths would leave node_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.

  7. shawnmcknight commented on Jul 26, 2024

    @shawnmcknight

    I would think that this detail wouldn't matter because the realpath of these paths would leave node_modules

    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. 👍

    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.

    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 exports and/or main properties in package.json are referencing the .ts files in the source. This allows for internal type checking to use the dependencies' .ts files and for ts-node to operate against the source file even in the dependencies. At build time, the exports and/or main properties are converted from src/*.ts to dist/*.js so 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 realpath comment above that this might just work without any additional overhead.

  8. Daniel15 commented on Jul 27, 2024

    @Daniel15

    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-types flag to include typescript or ts in its name?

  9. added a commit that references this issue on Jul 28, 2024
  10. alshdavid commented on Aug 13, 2024

    @alshdavid

    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 .ts extension to be supplied in the import specifier. The issue is that there is no easy way to transpile imports with .ts extensions 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?

  11. marco-ippolito commented on Aug 14, 2024

    @marco-ippolito
    MemberAuthor

    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

  12. 27 remaining items

  13. karlhorky commented on Oct 13, 2024

    @karlhorky

    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_modules should 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:

    Publishing .ts files 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/*/*.ts in 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-types was 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/*/*.ts files.

    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:

    If we support a file type, we support it everywhere. Just as module detection needs to apply within node_modules so that a package that works locally continues to work when published to npm, the same applies to TypeScript. We shouldn't be limiting our functionality to try to encourage best practices.

    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/*/*.ts to 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/*/*.ts opponents such as Matteo are important and should be considered.

    But it feels like also:

    1. Each problem should be carefully thought through, examined, clearly enumerated and documented (also asked for by Geoffrey)
    2. Once clearly enumerated and documented, solutions should be also thought through, with each solution's tradeoffs being also enumerated and documented

    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/*/*.ts banning with a flag, eg:

    node --experimental-strip-types-in-dependencies
    
  14. karlhorky commented on Oct 13, 2024

    @karlhorky

    Edit: Copied to dedicated issue nodejs/typescript#14

    6) Similarities to .ts Rewriting Limitation

    Oh one more thing, more related of a feeling than fact-based:

    This node_modules/*/*.ts banning feels like it has a similar vibe to the .ts -> .js rewriting decision in TypeScript, which was overturned after years of doubling-down on .js imports (ironically heavily inspired by Node.js type stripping).

  15. robpalme commented on Oct 13, 2024

    @robpalme

    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-types yet, 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.

  16. karlhorky commented on Oct 13, 2024

    @karlhorky

    @robpalme thanks for the feedback! Copied to a dedicated issue here and edited my posts above:

  17. alshdavid commented on Oct 14, 2024

    @alshdavid

    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 --loader loaders from the parent process. This is a problem I am currently facing

  18. allisonkarlitskaya commented on Oct 14, 2024

    @allisonkarlitskaya

    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-types yet, so your real-life experience is useful to hear.

    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 tsx in 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.

  19. marco-ippolito commented on Oct 14, 2024

    @marco-ippolito
    MemberAuthor

    @allisonkarlitskaya feedback is being received through issues/discussion is the nodejs/typescript repo

  20. thernstig commented on Oct 28, 2024

    @thernstig

    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?

  21. marco-ippolito commented on Jan 3, 2025

    @marco-ippolito
    MemberAuthor

    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.

  22. akirarika commented on Mar 26, 2025

    @akirarika

    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

  23. karlhorky commented on Mar 26, 2025

    @karlhorky

    @akirarika you may want to take a look at the proposal to allow TypeScript type stripping in node_modules/*/*.ts:

  24. w3nl commented on Mar 26, 2025

    @w3nl

    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.

  25. JakobJingleheimer commented on Mar 26, 2025

    @JakobJingleheimer
    Member

    @w3nl I think you'll find the type-stripping PRs, blog posts, Learn articles, etc helpful as those questions have been answered/addressed 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    typescriptDiscussions related to TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions