Skip to content

Transpile TypeScript code inside node_modules. #58429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
arthurfiorette opened this issue May 23, 2025 · 12 comments
Open

Transpile TypeScript code inside node_modules. #58429

arthurfiorette opened this issue May 23, 2025 · 12 comments
Labels
feature request Issues that request new features to be added to Node.js. strip-types Issues or PRs related to strip-types support

Comments

@arthurfiorette
Copy link

arthurfiorette commented May 23, 2025

What is the problem this feature will solve?


I do not propose allowing transforms for all packages in node_modules. That would encourage publishing TypeScript directly to npm, which comes with significant downsides for the ecosystem.


With the introduction of --experimental-strip-types and --experimental-transform-types, it is now feasible to run TypeScript code directly without a separate build step.

However, in monorepo environments, this feature is limited by how Node.js currently handles node_modules resolution. In a typical monorepo setup, you might have a structure like:

graph TD
  libA --> server
  libB --> server
  libC --> server
  libD --> libB
Loading

Tools like pnpm (and similarly yarn and npm workspaces) install local packages via symlinks into the node_modules folder. For example, if server depends on libA, pnpm will symlink (or copy, depending on configuration) libA into server/node_modules/libA.

The issue is that Node.js currently does not apply --experimental-strip-types or --experimental-transform-types to anything inside a node_modules folder, even if it’s a symlink to a local workspace package.

This forces developers to pre-transpile any local dependencies, which essentially defeats the purpose of the feature in monorepos, where the server package often represents only a small percentage of the codebase compared to its locally installed dependencies.

This might introduce some complexity to the current implementation, since each package could have its own tsconfig.json with potentially different options. That said, I’m not deeply familiar with the internals of --experimental-strip-types and --experimental-transform-types, so this is just an assumption.

Bun and Deno come to mind as the major runtimes that support TypeScript out of the box. A study might be needed to determine how to handle tsconfig resolution when transpiling packages inside node_modules. Should it respect each subpackage's own tsconfig? Or should it transpile everything according to the server’s tsconfig? I’m also unsure what the best or most correct approach would be here.


What is the feature you are proposing to solve the problem?

I'm still unsure what the best decision would be here, as there are multiple ways to distinguish which folders inside node_modules come from local/owned packages and which do not. What about private registries? There's still a lot to decide.


What alternatives have you considered?

Some options have come to mind:

  1. Transpile all packages in node_modules that have "private": true. This ensures those packages did not come from a public registry.
  2. Add a flag like --transpile-packages=@mycompany/* to explicitly opt into transforming specific scoped packages.
  3. Automatically transpile all symlinked packages.
  4. Require each package that needs to be transpiled to include a field like "source": "./src/index.ts" or a custom flag such as "needsTranspilation": true.

A flag like the latter would be especially useful, as it would enable users to publish TypeScript packages to private or internal registries while still opting into transformation behavior.

Since this restriction was intentionally designed to prevent publishing uncompiled TypeScript to npm, perhaps a more consistent approach would be to only block transpilation for packages that clearly come from the public registry. Again, there's a lot to discuss here...

@arthurfiorette arthurfiorette added the feature request Issues that request new features to be added to Node.js. label May 23, 2025
@github-project-automation github-project-automation bot moved this to Awaiting Triage in Node.js feature requests May 23, 2025
@ljharb
Copy link
Member

ljharb commented May 23, 2025

I don’t think there’s any reliable way to ensure people don’t publish untranspiled code except never having this feature by default - people will find workarounds if they need to. Postinstall scripts can add private true, or use symlinks, or make any change that would distinguish - it’s just not an achievable guarantee.

@arthurfiorette
Copy link
Author

arthurfiorette commented May 23, 2025

Postinstall scripts can add private true, or use symlinks, or make any change that would distinguish - it’s just not an achievable guarantee.

That same argument applies to publishing untranspiled TypeScript to npm today, there’s nothing preventing someone from doing it now, even without this feature.

While npm may have become a de facto standard and can enforce certain norms within its ecosystem, Node.js itself cannot (and arguably should not) enforce the same rules across all private or self-hosted registries. In short, preventing misuse entirely isn’t a realistic goal. Instead, Node.js should aim to enable powerful, ergonomic workflows for legitimate use cases, like monorepos, while still discouraging misuse through clear defaults and documentation.

@aduh95
Copy link
Contributor

aduh95 commented May 23, 2025

FYI this was already suggested (and rejected) in #57215.

@bakkot
Copy link
Contributor

bakkot commented May 23, 2025

Hm. #57215 links to #55385, which says

I said this on nodejs/typescript#14 (comment), but I don't understand the use case here; if you have a package like this, you can't have published it because it's marked private. How did you get the package, then? For monorepos, I would really expect that you'd get it via a symlink, in which case the realpath of the files will not prevent the transform from happening.

But this thread implies that packages that resolve via a symlink in node_modules to something outside node_modules are not being transformed, contrary to my understanding of the above comment. What's the intended behavior for that case?

@slagiewka
Copy link

slagiewka commented May 24, 2025

I have a monorepo that supports both tsc-ed code on prod and node's type stripping in canary/local. Getting both to work required a workaround on my end.

  1. Adjust all the local package.jsons that need to be imported with:
{
  "exports": {
    "import": "index.js",
    "importts": "index.ts"
  }
}
  1. Run node with --conditions=importts when using type stripping.

Is it pretty? No.
Does it work? Yes.

The issue is that Node.js currently does not apply --experimental-strip-types or --experimental-transform-types to anything inside a node_modules folder, even if it’s a symlink to a local workspace package.

Given the above, I don't think this is true 🙈

@arthurfiorette
Copy link
Author

@slagiewka that's quite interesting... I wasn't aware this was possible at all. I wonder if Node's maintainers were also aware of it or if they will take any actions against it.

If this is possible today, why not just document this interesting behavior as something like:

{
  "exports": {
    "import": "index.js",
    "typescript": "index.ts"
  }
}
node -C typescript

@marco-ippolito
Copy link
Member

marco-ippolito commented May 26, 2025

This use case is documented in Amaro
https://github.com/nodejs/amaro.
I think it should also be documented in Node, PR welcome.
Pinging @nodejs/typescript for visibility
(This does not allow to run in node_modules anyways)

@marco-ippolito marco-ippolito added the strip-types Issues or PRs related to strip-types support label May 26, 2025
@jakebailey
Copy link

@jakebailey
Copy link

That being said, I'm not sure why the realpath-ing is not working in this case, unless I'm not understanding the problem. Would need a real repo for an example.

@marco-ippolito
Copy link
Member

marco-ippolito commented May 26, 2025

That being said, I'm not sure why the realpath-ing is not working in this case, unless I'm not understanding the problem. Would need a real repo for an example.

It's not bypassing the node_modules restriction afaik. Realpath-ing should be working as expected.

@muzuiget
Copy link

muzuiget commented May 27, 2025

Node.js: developers are discouraged to upload TypeScript to npmjs.com.

But,

npmjs.com: developers are free to upload their favorite movies.

The .ts files are MPEG transport stream files 😆


Actually node_modules can contain arbitrary files and come from anywhere, file system symlinks, private registry, not just public packages from npmjs.com.

If a library's downstream developers really want TypeScript code, re-transpiled with their favorite bundler, then the library's developer does have reason to upload TypeScript to npmjs.com.

I also don't understand why Node.js insists on this limitation.

@aduh95
Copy link
Contributor

aduh95 commented May 27, 2025

I also don't understand why Node.js insists on this limitation.

You say that when you just gave an example of why we cannot assume we'd be able to transpile .ts files. Note that as far as Node.js is concerned, publishing source files is perfectly fine, as long as there's also a transpiled version alongside.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Issues that request new features to be added to Node.js. strip-types Issues or PRs related to strip-types support
Projects
Status: Awaiting Triage
Development

No branches or pull requests

8 participants