ยท hands on
Write a simple TypeScript script with ESM
I recently wrote a small TypeScript script to generate a Markdown file with a sluggified filename. Since we're now in the era of modern ECMAScript Modules (ESM), I wanted to use this new module system in my TypeScript code. Here's how I did it.
I installed ts-node so I could run my script in a Node.js environment from the command line. The great thing about ts-node
is that it works well with ESM. It comes with a binary called ts-node-esm
that supports loading ECMAScript modules when you run TypeScript code.
Contents
Making TypeScript Work with ESM
Once you've installed ts-node
with npm install
, you can immediately direct it to your script by:
This ensures that an ECMAScript loader is ready when your code is executed.
Handling File and Folder Paths
In the world of ESM, the usual __filename
and __dirname
constants are not available. So, I found a way to mimic this by using the import.meta property:
Using New File Extensions
TypeScript lets you pick different file extensions to tell whether you're using ES modules (.mts
) or CommonJS modules (.cts
).
If you only need to run a single script with the ES module syntax, I recommend using the .mts
extension. It's great in scenarios where you don't want to set up your whole project to use ES modules.
Demo Code
Here's the complete script I developed:
It can be ran as follows:
There is the possibility that this error occurs when using Node.js v20:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for src/start.mts
This is because ts-node v10.9.2 doesn't work well with Node.js v20 and ECMAScript modules (see GitHub issue).
Luckily, there is a solution. You just have to make use of the --loader
flag of Node.js:
Unfortunately, this workaround doesn't provide type checking as it is broken with ts-node v10.9.2, so your error output will appear as follows:
node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
^
[Object: null prototype] {
[Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]]
}
To fix this, run type checking directly through TypeScript's compiler using the noEmit
flag to prevent unintentional output:
You may see an experimental warning because the --loader
flag is likely to be removed in future Node.js versions. To hide this message, use the following command:
For a full explanation, check out the video I made here: