I'm having enormous trouble constructing a project using ts-node and ESM. There are several other questions on this topic, but I've tried solutions from all of them and gotten nowhere. It is very difficult to tell what information is out of date, with most answers involving adding additional options into the config which don't seem to make any difference in my case.
Here's the demo setup:
Node v20.4
Package.json has type: "module"
As the start script I'm currently using npx ts-node-esm src/index.ts
ts-node and Typescript are both installed locally to the project
tsconfig.json as follows:
{
"compilerOptions": {
"target": "ESNext",
"baseUrl": ".",
"esModuleInterop": true,
"moduleResolution": "NodeNext",
"module": "ESNext",
},
"types": ["node"]
}
And our demo files - src/index.ts:
import { sayHi } from "src/helpers.js";
sayHi();
And src/helpers.ts:
export function sayHi() { console.log('hi!') }
So far the error I am currently facing upon run is Unknown file extension ".ts" for src\index.ts
(I have learned that 'tsx' is likely to just work, but if there is misconfiguration on my part going on here, I would like to understand what is going wrong.)
Aside from the actual problem at hand, I also wouldn't mind some broader advice. Is this actually sensible? Everything I have read suggests new projects should be using ESM as there is no particular advantage to CommonJs (providing you can make ESM work...). Meanwhile the functionality of ts-node (or similar) seems pretty important for its role in achieving a quick development feedback loop. Given these two facts, I'm surprised I'm having so much trouble setting this up.
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
I also made a tutorial showing each step: