TypeStrong / ts-node

TypeScript execution and REPL for node.js
https://typestrong.org/ts-node
MIT License
12.89k stars 535 forks source link

ERR_UNKNOWN_FILE_EXTENSION on Node v20.0.0 #1997

Open septs opened 1 year ago

septs commented 1 year ago

Search Terms

Node, ERR_UNKNOWN_FILE_EXTENSION

Expected Behavior

Fix it

Actual Behavior

see Minimal reproduction

Steps to reproduce the problem

see Minimal reproduction

Minimal reproduction

$ cat example.ts
console.log('example')
$ cat tsconfig.json
{ "ts-node": { "esm": true } }
$ npx ts-node example.ts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/septs/Projects/example/example.ts
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at nextLoad (node:internal/modules/esm/hooks:781:28)
    at load (/Users/septs/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/dist/child/child-loader.js:19:122)
    at nextLoad (node:internal/modules/esm/hooks:781:28)
    at Hooks.load (node:internal/modules/esm/hooks:381:26)
    at handleMessage (node:internal/modules/esm/worker:153:24)
    at checkForMessages (node:internal/modules/esm/worker:102:28) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

Specifications

septs commented 1 year ago

https://github.com/microsoft/TypeScript/issues/53922


Edited by 11/18/2023

Use tsx replace ts-node?

Or use ts-node@beta?

more see https://github.com/privatenumber/tsx, https://github.com/TypeStrong/ts-node/issues/2077

csvn commented 1 year ago

I have the same issue. I tried using --esm, { "esm": true } and --loader="ts-node/register", but got the ERR_UNKNOWN_FILE_EXTENSION no matter what I tried. Could not get ts-node to run on Node v20 with native ESM modules.

Jack-Works commented 1 year ago

FYI Node 20 has made a breaking change:

Custom ESM loader hooks run on dedicated thread ESM hooks supplied via loaders (--experimental-loader=foo.mjs) now run in a dedicated thread, isolated from the main thread. This provides a separate scope for loaders and ensures no cross-contamination between loaders and application code.

https://nodejs.org/en/blog/release/v20.0.0

RobinTail commented 1 year ago

+1

The issue also appears when using the specially dedicated ESM runner

$ ts-node-esm file.ts
manuth commented 1 year ago

I went forward and downgraded my node version to lts. However, doing node --loader ts-node/esm {file} did work for me.

wibed commented 1 year ago

i expirience the same issue on:

# node --version
v18.16.0

EDIT: my bad, i misinterpreted the gravity of the situation. manually enforcing the loader like this.

{
  "start": "npm run clean && cross-env NODE_ENV=development NODE_OPTIONS=\"--loader=ts-node/esm --trace-warnings\" webpack serve --mode=development --config webpack.config.ts",
}

worked out.

example sourced from here: https://github.com/webpack/webpack-cli/issues/2458

RandivCosta commented 1 year ago

i expirience the same issue on: Node v20, I tried: $ node --loader ts-node/esm ./path It worked for me but getting this warnning: ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time

sywesk commented 1 year ago

I'd like to add quickly that on windows (at least), doing node --loader ts-node/esm ./path works but with an insanely high CPU usage. Doing a quick npx tsc + node ./build/app.js works perfectly without the performance issue.

RobinTail commented 1 year ago

Yes, node --no-warnings=ExperimentalWarning --loader ts-node/esm file.ts helps to bypass the issue until it's fixed.

Aenigma commented 1 year ago

yeah, I recently upgraded to node 20 and started running with the --loader ts-node/esm parameter but noticed that my memory usage in my application had skyrocketed to 32G before it would die from OOM. I went chasing for a cause because I assumed it was due to a change I made, but downgrading to node 19 lowered memory usage down to around 500M again. I assume this is probably related to the CPU issues @sywesk had.

So I'd recommend people stay off Node 20 until this is fixed if you don't want to have unexpected issues in your runtime.

Maxim-Mazurok commented 1 year ago

Can't use esm:

node --loader ts-node/esm ./src/tools/spell-check.ts
(node:18954) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
file:///home/maxim/MyProject/src/tools/spell-check.ts:39
Object.defineProperty(exports, "__esModule", { value: true });
                      ^

ReferenceError: exports is not defined in ES module scope
    at file:///home/maxim/WebWatcher/Frontend/src/tools/spell-check.ts:39:23
    at ModuleJob.run (node:internal/modules/esm/module_job:192:25)

Node.js v20.2.0
dandv commented 1 year ago

I'm getting the same error as @Maxim-Mazurok when running a file that only does console.log(). I'm using the node20 Docker image:

docker run -it --name node20 -v "$PWD":/usr/src/myapp -w /usr/src/myapp node /bin/bash
# cat test.ts
console.log('hi');
root@12071c96b5fc:/usr/src/myapp# node --loader=ts-node/esm test.ts
(node:101) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
file:///usr/src/myapp/test.ts:2
Object.defineProperty(exports, "__esModule", { value: true });
                      ^

ReferenceError: exports is not defined in ES module scope
    at file:///usr/src/myapp/test.ts:2:23
    at ModuleJob.run (node:internal/modules/esm/module_job:192:25)

Node.js v20.2.0
root@12071c96b5fc:/usr/src/myapp# node --version
v20.2.0
root@12071c96b5fc:/usr/src/myapp# npm ls ts-node
ts-code-samples@ /usr/src/myapp
`-- ts-node@10.9.1
erictheswift commented 1 year ago

Yes, node --no-warnings=ExperimentalWarning --loader ts-node/esm file.ts helps to bypass the issue until it's fixed. @RobinTail

Note — by using --no-warnings=ExperimentalWarning you're suppressing all node warnings because --no-warnings flag does not handle a value. --no-warnings works the same. https://nodejs.org/api/cli.html#--no-warnings

manuth commented 1 year ago

@Maxim-Mazurok, @dandv Are you sure you flagged your files as ESM properly? This can either be done by renaming the files to .mts or by having the package.json file corresponding to the source file containing "type": "module"

Maxim-Mazurok commented 1 year ago

@Maxim-Mazurok, @dandv Are you sure you flagged your files as ESM properly? This can either be done by renaming the files to .mts or by having the package.json file corresponding to the source file containing "type": "module"

I use CJS, and have type: commonjs in my package.json.

The following ended up working for me: tsconfig.node.json:

{
  "extends": "@tsconfig/node18/tsconfig.json",
  "include": [
    "vite.config.*",
    "vitest.config.*",
    "cypress.config.*",
    "playwright.config.*",
    "orval.config.*",
    "src/tools/**/*"
  ],
  "compilerOptions": {
    "composite": true,
    "types": ["node"]
  }
}

(inherited "module": "Node16", "target": "es2022")

package.json scripts:

"ts-node-tool": "cross-env TS_NODE_PROJECT=\"tsconfig.node.json\" ts-node",
"my-tool": "npm run ts-node-tool ./src/tools/my-tool.ts"

Of course, can't use top-level await and had to use tsimportlib to import esm deps, might find more on that in https://github.com/Maxim-Mazurok/esm-in-cjs-ts-demo

Unsubscribing for now as I'm no longer blocked and happy with a solution. Hope it helps someone.

AlexandreFournier commented 1 year ago

When will you release a version that fixes this issue ? Last ts-node version is more than one year old :(

danieltroger commented 1 year ago

Just chiming in here regarding the OOM issue: I believe it's FUD today and you're safe to update and use the workarounds detailed in this thread. The OOM issue with node on macOS was fixed in 20.1.0, see https://github.com/nodejs/node/issues/47761#issuecomment-1533793099

adam-nielsen commented 1 year ago

One problem with the workarounds is that they hide any typos you have in your code. Like this isn't very helpful:

$ node --loader ts-node/esm example.ts 

node:internal/process/esm_loader:46
      internalBinding('errors').triggerUncaughtException(
                                ^
[Object: null prototype] {
  [Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]]
}

Node.js v20.4.0

Yet if you feed the same file into tsc it gives you a much more useful error:

$ npx tsc
example.ts:71:1 - error TS2304: Cannot find name 'make_an_error_happen'.

71 make_an_error_happen
   ~~~~~~~~~~~~~~~~~~~~
Found 1 error in example.ts:71
floatrx commented 1 year ago

👋 I deleted the *lock files with node_modules and reinstalled the dependencies. In my case, it helped me!

paxperscientiam commented 1 year ago

I have the same issue. I tried using --esm, { "esm": true } and --loader="ts-node/register", but got the ERR_UNKNOWN_FILE_EXTENSION no matter what I tried. Could not get ts-node to run on Node v20 with native ESM modules.

Same problem here. Fortunately, I don't need 20, so I just downgraded to v19.9 and it works. EG: ts-node --esm index.ts

Context: using vitejs to build typescript projects.

manuth commented 1 year ago

I have the same issue. I tried using --esm, { "esm": true } and --loader="ts-node/register", but got the ERR_UNKNOWN_FILE_EXTENSION no matter what I tried. Could not get ts-node to run on Node v20 with native ESM modules.

Same problem here. Fortunately, I don't need 20, so I just downgraded to v19.9 and it works. EG: ts-node --esm index.ts

Context: using vitejs to build typescript projects.

Are you sure this is the command you executed? --loader="ts-node/register" is not correct. For ESM support, you need to pass --loader="ts-node/esm"

thesmart commented 1 year ago

I also have run into this issue w/ Node v20. The workaround to run node --loader ts-node/esm runs, but the line numbers in Error stack traces are incorrect. I think this must be related to the breaking changes, and some impact on source maps.

Here is what my (incorrect) stack trace looks like using the workaround in Node v20:

$ node -r ts-node/transpilers/swc --loader ts-node/esm --no-warnings --test ./api/src/shared/socket-set.test.ts:
  TypeError [Error]: Cannot read properties of undefined (reading 'once')
      at SocketSet.add (file:///Users/blah/blah/api/src/shared/socket-set.ts:47:20)

Here is what my (correct) stack trace looks like using the workaround in Node v19:

$ node -r ts-node/transpilers/swc --loader ts-node/esm --no-warnings --test ./api/src/shared/socket-set.test.ts:
  TypeError: "Cannot read properties of undefined (reading 'once')"
      at SocketSet.add (file:///Users/blah/blah/api/src/shared/socket-set.ts:58:14)
thesmart commented 1 year ago

I tried out tsx to see if it also has issues with Node v20 and it does.

Node v20 line numbers seem to be based on single-line transpiled code:

$ node --loader tsx --no-warnings --test ./api/src/shared/socket-set.test.ts:
  TypeError [Error]: Cannot read properties of undefined (reading 'once')
      at SocketSet.add (file:///Users/blah/blah/api/src/shared/socket-set.ts:1:970)

Node v18 produces correct line numbers:

 $ node --loader tsx --no-warnings --test ./api/src/shared/socket-set.test.ts:
   TypeError [Error]: Cannot read properties of undefined (reading 'once')
      at SocketSet.add (file:///Users/blah/blah/api/src/shared/socket-set.ts:62:14)
ayonli commented 1 year ago

I encountered this issue when trying to run mocha tests with native ES modules, after reviewing a lot of suggestions above, searching a lot of web pages, and doing a lot of try-outs, I finally got it worked.

Here is what I do:

  1. set these compiler options options in tsconfig.json
{
    "compilerOptions": {
        "module": "esnext", // of course
        "noEmit": true,
        "allowImportingTsExtensions": true, // This is optional, but since I use this style, I need to turn it on.
        "esModuleInterop": true, //This is required since some of the dependencies are CommonJS packages.
        "moduleResolution": "NodeNext", // required
        // ...
    }
}
  1. set type: module in package.json (this is the key setting), since I'm coding a dual module system package, I wrote two scripts to temporarily change this setting during the test.
// pretest.cjs
const fs = require("fs");
const pkg = require("./package.json");

pkg.type = "module";

fs.writeFileSync("./package.json", JSON.stringify(pkg, null, "    ") + "\n", "utf8");
// posttest.cjs
const fs = require("fs");
const pkg = require("./package.json");

delete pkg.type;

fs.writeFileSync("./package.json", JSON.stringify(pkg, null, "    "), "utf8");
  1. Change the test command to this:
{
    "script": {
        "pretest": "node pretest.cjs",
        "posttest": "node posttest.cjs",
        // use `node --loader=ts-node/esm mocha` in stead of `mocha -r ts-node/esm`
        "test": "node --no-warnings=ExperimentalWarning --loader=ts-node/esm ./node_modules/mocha/bin/mocha *.test.ts"
    }
}

This configuration allows me to test from Node.js v14 (TypeScript 5.x demands) to v20.

Hope this helps others, especially for doing mocha tests.

rosmcmahon commented 1 year ago

issue remains unresolved, and is now present in NodeJS v21.0.0 also

atomicptr commented 1 year ago

Don't know if this is an acceptable work around for most people here but we switched to tsx https://github.com/esbuild-kit/tsx for the time being as upgrading to Node v20 (the now current LTS) was important to us.

eldare commented 1 year ago

@atomicptr I was able to avoid tsx with the following work around:

package.json

{
    "type": "module",
    "scripts": {
        "start": "node --loader ts-node/esm src/app.ts",
        "test": "c8 --reporter=html node --loader=ts-node/esm node_modules/mocha/lib/cli/cli.js  --grep '' 'tests/**/*.ts'"
    },
    // dependencies
}

I've included the testing part as well, because it was a real pain in the ass to setup it up for a project that is ESM only, since jest and nyc don't work well with ESM only projects but mocha and c8 worked great!

tsconfig.json

{
    "compilerOptions": {
        "target": "es6",
        "module": "NodeNext",
        "outDir": "dist",
        "rootDir": "src",
        "strict": true,
        "esModuleInterop": true,
        "moduleResolution": "NodeNext",
        "sourceMap": true,
        "removeComments": true
    },
    "include": [
        "src"
    ],
    "ts-node": {
        "esm": true
    }
}

and from command like, since I can't just run ts-node, I had to alias it: alias tn="node --loader ts-node/esm"

I'm relatively new to nodejs and typescript, so if you noticed something weird feel free to suggest improvements.

kamaslau commented 1 year ago

The problem still remains in Node.js v20.8.1,and the work arround node --loader ts-node/esm src/app.ts could work, with the following warning outputs:

(node:42788) 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("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
nickserv commented 12 months ago

tsx also doesn't properly support Node 20+ IME, and has performance issues.

eldare commented 12 months ago

@kamaslau I see it too, but I don't see any side effect from this warning. Everything works great. And since TypeScript is for development only, I can live with a small warning.

pfusik commented 12 months ago

This issue is open for half a year now and I wonder what is its status? We have Node 21 now and the issue is still reproducible with ts-node 11 beta. The workaround of --loader ts-node/esm results in an ugly warning that cannot be silenced, except together with all Node warnings. tsx is not an option for me as it doesn't perform any type-checking.

jsaraiva commented 12 months ago

I've been using bun (https://bun.sh) lately to replace ts-node and tsx, and it seems to be working quite well for my purposes. Maybe it can help some folks here.

danieltroger commented 12 months ago

This issue is open for half a year now and I wonder what is its status? We have Node 21 now and the issue is still reproducible with ts-node 11 beta. The workaround of --loader ts-node/esm results in an ugly warning that cannot be silenced, except together with all Node warnings. tsx is not an option for me as it doesn't perform any type-checking.

There's a much better (no warnings) and more performant drop-in alternative IMO, which is: https://github.com/swc-project/swc-node

To use: yarn add @swc-node/register @swc/core

And then to run typescript just do node --loader @swc-node/register/esm index.ts

nickserv commented 12 months ago

--loader is still deprecated.

danieltroger commented 12 months ago

@nickmccurdy Why do you think that? It doesn't print a warning for me with swc-node. Also I think it's a new feature, would be weird if it was deprecated already

daniel@mmmmmmmmmm test2 % yarn node --loader @swc-node/register/esm test.ts
hi
manuth commented 12 months ago

@nickmccurdy Why do you think that? It doesn't print a warning for me with swc-node. Also I think it's a new feature, would be weird if it was deprecated already

daniel@mmmmmmmmmm test2 % yarn node --loader @swc-node/register/esm test.ts
hi

It's not exactly deprecated. But looking at this, it looks like the Node.js team discourages devs to use this flag: https://nodejs.org/api/cli.html#--experimental-loadermodule

yarobash commented 11 months ago

Had the same issue, finally with lot of pain got to strange workaround: yarn pnpify node --loader ts-node/esm src/index.ts Using node 21.0.0 and @yarn/pnpify in package.json

sleep-written commented 11 months ago

I made a library to execute projects with path-mapping: @bleed-believer/path-alias and works with node 20. This library uses ts-node as dependency, and uses ts-node loaders only when your execution calls a file inside of your source files. If you want to try:

aelassas commented 11 months ago

The following workaround I found works without warnings (but needs Node >=20):

  1. Create a new file register-hooks.js:
    
    import url from 'node:url'
    import { register } from 'node:module'

const filename = url.fileURLToPath(import.meta.url) register('ts-node/esm', url.pathToFileURL(filename))

2. Use the following command:
```shell
node --import ./register-hooks.js ./src

If you're using imports without file extensions, you need to add --experimental-specifier-resolution=node:

node --experimental-specifier-resolution=node --import ./register-hooks.js ./src

Otherwise the following workaround works on Node >=20 and Node 18:

node --experimental-loader=ts-node/esm  ./src
bennycode commented 11 months ago

Thanks to the community for creating awareness for this issue. I was just wondering why my app on Heroku is suddenly crashing and then noticed that it is running on Node v20 as it is Heroku's default since 31st October.

Switching from ts-node-esm to node --no-warnings --loader ts-node/esm solved the issue for me. 👍

Okpainmo commented 11 months ago

Thanks to the community for creating awareness for this issue. I was just wondering why my app on Heroku is suddenly crashing and then noticed that it is running on Node v20 as it is Heroku's default since 31st October.

Switching from ts-node-esm to node --no-warnings --loader ts-node/esm solved the issue for me. 👍

This works for me.

node --no-warnings --loader ts-node/esm --watch ./src/app.ts

midzdotdev commented 11 months ago

Also for whoever finds this useful, you can use this as a shebang too for scripts in the package.json bin field:

#!/usr/bin/env node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm

I also have { "type": "module" } although I'm not 100% if it's needed.

Worth noting that using node ... --loader approach described above, the ts-node config in tsconfig.json isn't respected so you need to specify everything as flags to node.

