-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Description
What is the problem this feature will solve?
The ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING
arises if you import a TypeScript file from another package. I understood this is to discourage having TypeScript-only packages. However, this also applies to private packages, where the organization decides whether a TS-only ecosystem fits for them. This is particularly annoying in monorepos, where imports are local, because it forces you to build the changed package before using it instead of changes just propagating naturally.
What is the feature you are proposing to solve the problem?
We could have an opt-in flag, environment variable, or package.json
field that enables type stripping in node_modules
. In this way, we keep discouraging TypeScript-only public packages, while allowing their usage in contexts where it makes sense.
What alternatives have you considered?
-
pnpm Workspaces: In a monorepo, pnpm symlinks local packages, so development works fine. But when building an artifact (like a container image),
pnpm prune
copies local packages intonode_modules
, forcing you to copy the entire monorepo—which increases the artifact size. -
Turbo.build: Turbo lets you selectively copy only the necessary packages, avoiding the need to hardcode local packages in
node_modules
. The trade-off is that it introduces extra tooling and only works in monorepos; using local packages from cloned repositories still poses challenges (TS in development vs. JS in builds). -
File Watcher: A file watcher can rebuild changed dependencies and restart the server on file changes. However, this requires complex logic for monitoring files and managing dependencies.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Activity
aduh95 commentedon Feb 26, 2025
You can use a loader/module hook to workaround that. /cc @JakobJingleheimer
JakobJingleheimer commentedon Feb 26, 2025
I believe this was specifically addressed, the package just needs
"private": true
to be set in itspackage.json
.Yes, you can use a loader like
@nodejs-loaders/tsx
. It's pretty trivial to set up 🙂marco-ippolito commentedon Feb 26, 2025
I think a flag to enable it would be reasonable
ljharb commentedon Feb 27, 2025
For private true only packages, this seems fine to me; bit i don’t think node should ever provide anything that directly allows stripping types from published packages (not counting the tools so a loader can be easily built)
marco-ippolito commentedon Feb 27, 2025
I created a PR that read the package.json private property. But private apparently does not allow publishing at all
ljharb commentedon Feb 27, 2025
Correct, that’s why it’s a viable escape hatch - because nothing can ever be published with private true, so the only way to have one of those in node_modules is if it’s bundled or linked in.
aduh95 commentedon Feb 27, 2025
… on the public registry of npm, I’m not sure it makes sense to rely on it and assume it’s always going to be the case
ljharb commentedon Feb 27, 2025
Certainly an unknown npm client, or an unknown private registry, could violate that rule (all known ones comply with it afaik), but the concern is package ecosystem leakage, and anything in those buckets isn't part of the package ecosystem.
aduh95 commentedon Feb 27, 2025
Node.js doesn't read the
private
property at all atm IIRC, it would feel a bit too magical to start now, I'd much prefer a flag – or even keep loaders as the only way to get this to work, the loader itself can choose to read theprivate
field or notmarco-ippolito commentedon Feb 27, 2025
Id create a flag and keep it opt in forever.
ljharb commentedon Feb 27, 2025
As long as the flag didn't work in NODE_OPTIONS or any committable config file, that'd be fine - otherwise, it would still cause ecosystem leakage since package authors who want to ship TS directly will just tell people to commit that config somewhere.
19 remaining items
JakobJingleheimer commentedon Feb 28, 2025
Loaders should not be used in production because you pay the cost every time. That makes sense in development where files are changing every time. For production, they do not change at all, so you should instead pre-compile with a tool like esbuild.
You should also not use them in production because many of them provide only an inexpensive facsimile that is sufficient in their target environment. For example,
nodejs-loaders/css-module
provides a simple key-value pair of css identifiers like you would see used in jsx, and does not expose aCSSStyleSheet
that you would need in production for a browser to render the page. The cost difference is huge. Again, esbuild for production.That's not what that text means, but sure. I'm not sure whether amaro/strip will go into node_modules though (I haven't checked, but I expect it to have the same restriction as node itself because it's the tool mode is using to do this internally).
Because you need to do different things. Unfortunate to have to pay the hard disk space twice.
daquinoaldo commentedon Feb 28, 2025
It does, see daquinoaldo/pnpm-monorepo-type-stripping#1. The reason is that Node.js type stripping is a wrapper around
amaro
(ref), but the check aboutnode_modules
is in the wrapper, not inamaro
(see #57215 (comment)).Sorry, @JakobJingleheimer, I'm not very knowledgeable on loaders. Can you explain why I need to do different things?
The
--import 'amaro/strip'
directive seems to replicateimport("amaro/strip")
(#43942), which executes a registration of the sametransformSync
that Node.js uses. It's not clear to me the difference between that register and how Node.js is using it for type stripping. The core function is the same, though.JakobJingleheimer commentedon Feb 28, 2025
Ah, good :) By the way, the code in node you cited is not where the check is. It's here:
node/lib/internal/modules/typescript.js
Lines 169 to 171 in 269c851
Sure! I'm happy to improve the documentation. Please let me know (perhaps a DM so we avoid cluttering this issue) what parts are unclear or need expanding 🙂 I could potentially also write up a Learn article if it's more about consumption.
daquinoaldo commentedon Feb 28, 2025
I'm trying to use
esbuild
in daquinoaldo/pnpm-monorepo-type-stripping#2, but it breaks on some dependencies, recommending to list them as external.I worked it around for direct dependencies of my app, but it still breaks on dependencies of other private packages in the workspace. This would imply having to manually list all problematic dependencies recursively, which is a very annoying operation.
If we apply the same workaround in private packages, we would need to bundle them first and then the final app (which I think is not the purpose of the bundler), but at this point, it is the same DevX as compiling TypeScript (e.g., you needTurbo.build or similar tooling).
Why is running Node.js type stripping or a loader in production discouraged? From @GeoffreyBooth's comment, I understood that the overhead of type stripping is minimal.
JakobJingleheimer commentedon Feb 28, 2025
The problems you're encountering with esbuild are likely due to misconfiguration and/or misconsumption. I have almost never needed to mark a dependency as "external" for esbuild (basically, only for OS-specific packages). Esbuild is probably not recommending you mark them as external (I have never seen it do that), but rather suggesting that may be appropriate (it probably isn't).
Loaders in general are a development API, not a production API. Type stripping is relatively cheap but not free, and the only reason to pay¹ that cost in production is laziness 😜 (we're all as lazy as we can be, but this is not the place to be lazy). So amaro is just as inappropriate for production as nodejs-loaders 😉
Amaro
may well suit your development needs better thannodejs-loaders/tsx
.¹ it will literally cost you money
paulovieira commentedon Mar 1, 2025
Great discussion, I learned a lot with this issue. My conclusion is that typescript inside
node_modules
is discouraged and a loader should be used instead.Just wanted to bring attention to an alternative type-stripping compiler that doesn't seem to be much talked about: https://github.com/bloomberg/ts-blank-space
This thing works great and can also be used as a loader. A quick test:
One interesting thing about
ts-blank-space
is that it depends only on the typescript compiler, unlike other similar tools (which require esbuild, biome, etc). So if you like simplicity this might be a good choice.ljharb commentedon Mar 1, 2025
@paulovieira pretty sure ts-blank-space is what node uses, via amaro.
jakebailey commentedon Mar 1, 2025
Amaro is just SWC with some options set, and that's bundled with Node (but also on npm). ts-blank-space has the same idea (replacing types with spaces + ASI fixups etc) but using TS's APIs, largely for simplicity but also because it plugs well into Bloomberg's build system where they need the ASTs anyway. But they are different.
(I think ts-blank-space was being worked on before strip types?)
ljharb commentedon Mar 1, 2025
ah, fair - but i'm still pretty sure that SWC's implementation was inspired by ts-blank-space.
paulovieira commentedon Mar 1, 2025
Yes, I think ts-blank-space was the first one with this approach (released to the public in september/2024) and SWC quickly followed. This thread has some context: https://x.com/acutmore/status/1836762324452975021
Another interesting thing: this benckmark by @acutmore shows it is 2x faster than SWC/wasm (which is what
amaro
is currently using?).marco-ippolito commentedon Mar 4, 2025
Closing as not planned for now
fend25 commentedon Mar 4, 2025
The problem is: what if the module is included as "file:..."?
For monorepos, it's a catch-22: the
imports
section allows TypeScript files, but it can't reference files located above the current directory, while thedependencies
section can reference files outside of./
, but it can't import TS files.stagas commentedon Mar 4, 2025
Simply remove the arbitrary node_modules check or provide an option. There's no reason to have it as node can now read .ts files.
Currently I can't modularize my vite configs and plugins or anything because I have to introduce a build step.
private
won't work because I am publishing to my local npm registry (verdaccio).A build step breaks functionality such as editor goto symbol and clicking on stack traces and test links to the files, as they point to the dist instead.
TSX and loaders don't work as Vite spawns its own instance. Bun doesn't work either.
The only solution that works if I want to use Typescript AND be able to use the above features is to vendor everything in one giant monorepo and copy everything every time i want to replicate. Loaders/bun don't work either with vite. Obviously this workflow is broken, but less than having broken stack traces or not be able to navigate to source code symbols from the editor.
What is the point of having the stripping types feature if it's not going to work after you extract something to its own module?