Running Single File TypeScript in 5.9 with ts-node and tsx
On July 31, 2025, Microsoft released the next version of TypeScript, v5.9 (click here for full release notes)
One of the changes that caught my attention is what the default tsconfig.json
looks like when you leverage npx tsc --init
.
Before 5.9, when you ran npx tsc --init
, you would get a default tsconfig.json
that had all the options outlined with most of them commented, but you could see what their default values would be.
What Changed?
In this release, two changes were made to the tsconfig.json
- Instead of all the options being shown, now a subset of commonly tweaked settings are generated.
- There's now a section of recommended settings that have values set.
Before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
|
After
I'm still working through my thoughts on these changes. On one hand, I like the fact that the tsconfig.json is much smaller now and that you can use VS Code to get intellisense on the other options. On the other, if you don't know what you're looking for, it was nice to just see what the options were (with their default values).
Exports Not Working
After setting up a new test application, I tried running my app with ts-node
, but got an error with one of my exports.
Huh, I'm not familiar with that setting, let's look into it.
At a high level, this setting makes sure that you're not accidentally mixing CommonJS syntax (i.e. using require
to access dependencies) with ESModule syntax (i.e. using import
to access dependencies).
Finding the Root Cause
Weird, I'm not aware that I'm using CommonJS syntax anywhere, so I wonder where that's coming from. Doing some digging, I find out that there's a type
setting you can specify for package.json
with the following docs:
Ah, yeah, that will do it. So the issue is that by default, when you generate a package.json file, it doesn't create a type
setting, so by default, it will be CommonJS.
However, with TypeScript 5.9, the default configuration is assuming ESModule and with the verbatimModuleSyntax
being set to true, this breaks our initial set-up scripts.
Migrating to ESModule
Given the docs, the first thing I tried was to set the type
setting in the package.json to module
.
This fixed my import and now my index file looks like this
At this point, I figured I was in a good spot, so I tried npx ts-node src/index.ts
again.
However, I was greeted with the following:
Fighting with ts-node
What in the world? I've never seen that error before, but doing some more digging into ts-node, it turns out that the issue is that ts-node doesn't know how to load ESModules by default, so you need to get an ESM specific loader to work. The docs mention a few different ways to work around that, so I started trying them out.
No dice, even with trying all of that (and even some help with AI), I still wasn't able to get the TypeScript file to load.
At the end of the day, if I wanted to stick with ts-node
, I had to make the following changes.
- Update the
package.json
to have a type ofcommonjs
- Update the
tsconfig.json
to setverbatimModuleSyntax
to be false - Update the import in
index
to beimport {add} from "./other"
This works, however, CommonJS is an old way of setting up files and I don't like that I had to turn off the verbatimModuleSyntax
setting.
An Alternative Solution with tsx
Doing more digging, I found an alternative tool to ts-node
, [tsx](https://tsx.is/)
which purports having a simpler configuration and better support for ESM. This package has been around for a while now, has good support, and is actively maintained, so let's give that a whirl.
After installing tsx
, I changed my package.json
back to having a type of module
and set verbatimModuleSyntax
back to its default setting of true.
Let's try to run the file
Hooray! I've never been happier to see basic addition working.Wrapping Up
I've been a big proponent of ts-node for a long-time now due to its easy-to-setup nature and how well it played with TypeScript out of the box. However, with the latest changes to TypeScript, I highly recommend using tsx
over ts-node
as it just seems to work without having to mess with a bunch of other settings. With the friction I ran into and the direction TypeScript is going, I'll be curious to see how ts-node evolves over time.