23 February 2024

Switching from ts-node to tsx

 Running server side typescript with ts-node

JavaScript is a fast changing ecosystem. With so many new and old kids on the block there  are so many options that having them play nicely together is a challenge.

We're working with node.js 18 at the server side and have moved to using TypeScript (instead of JavaScript) and ES modules (instead of CommonJS modules).

We're using ts-node to feed node TypeScript, and added extra options to our tsconfig.json for this:

{
"extends": "@tsconfig/node-lts/tsconfig.json",
"compilerOptions": {
"module": "ESNext",
},
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
}
}

We're using nodemon to restart node upon changes.  

This was giving us some problems when launching in debug mode, so we had to explictly pass the ES module loaders of node to nodemon. These are our commands in package.json

"scripts": {
"start": "nodemon index.ts",
"ts": "ts-node ",
"debug": "nodemon -x \"node --inspect --loader ts-node/esm \" "
}

For both npm run ts and npm run debug we're appending the ts file to be run.

We're testing with Jest and  again we needed some extra configuration to play nicely with Typescript and ES modules.

ts-node does not play nicely with Node.js 20

And then we moved to the Node.js 20 LTS release and things broke. It cannot even grok @tsconfig/node-lts/tsconfig.json (es2023 not supported).  You can get things going by also passing the esm loader when running the code normally, just as you do when you're debugging.

But really, not running with the LTS seemed a bit lame to me for a tool like ts-node. The 10.9 release dates from juli 2022, with only two bug fix releases since, and no sign of updates for new releases of underlying packages.

 Running server side typescript with tsx

So, we took a stab at tsx as an alternative for ts-node. If you look at the comparison between the two (by tsx, but still informational), you see that tsx has less downloads (6M/month vs 94M/month), has a larger footprint (10 MB vs 2MB), has less github stars (7k vs 12k).

Trying out tsx we could reduce tsconfig.json to sensible defaults

{
"extends": "@tsconfig/node-lts/tsconfig.json",
"compilerOptions": {
"outDir": "dist"
}
}

With this setup we are importing files with a .js extension, even if we are developing with .ts extensions. We need to avoid picking up previously generated .js files is compiling with tsc, so we are directing compiled files to a dist directory.

Annoying downside of tsx is that its name overlaps with the .tsx file extension (React typescript file), which gives you false positives when googling.

Monitoring changes

Also, we did not need nodemon anymore, as tsx comes with a watcher. Our scripts commands in package.json have simplified to

"scripts": {
"start": "tsx watch index.ts",
"ts": "tsx ",
"debug": "tsx --inspect-brk "
},

I have not tried out (Jest) testing yet, but as for now, tsx looks like the way to go!