TypeStrong/ts-node

ERR_UNKNOWN_FILE_EXTENSION on Node v20.0.0

septs opened this issue ยท 118 comments

septs commented

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

  • ts-node version: 10.9.1

  • node version: 20.0.0

  • TypeScript version: 5.0

  • tsconfig.json:

    { "ts-node": { "esm": true } }
  • package.json:

    {}
  • Operating system and version: macOS 13.3.1

septs commented

microsoft/TypeScript#53922


Edited by 11/18/2023

Use tsx replace ts-node?

Or use ts-node@beta?

more see https://github.com/privatenumber/tsx, #2077

csvn commented

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.

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

+1

The issue also appears when using the specially dedicated ESM runner

$ ts-node-esm file.ts
manuth commented

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

wibed commented

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:
webpack/webpack-cli#2458

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

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.

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

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.

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

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

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

@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, @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.

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

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 nodejs/node#47761 (comment)

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

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

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

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"

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)

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

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.

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

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

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

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)

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

eldare commented

@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

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.

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.

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

--loader is still deprecated.

@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

@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

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

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:

  • Install the package:

    npm i --save @bleed-believer/path-alias
  • Assuming your source code is inside ./src folder, simply execute (this will run using ts-node hooks):

    # If you want to use the integrated cli
    npx bb-path-alias ./src/index.ts
    
    # Or if you want to use the --import flag instead
    node --import @bleed-believer/path-alias ./src/index.ts
  • Assuming your transpiled code is inside ./dist folder, simply execute (this will not use the ts-node hooks):

    # If you want to use the integrated cli
    npx bb-path-alias ./dist/index.js
    
    # Or if you want to use the --import flag instead
    node --import @bleed-believer/path-alias ./dist/index.js

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))
  1. Use the following command:
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

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. ๐Ÿ‘

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

  • the "--watch" flag for live reload in dev mode(only in nodeJs 18.XX - I guess 18.11.0) and above

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.

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 :)

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

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

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

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

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.

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

I also get this error when using node v18.19.0, but works fine on v18.18.2

I also get this error when using node v18.19.0, but works fine on v18.18.2

Yeah, this could be the same change affecting it: https://nodejs.org/en/blog/release/v18.19.0

Loaders now apply to subsequent loaders, for example: --experimental-loader ts-node --experimental-loader loader-written-in-typescript.

everybody here should try https://github.com/privatenumber/tsx. it just works.
it can be a drop-in replacement for ts-node

I made a ts-node wrapper to launch your projects in ESM with node v20 - v21: @bleed-believer/path-alias. If you want to try it, i'll use this tsconfig.json as example:

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        
        "outDir": "./dist",
        "rootDir": "./src"
    }
}

And simply you can run your project using this command (assuming your rootfile is ./src/index.ts):

node --import @bleed-believer/path-alias ./src/index.ts

Or if you want, you can use the cli command:

bb-path-alias ./src/index.ts

The ideal escenario is ts-node has these capabilities built-in, but because the actual case, I built this package. Please bring me feedback if you want to implove this.

For anyone facing a issue with getting node v19 to run your ts node server again.

I was facing the same issue after downloading a new package and couldn't figure out why all of a sudden my ts node server wouldn't start. I had first thought that it had something to do with upgrading to node v20 from v19. However, downgrading back to node v19 still didn't fix the issue with "Unknown file extension .ts" error. I had read online that maybe it had someting to do my with tsconfig.ts file. However, it was working perfectly fine with node v19. I was about to go the tsx route - https://www.npmjs.com/package/tsx until I tried running the cmd npx tsx app.ts and the terminal started that something was already running on port 3000. I then realize that I had accidentically open another terminal and tried running my app again, which is why I got the error "Unkwon file extension .ts".
Here's my tsconfig.ts file.

{
  "compilerOptions": {
    "target": "es2020", 
    "module": "NodeNext",   
    "moduleResolution": "NodeNext",      
    "sourceMap": true,
    "outDir": "./build",
    "strict": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true, 
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
  },
  "include": ["src/", "app.ts", "public/html/fileUpload.js"],
  "exclude": ["node_modules", "**/*.spec.ts"],
  "ts-node": {
    "esm": true
  }
}

Here's my package.json file

{
 "main": "app",
  "type": "module",
  "scripts": {
      "dev": "nodemon app.ts",
      "start": "node build/app.js,
      "build": "tsc"
    }.
 "dependencies": {
    "express": "^4.18.2"
   },
  "devDependencies": {
      "nodemon": "^3.0.1",
      "typescript": "^5.3.2"
    },
 "engines": {
    "npm": ">=9.0.0",
    "node": "18.17.0"
  }
}

To run app type - npm run dev

How I import file

Ex.
import auth from "src/auth/auth.js"

Create a file in root directory name .nmprc to force the use of correct nodejs version. Be sure to add the property in file.

engine-strict=true

Here's my file structure

  • Create a file in root directory as app.ts. Then create a folder in root directory with name src/app/. In src/app I have create d more .ts files and create subfolder that contain .ts files. REmember to import .ts with the .js extension.

Edit -
Since I have been downvoted with no explaination. I asume it was bc what I have stated is not working. I have added the engine field to package.json. This specifics the nodejs version that works for me without throwing the .ts config error and that works with the nodejs package sharp for image processing in my project. Not sure about all the details but I do know that nodemon does everything and more that ts-node would do. I use nodemon in dev to get auto recompile. I also added the start script and build script to my package.json file. In production your cloud server hoster should have in thier build process to run the build script in the package.json file using the command - npm run build and then to start the application with the comand - npm run start. If your wondering why the path is build/app in package.json. It's bc in the tsconfig file there's property named outDir that specified the output folder to build.

I Hope the further explainaiton helps. Happy new years everyone!๐ŸŽ‰

works for me:

node --no-warnings=ExperimentalWarning --experimental-specifier-resolution=node --loader ts-node/esm [file]

The only decent solution I found for this was to switch to tsx btw.

It's amazing that people prefer to switch to tsx, instead of getting a small one time pointless warning when they start node: #1997 (comment)

It's amazing that people prefer to switch to tsx, instead of getting a small one time pointless warning when they start node: #1997 (comment)

it's because tsconfig.json and ts-node simply doesn't work. You can follow all the instructions, it will work perhaps once you isolated everything but in real projects, it simply stops working due to all other unknown factors.

@1C0D Grow up.

@pencilcheck can you give me an example? I'm currently working on a large project that is ESM only, and haven't seen any unexpected issues.

It's amazing that people prefer to switch to tsx, instead of getting a small one time pointless warning when they start node: #1997 (comment)

Well, that was the first thing I tried. But it didn't work.

I think everything that needed to be said has already been posted in this thread.

The rest is "me too" for the 2 workarounds and irrelevant messages.

My main takeaway here is that ts-node is becoming unmaintained. Let's see what path the tools that rely on ts-node take to prevent also becoming irrelevant.

@1C0D And you are the expert apparently, using tsx when you don't have jsx, right?

Just to clear any confusions, tsx mentioned here is TypeScript Execute, not the file extension used in React projects.

I'm working on a massive codebase that just moved to Node 20 and we've had multiple scripts using ts-node end up with the issue described in this thread. The amount of config files required is hard to maintain. Changing one config leads to different errors. I tried tsx and, it just worked...

I don't understand all the details under the hood of these two packages, but moving everything over to tsx feels attractive at this point.

tsimp is also a neat alternative to ts-node. Works beautifully with newer versions of Node AND maintains strict consistency with TypeScript itself which tsx can't provide.

https://github.com/tapjs/tsimp

@1C0D Grow up.

@pencilcheck can you give me an example? I'm currently working on a large project that is ESM only, and haven't seen any unexpected issues.

I have a project right now, that have the two .ts files, one is executed with ts-node loader, and it seems to work, the other I wrote that imports "lodash" and that's it.

One without lodash works, the other with lodash simply error out. Now how do I make sure this works? to support both commonjs imports and esm imports with the subtlety of picking whether to transform the import or the files, or picking the right format at the file in node_modules is just impossible for me to understand.

All I have is to change either "type": "module" in package.json, (that will break stuff), or change tsconfig to include module, target, and compileOptions.

None of the changes I have will fix this, because tsconfig is just confusing and hard to isolate. (btw, while writing this message, I just switch to tsx, and it just works, no confusing error, but actual error in my code that I can use to fix and start running)

This is just one of an example. I have another project with expo, and I have a sever component. I'm using telefunc so I can unify server and expo code into one repo.

Expo uses a completely different bundler and compiler system than a normal typescript import system. If you setup the tsconfig for expo, you will not be able to make it work with server code that uses normal server code in ts.

Look, i'm not expert in node, I just use node so I can get on with my work. tsx just works, so I will continue to use tsx.

tsimp is also a neat alternative to ts-node. Works beautifully with newer versions of Node AND maintains strict consistency with TypeScript itself which tsx can't provide.

https://github.com/tapjs/tsimp

tsimp is not bad but not as simple and no config as tsx.

This is also happening to me on GitHub Actions with Node v18, with the following command:

npx --yes ts-node --transpileOnly --esm --experimental-specifier-resolution=node --files ./compile/index.ts

This same command/workflow was working fine a few months ago. Tried downgrading ts-node to 10.9.1 but no luck.

Edit: yeah, I tried tsx and it worked no problem. And btw it's not referring to using a .tsx extension like I thought at first glance, it's "TS Execute", a ts-node alternative. Also, the command ended up being a lot simpler, no flags needed: npx tsx ./compile/index.ts.

Given how fast things change in TS and Node, I feel like it's not a good sign that this repo is not very active. Not trying to cast blame; seems like it would be a nightmare to stay on top of this. But right now I'd have to recommend tsx and consider ts-node deprecated/abandoned. An issue this big and popular should've been patched within a few weeks.

It's amazing that people prefer to switch to tsx, instead of getting a small one time pointless warning when they start node: #1997 (comment)

@eldare Just to point out, a lot of people in this thread report high memory and cpu usage too. So I think that perhaps you might be missing something with the "pointless" warning.

I would personally have to get a fairly large concussion before I ever considered to run ts-node in prod. But for me a slow dev experience is undesirable, especially as jest requires ts-jest and ts-node, and running it is hella slow since Node 20 and hard to say with confidence what is the slow component in this mess.

it's because tsconfig.json and ts-node simply doesn't work. You can follow all the instructions, it will work perhaps once you isolated everything but in real projects, it simply stops working due to all other unknown factors.

@pencilcheck Have you tried putting ts-node specific compileroptions? I had to do that to make things work, sample:

{
  "extends": "../../tsconfig.development.json",
  "compilerOptions": {
      // For some reason, this doesn't work in TS 5.1.3, so I moved it here so ts-node works (it needs this)
      "typeRoots": ["../../shared/types"],
      "types": ["mappersmith"]
    }
  },
  "references": [{ "path": "../../shared" }]
}

I was able to make it work with the following command:

