ยท 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
- Handling File and Folder Paths
- Using New File Extensions
- Demo Code
- Fixing ERR_UNKNOWN_FILE_EXTENSION
- Video Tutorial
Making TypeScript Work with ESM
Once you've installed ts-node
with npm install
, you can immediately direct it to your script by:
Please note that we are using ts-node-esm
instead of ts-node
to leverage its ESM loading capabilities.
This requires marking your Node.js package as an ECMAScript module. You can achieve this by using specific file extensions (as discussed later in the article) or by setting "type": "module"
in your package.json
file.
Additionally, ensure your tsconfig.json
file has the module
option set to Node16
(for module resolution introduced in Node v16) or NodeNext
(for experimental module resolution).
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 executed as follows:
Fixing ERR_UNKNOWN_FILE_EXTENSION
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 address this shortcoming, we can use TypeScript's compiler for type checking. To use TypeScript's compiler solely for type checking, enable the noEmit
flag to prevent JavaScript output. The JavaScript output will be generated by ts-node
:
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:
We can fine-tune our workflow by disabling type checking in ts-node
, as it's already handled by tsc
. Normally, we would do this as follows:
Unfortunately, we cannot simply pass the --transpileOnly flag to node
. When doing so, we will encounter this error:
node: bad option: --transpileOnly
In such cases, we can utilize the environment variables provided for each CLI option:
Video Tutorial
For a full explanation, check out the video I made here: