node
node copied to clipboard
Remove --experimental-loader ExperimentalWarning as the option doesn't exist anymore / is no longer experimental
Version
21.4.0
Platform
MacOS Sonoma
Subsystem
No response
What steps will reproduce the bug?
Using --loader
(not --experimental-loader
)
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
This feature is clearly no longer experimental (ESM has been out for years, is the default for many new projects, and the option is no longer --experimental-loader
but --loader
). Since it generates an ExperimentalWarning
and specifically logs --experimental-loader
I'm almost certain this is just an oversight.
What do you see instead?
(node:82007) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("./loader.js", pathToFileURL("./"));'
Additional information
Was previously discussed in #30213 and there were plans to merge a fix for this in 2019, but seems the discussion was closed and died out. Opening a new issue for visibility (and also since the error/basis is slightly different).
https://github.com/nodejs/node/issues/30213 is about --experimental-modules
, not --experimental-loader
– --experimental-modules
was the flag used to enable ESM at the time it was still experimental. ESM has since been unflagged, and is no longer experimental.
On the other hand, --experimental-loader
is a different flag, still very much experimental. The --loader
alias is undocumented, and not recommended for use. As the warning suggests, it's unclear whether this flag is going to stay for much longer, as it stands it seems that Module.register
covers all of the use cases (but it's not a done deal, if someone brings a good use case for --experimental-loader
, it can stay).
While #30213 was opened for experimental modules it seems it became inclusive of experimental loaders midway through, but can understand that sentiment has shifted since then.
I'd defend not removing the --loader
flag altogether very, very strongly as it's still widely used by TypeScript projects and is the most viable way to use ts-node
, esm
, and Node 20+ in combination (as these do not normally play well together). I'm aware ts-node
is a disjoint project outside the purview of Node.js, but it would still impact a large portion of the community. Some issue tracking here: https://github.com/TypeStrong/ts-node/issues/1997
Instead, as Module.register
moves to stable, could --loader
be accelerated directly to legacy? module.register
is a better alternative to --loader
, but there are not currently better alternatives using module.register
to popular loaders using --loader
. Allowing the two to coexist for some time would allow the community to shim their way to parity.
but there are not currently better alternatives using
module.register
to popular loaders using--loader
What about the flag suggested in the warning though, the one you pasted above? It should be a drop-in replacement.
@aduh95, can you please elaborate on how to apply suggested solution? I do not understand where I should put these line.
Currently I am starting my project as node --loader ts-node/esm ./src/index.ts
Warning is the same, nodejs 20, module == moduleResolution == "Node16", no frontend
As the warning suggests, you should replace the --loader ts-node/esm
flag with the one suggested in the warning, and the warning will disappear. FWIW there's an open issue on the ts-node repo to allow users to use a simpler syntax: https://github.com/TypeStrong/ts-node/issues/2072
@aduh95
as it stands it seems that
Module.register
covers all of the use cases (but it's not a done deal, if someone brings a good use case for--experimental-loader
, it can stay).
What about the flag suggested in the warning though, the one you pasted above? It should be a drop-in replacement.
The implementation suggested in the warning is more than just a flag, it's an entire inline script:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("./loader.js", pathToFileURL("./"));'
This is unergonomic and reduces the readability of package scripts by pulling implementation details into what should be a high-level orchestration of commands.
The alternative of moving the inline script into an actual file indeed improves the readability of package scripts, at the expense of creating an unnecessary boilerplate file which will likely be duplicated across many projects.
Several issues have been opened related to this matter, and the typical suggestion of stabilizing the --loader
flag and removing the warning solves this common use case in a more elegant and concise way than either of the aforementioned workarounds.
Several issues have been opened related to this matter
If you want to make a stronger case, I'd suggest linking to those issues.
The alternative of moving the inline script into an actual file indeed improves the readability of package scripts, at the expense of creating an unnecessary boilerplate file which will likely be duplicated across many projects.
I think the idea is that most packages that want to hook into the module resolution would probably need communication with the main thread – and so, it would not be an "unnecessary boilerplate file", but a file containing the main thread logic as well as the actual Module.register
call and setting up the communication channel. (N.B. it's still possible to do cross-thread communication with --loader
, but with a worse DX.)
If your use case is a loader that doesn't need any cross-thread communication, I totally understand that it's frustrating not to use the --experimental-loader
flag (in fact, Node.js tests contains a bunch of tests that are still using that flag, because it's simply more convenient indeed). But how niche is that use-case? I don't know, if that's you, you should definitely speak up so @nodejs/loaders are aware of it.
Update:
Just use tsx
command and simplify everything, it's also faster: tsx file.ts
VScode launch.json:
{
"name": "TS file",
"type": "node",
"request": "launch",
"console": "integratedTerminal",
"localRoot": "${workspaceFolder}/c",
"skipFiles": ["<node_internals>/**", "node_modules/**"],
"runtimeArgs": [ "--import", "tsx" ],
"args": ["${file}"]
}
Old answer:
node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));' ./path/to/your_file.ts
if you want to add into VSCODE's launch.json
for debugging, you can use:
{
"version": "0.2.0",
"configurations": [
{
"name": "run selected TS file",
"type": "node",
"request": "launch",
"localRoot": "${workspaceFolder}",
"runtimeArgs": [
"--import",
"data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));"
],
"args": ["${file}"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "node_modules/**"],
}
]
}
The --loader
/--experimental-loader
flag
NODE_OPTIONS="--experimental-loader ts-node/esm" npx webpack configtest
offers a solution
ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
To make the command more ergonomic, create a file that registers module resolution hooks. Let’s name the file register.js
.
import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
register('ts-node/esm', pathToFileURL('./'));
Then use the --import
flag to import the file.
NODE_OPTIONS="--import=./register.js" npx webpack configtest
Node.js v20.9, v20.10, v20.11 (LTS: Iron).
I'd like to mention here that the "getting started" documentation page Node.js with Typescript simply says:
Since Node.js v19.0.0, you can use a custom loader. Download a loader such as ts-node or tsx or nodejs-loaders First you need to install the loader:
npm i -D ts-node
Then you can run your TypeScript code like this:node --loader=ts-node/esm example.ts
No mention of this being experimental or planned for removal. So it doesn't seem quite the case as @aduh95 was saying earlier in the thread:
The
--loader
alias is undocumented, and not recommended for use.
Is this just a documentation bug that should be fixed?
I also see the --loader
option in node --help
. (On Node v22.0.0)
Is this just a documentation bug that should be fixed?
Yes, the documentation should be using --import
instead. I posted https://github.com/TypeStrong/ts-node/issues/1909#issuecomment-2089155346 to encourage ts-node
to update.
I also see the
--loader
option innode –help
. (On Node v22.0.0)
Technically it exists, even though it was created by accident. I still think we’re likely to remove it in the future, but until we do, node --help
will probably list it as it would be incorrect to leave out a flag that’s supported.
One thing I ran into was that you can't use quotes if you set the --import
flag in node_options
under .npmrc
. I was eventually able to get it working after url-encoding the whole string, doesn't quite seem like a bug, but it's quite a pain:
node-options=--import data:text/javascript,import%20%7B%20register%20%7D%20from%20%22node%3Amodule%22%3Bimport%20%7B%20pathToFileURL%20%7D%20from%20%22node%3Aurl%22%3Bregister%28%22ts-node%2Fesm%22%2C%20pathToFileURL%28%22.%2F%22%29%29%3B
if this benefits others, i advise to try tsx
instead of ts-node
. Either run directly tsx file.ts
or with '--import', 'tsx'
arguments.
if this benefits others, i advise to try
tsx
instead ofts-node
. Either run directlytsx file.ts
or with'--import', 'tsx'
arguments. If this would be of benefit to others, I'd recommend tryingtsx
instead ofts-node
. Runtsx file.ts
directly or use'--import', 'tsx'
parameters.
Thank you for sharing, let me know there is a better tsx
For anyone using node with Typescript, I used this solution in your package.json
"scripts": {
"dev": "nodemon --watch src --ext ts --exec \"tsc --noemit && node --import=./src/dev/register.js src/app.ts\"",
}
with @edukisto 's solution above for register.js
. You'll need to make sure you've got the path right - put mine in ./src/dev
. Also check your app's entry point.
This will launch your app with nodemon and watch for changes to your src folder.
This is what worked for me:
- I created register-ts-node.js from @edukisto commen above.
- I set the dev script in my package-config.js: "dev": "cross-env NODE_ENV=development NODE_OPTIONS=--import=./register-ts-node.js node --experimental-specifier-resolution=node server.ts"