node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL(\"./\"));' bin/index.ts

It seems to me, that it's just the --esm flag that doesn't work any more with node >= 18.16.

Node.js v20.6.1

I am still looking for solutions.

still facing this issue

node v21.5.0 
ts-node 10.9.2
typescript 5.3.3

Please avoid posting +1s here. Reply if you have something to contribute like a related issue, a fix, or a suggested workaround.

@pencilcheck

Expo uses a completely different bundler and compiler system than a normal typescript import system. If you setup the tsconfig for expo, you will not be able to make it work with server code that uses normal server code in ts.

"Normal" typescript import system tries to emulate plethora module systems of all other bundlers and NodeJS ESM specifically, which is governed by tsconfig.json values. So it sounds like an expo problem, not a typescript or even ts-node one.
In fact it already struggles with ESM (note how a temp fix uses cjs module syntax):
expo/expo#23180

Look, i'm not expert in node, I just use node so I can get on with my work. tsx just works, so I will continue to use tsx.

It works because tsx completely skips the typechecking step. Meaning whatever problems with types are still dragged into runtime. Except the whole point of typescript is to have type-checking and compilation errors at build time, if you skip that part might as well not use typescript at all and spare yourself from all complexity it entails.

I stumbled upon TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" on a working project, after I added another npm package, namely serialize-error to be able to serialize Error types.
After using the sample code in combination with mocha I immediately got the UNKNOWN_FILE_EXTENSION.

Can somebody explain to me, how an additional typescript npm package may introduce this error in an already working typescript project? There was no other change to tsconfig, nor did I add any esm flags. I am staring at the callstack but it is totally unclear to me why by using another npm package "suddenly" a ts file gets passed to node.

(I am on node v18.16.1 with ts-node 10.9.2)

@GabenGar

It works because tsx completely skips the typechecking step. Meaning whatever problems with types are still dragged into runtime. Except the whole point of typescript is to have type-checking and compilation errors at build time, if you skip that part might as well not use typescript at all and spare yourself from all complexity it entails.

Using tsx doesn't necessarily mean that you will lose the benefits of types checking. Type checking is more of a build-time process, when we're using tsx or something alike (I use Vite), we can add an additional process (via tsc --noEmit [--watch]) to check the types at build-time, while letting tsx (or Vite) to just run the program (in runtime).

Although it now requires two processes in order to guarantee the program is coding and running properly, however, I find it even more convenient during development. When there are typing errors, the tsc process will report those errors in a separate window and doesn't jam with the runtime errors and logs.

Especially when I'm prototyping or refactoring, type errors are many. But since they are in the separate window, even the program fails and exits because of the wrong types, the tsc window will keep open and running. So I just keep fixing the type issues, after all is done, and rerun, the program will now work again.

So using the composition of tsc and tsx in the same project and separating build-time errors and runtime errors is actually a very good practice.

Not to forget that your IDE does type checking as well against your tsconfig. And anyone who uses ts-node with the --swc flag has the same "problem". Putting that in quotes since it is not really a problem at all. Your build pipeline will sitll use tsc in some way or another to check types, but hot reloads are so much faster in development.

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.

Yeah thats right use node v18.17.1
until the issue is solve in node 20

The best solution I found is to give up and switch to tsx - which, as an added bonus, is much faster too.

If you are using jest with ts-jest you also need to switch to babel before ts-node is completely scrubbed from your repo, and then this whole world of pain is but a memory and you can leave this thread and go on with your life ๐Ÿ‘

tsimp has typechecking by default. I believe it's the best alternative considering compatibility.

the best solution for me was:

install cross-env and tsx packages

package.json

{
  ...,
  "type": "module",
  "scripts": {
      "start": "cross-env NODE_OPTIONS='--import=tsx' webpack serve --env development --config webpack.config.ts"
  }
}

pay attention to the equal sign after --import, because all the examples I saw were without it and it caused an error

tsconfig.json doesn't matter

Node: 20.11.0
TypeScript: 5.3.3
cross-env: 7.0.3
tsx: 4.7.0

This works for me out of the box using tsx
npm i -D tsx
npx tsx src/index.ts

nodejs ็‰ˆๆœฌ้—ฎ้ข˜๏ผŒ้™ไฝŽๅˆฐ16.20.xๅณๅฏ

Use node --loader ts-node/esm to replace ts-node-esm as a temporary workaround. I hope ts-node or node could fix this issue in future.

There are so many comments here that I thought it may be useful to have a summary:

There are many incompatiblities with Node.js v20.11.0 (current LTS release) and ts-node v10.9.2 (current release). It comes down to the following:

When using Node.js 18 and CommonJS, you can use the following start scripts:

  • ts-node src/main.ts
  • node --require ts-node/register src/main.ts

When using Node.js 18 and ESM, you can use:

  • ts-node-esm src/main.ts
  • node --loader ts-node/esm src/main.ts
  • node --no-warnings=ExperimentalWarning --loader ts-node/esm src/main.ts (if you don't want to see experimental warnings)

When using Node.js 20 and ESM, you cannot use ts-node-esm anymore (it will show you ERR_UNKNOWN_FILE_EXTENSION). You have to use:

  • node --loader ts-node/esm src/main.ts

Unfortunately, this won't give you stacktraces anymore. So if there is a compiler error, you will only get to see:

internalBinding('errors').triggerUncaughtException

The TypeScript runner tsx won't help in this situation as it doesn't provide type checking capabilities.

You can work around this by using a combination of tsc and ts-node such as:

  • tsc --noEmit && node --loader ts-node/esm src/main.ts

Alternatively, you can use tsimp:

  • TSIMP_DIAG=error node --import=tsimp/import src/main.ts

I also made a tutorial showing each step:

Does anyone get error "Cannot use import statement outside a module" but already added type:module in package.json?
image

Have you tried adding the .js or .ts extension? test is not a file in this directory.

It is a pity we can not use ts-node with .ts files in "type": "module" projects

@vasily-mishanin we can. ๐Ÿ™‚ If you bait for an answer, here it is: you need to use --no-warnings=ExperimentalWarning --loader node_modules/ts-node/esm.mjs index.ts

In my case I use spawnSync to pass the execution down ts-node:

#! /usr/bin/env node
โ€ฆ
spawn(process.execPath, [ '--no-warnings=ExperimentalWarning', '--loader', path_loader, path_bin, ...argv ], { stdio: 'inherit' })

It is ordinary executable from outside, the package is type=module and I use TS for all the code.

I am getting below error @StreetStrider

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

I am using node latest LTS v20.11.1

@rsrahul-avataar don't know. Try to create simple setup first with simple index.ts. When it works apply solution to the real codebase.

This is how I use it with node 18 and 20 in NPM

"start": "node --import 'data:text/javascript,import{register}from\"node:module\";import{pathToFileURL}from\"node:url\";register(\"ts-node/esm\",pathToFileURL(\"./\"));' ./src/server.ts"

ugly, but works without warnings :)

There are so many comments here that I thought it may be useful to have a summary:่ฟ™้‡Œๆœ‰ๅพˆๅคš่ฏ„่ฎบ๏ผŒๆˆ‘่ฎคไธบๆ€ป็ป“ไธ€ไธ‹ๅฏ่ƒฝไผšๆœ‰็”จ๏ผš

There are many incompatiblities with Node.js v20.11.0 (current LTS release) and ts-node v10.9.2 (current release). It comes down to the following:ไธŽNode.js v20.11.0๏ผˆๅฝ“ๅ‰LTS็‰ˆๆœฌ๏ผ‰ๅ’Œts-node v10.9.2๏ผˆๅฝ“ๅ‰็‰ˆๆœฌ๏ผ‰ๆœ‰่ฎธๅคšไธๅ…ผๅฎนไน‹ๅค„ใ€‚ๅฎƒๅฝ’็ป“ไธบไปฅไธ‹ๅ‡ ็‚น๏ผš

When using Node.js 18 and CommonJS, you can use the following start scripts:ไฝฟ็”จNode.js 18ๅ’ŒCommonJSๆ—ถ๏ผŒๅฏไปฅไฝฟ็”จไปฅไธ‹ๅฏๅŠจ่„šๆœฌ๏ผš

  • ts-node src/main.ts
  • node --require ts-node/register src/main.ts

When using Node.js 18 and ESM, you can use:ไฝฟ็”จNode.js 18ๅ’ŒESMๆ—ถ๏ผŒๆ‚จๅฏไปฅไฝฟ็”จ๏ผš็”จ้€”๏ผš

  • ts-node-esm src/main.ts
  • node --loader ts-node/esm src/main.ts
  • node --no-warnings=ExperimentalWarning --loader ts-node/esm src/main.ts (if you don't want to see experimental warnings) node --no-warnings=ExperimentalWarning --loader ts-node/esm src/main.ts ๏ผˆๅฆ‚ๆžœๆ‚จไธๆƒณ็œ‹ๅˆฐๅฎž้ชŒ่ญฆๅ‘Š๏ผ‰

When using Node.js 20 and ESM, you cannot use ts-node-esm anymore (it will show you ERR_UNKNOWN_FILE_EXTENSION). You have to use:ๅฝ“ไฝฟ็”จNode.js 20ๅ’ŒESMๆ—ถ๏ผŒไฝ ไธ่ƒฝๅ†ไฝฟ็”จไบ†๏ผˆๅฎƒไผšๆ˜พ็คบ็ป™ไฝ ๏ผ‰ใ€‚ๆ‚จๅฟ…้กปไฝฟ็”จ๏ผš็”จ้€”๏ผš

  • node --loader ts-node/esm src/main.ts

Unfortunately, this won't give you stacktraces anymore. So if there is a compiler error, you will only get to see:ไธๅนธ็š„ๆ˜ฏ๏ผŒ่ฟ™ไธไผšๅ†็ป™ไฝ stacktraceไบ†ใ€‚ๆ‰€ไปฅๅฆ‚ๆžœๆœ‰็ผ–่ฏ‘ๅ™จ้”™่ฏฏ๏ผŒไฝ ๅช่ƒฝ็œ‹ๅˆฐ๏ผš

internalBinding('errors').triggerUncaughtExceptionInternalBinding(โ€˜errorsโ€™).triggerUncaughtException

The TypeScript runner tsx won't help in this situation as it doesn't provide type checking capabilities.็ฑปๅž‹่„šๆœฌ่ฟ่กŒๅ™จTSXๅœจ่ฟ™็งๆƒ…ๅ†ตไธ‹ๅธฎไธไธŠๅฟ™๏ผŒๅ› ไธบๅฎƒไธๆไพ›็ฑปๅž‹ๆฃ€ๆŸฅๅŠŸ่ƒฝใ€‚

You can work around this by using a combination of tsc and ts-node such as:ๆ‚จๅฏไปฅ้€š่ฟ‡็ป„ๅˆไฝฟ็”จ tsc ๅ’Œ ts-node ๆฅ่งฃๅ†ณๆญค้—ฎ้ข˜๏ผŒไพ‹ๅฆ‚๏ผš

  • tsc --noEmit && node --loader ts-node/esm src/main.ts

Alternatively, you can use tsimp:ๆˆ–่€…๏ผŒๆ‚จๅฏไปฅไฝฟ็”จtsimp๏ผš

  • TSIMP_DIAG=error node --import=tsimp/import src/main.ts

I also made a tutorial showing each step:ๆˆ‘่ฟ˜ๅˆถไฝœไบ†ไธ€ไธชๆ•™็จ‹๏ผŒๅฑ•็คบไบ†ๆฏไธ€ๆญฅ๏ผš

I used this method and eventually my project worked successfully in Node.js 20.

But what is confusing is that if I try to use ts-node-esm, then it will choose to use the globally installed ts-node, and then it will report an error "ERR_UNKNOWN_FILE_EXTENSION". If using node and specifying loader, the globally installed ts-node won't work again, it will report "ERR_MODULE_NOT_FOUND", and ts-node has to be installed in the project to work properly.

For some reason node 18.18.x does work, but node 18.19.x doesn't. I'm not sure what changed, it's weird to get a breaking change for this in a minor version.

zuk commented

For some reason node 18.18.x does work, but node 18.19.x doesn't. I'm not sure what changed, it's weird to get a breaking change for this in a minor version.

This seems to be a controversial topic in the node community, but they're saying it's because this has to do with the --loader flag, which was marked as experimental, and is therefore not subject to semantic versioning.

If you ask me, anyone still using node without typescript is nuts. So breaking the --loader flag โ€” especially without a clear update path โ€” has a bigger impact than the node folks are acknowledging.

@jishi

For some reason node 18.18.x does work, but node 18.19.x doesn't.

This is exactly what happened to me. I was very suprised that it happened in a minor as well. And node version was not the topmost reason I was expected to cause a break, firstly I blamed deps going broke, as usual. After some search I ended up here. It was not obvious at all.

We had been using ts-node with the --swc option when we ran into the issue on v18.19.0 from our nightly base image build. We were able to use @swc-node/register to use swc directly to get over this issue:

node -r @swc-node/register ./tools/our-app.ts

Just added a reference to @swc-node/register, removed ts-node, and removed the tsconfig.json (it worked with the default configuration).

Can anyone summarize when this problem will be fixed in the ts-node?

for NodeJS 20 this comment helped me:
#1997 (comment)

But any error logs are still shown as:

... [Object: null prototype] ...

And this makes development impossible