NOTE FROM AUTHOR: "The date that this answer was published is different than the date that the answer was originally created on. Due to changes in TypeScript, I have had to write a new answer twice. Because dates are extremely relevant in this post, I have included them in a couple places, in-case the answer gets stale. This will help readers know when I was referencing certain versions of any technologies that I have written about below."
Originally Authored in December of 2021
The "es-module-specifier" Flag Fix for TS, ESM, NODE stacks
ESM was not an easy technology to incorporate into Node. And when it came to writing Node ES-Modules in the TypeScript language, well, that actually looked like, at a certain point in time, that it could never happen. IMO, it seemed like certain projects, didn't want to do what they, now have done, to support the Standard in the Node Back-end RTE. Anyhow, lets move on...
Initially when I answered this question I suggested that people who are experiencing this issue use the following flag:
(FYI: The solution came from the link attached to the snippet)
The flag worked, or worked as the best solution up to this point (or well up to TS 4.7 Nightly Release). IT SHOULD BE NOTED That depending on the environment, and the project, that you are working in, this will still be the best solution for now.
However, a Better Solution has been Made Available.
At this Point in Time Much has Changed:
During the Nightly releases of v4.6 TypeScript added support for new module & moduleResolution specifiers, which are ("obviously") set in the tsconfig.json
. They were released during the v4.6
nightly builds, just a little while before v4.6 beta was released, and 4.7 became the nightly build. Now, v4.7
is beta, and v4.8
is the new nightly build (I mention all this version stuff, just to make sure your up to date).
These are the new specifiers/settings that TS has given to us
"module": "NodeNext"
"moduleResolution": "NodeNext"
The reason I say this is because, the new TSC-flags (aka tsconfig settings) were available in v4.6
, which, if you remember, I stated a couple paragraphs ago, however; TypeScript decided not to release them in the current latest version (typescript@latest
) version which is v4.6
. So, they will be included in TypeScript officially, when v4.7
is no longer in beta, and becomes the latest version.
They should always be used with "esModuleInterop": true
, (unless somethings change on the "Node.js" side of things. The "esModuleInterop" setting eases support for ESM, and some of the node modules will need it to work properly with the new ESM import/export module system.
This is the ESM tsconfig.*.json
configuration template, which I add as a starting point to each new project.
"compilerOptions": {
// Obviously you need to define your file-system
"rootDir": "source",
"outDir": "build",
// THE SETTINGS BELOW DEFINE FEATURE SUPPORT, MODULE SUPPORT, AND ES-SYNTAX SUPPORT
"lib": ["ESNext"],
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true, // Eases ESM support
"types": ["node"],
"allowSyntheticDefaultImports": true,
}
Written on: MAY 22nd, 2022 8:50am PST
Gaining Support for the module
& moduleResolution
Specifiers
So you have a couple different TS versions you can use to support the new configurations. You can either use Beta or Nightly. Below is a chart that demonstrates the 3 available versions. As of right now, typescript@latest
is @ v4.6
, and does not support the new configurations.
TS RELEASE |
VERSION |
NPM COMMAND TO EXECUTE |
HAS SUPPORT? |
TS Latest |
v4.6 |
npm i -D typescript@latest |
NO |
TS Beta |
v4.7 |
npm i -D typescript@beta |
YES |
TS Nightly |
v4.8 |
npm i -D typescript@next |
YES |
Configure your Development Environment to use the Latest Ver
You need to configure your environment to use the new TypeScript version. I obviously cannot document ever editor's configuration, but V.S. Code seems to be the most common for TypeScript, and V.S. 2022 uses a similar configuration.
In VS Code, you will add the following configuration to your workspace ./.vscode/settings.json
file.
// "./.vscode/settings.json"
{ "typescript.tsdk": "./node_modules/typescript/lib",}
Configuring Your Runtime Environment
(Which, for this answer, is Node.js)
But why is this so? Why do I need to configure my RTE? Whats the deal??? Doesn't the RTE know everything?
- No, the RTE doesn't just automatically know everything. It has to have some sort of instructions, or clues.
**But didn't we already Configure the tsconfig.json
file?
- Yes, but that is for TypeScript, now we need to make sure node is ready for our TS configuration.
We need to tell node that we are going to be executing an ECMAScript Module, and not a CommonJS module. We can do this one of two different ways.
- The first is to configure our
package.json
file using the package.json
configuration file's "type" field.
// @file "package.json"
{
"name": "project-name",
"version: "2.4.16",
"type": "module", // <-- Lets env know this project is an ESM
// the rest of your package.json file configuration...
}
Alternatively, you can do the some thing as above, but by using the --input-type "module"
flag
Lastly, you can do, what I prefer to do, and just create each file using the ESM file extensions. In js it is .ejs
, and in typescript it is .ets
.
For Example, your index file will be index.ets
and TSC will emit index.ejs
.
Lastly, its IMPORTANT that you understand how imports work when writing an "ES-Module". I will list some notes, I think that will be the best format for this info.
You have to use JavaScript file extensions when importing files.
ESM imports are imported using URI's, and not by use of "possix filepaths". The opposite was true for CJS Modules.
Special chars must be percent-encoded, such as # with %23 and ? with %3F.
TypeScript won't make you prepend the MimeType, but ESM uses prepended data types so its a best practice to do so.
For example, its a best practice to import from Node API's & Libraries using the following URI format:
import EventEmitter from 'node:events';
const eEmit = new EventEmitter();