tchakabam commented 10 months ago

My advice: reconsider the need of ESM modules for your app itfp. None of this helped here for me. But if you are just doing this to be able to have await at top level (my case), consider wrapping it with something like

(async function main() { /**/ })()

which will work perfectly fine in CJS mode, while avoid the need of module type package/target, ESM etc at ll :)

noahsark769 commented 10 months ago

Seeing this on node 18.19.0 as well. Downgrading to 18.18.2 worked for us

uriva commented 10 months ago

this made ts-node unusable for me for a long time, I moved to tsx, it was easy and works well.

davidlj95 commented 10 months ago

I was using Module type overrides to force for ESM given I can't set type: module in package.json (and prefer to avoid using mts extension*

* given some tools don't automatically recognize as Typescript files. Like Prettier plugin for WebStorm

And workarounds provided don't allow those overrides to work.

Eventually, switched to tsm. No need to use mts extension (as when using module overrides). Worked like a charm 🎉

For a specific script, I had to use tsx as @uriva suggested. Plus set the extension of the file to run (which was ESM) to mts. Probably because it's importing CJS modules at some point. Not sure TBH

pfusik commented 10 months ago

With so many recommendations of tsx, I'd like to remind that tsx simply ignores all the type information.

DASPRiD commented 10 months ago

With so many recommendations of tsx, I'd like to remind that tsx simply ignores all the type information.

Just like ts-node in SWC mode, which which is preferred for development anyway. Type-checking can still be in CI and in the build process.

rcbevans commented 10 months ago

Switching to TSX worked out of the box, so will be switching to that.

rosmcmahon commented 10 months ago

cc @cspotcode