Node.js 16 was recently released bringing updated support for ES2021 and stable support for ES modules, how can TypeScript be configured to output JavaScript code that takes advantage of all these new features?
1 Answers
As of Node.js 16.0.0
, 100% of ES2021 is supported, and support for ES Modules is stable! If you know that you are targeting that version or newer, the optimal config would look like this:
"module": "ES2022"
&"moduleResolution": "node"
Node.js 16 support loading modules instead of the old CommonJS format, we do have to tell TypeScript that we are using Node.js's rules for resolving modules.
ES2022
is used here since Node.js 16 has support for top-levelawait
."allowSyntheticDefaultImports": true
To provide backwards compatibility, Node.js allows you to import CommonJS packages with a default import. This flag tells TypeScript that it's okay to use
import
on CommonJS modules."target": "ES2021"
This tells TypeScript that it's okay to output JavaScript syntax with features from ES2021. In practice, this means that it will e.g. output logical assignment operators & async/await syntax instead of embedding a polyfill.
"lib": ["ES2021"]
This tells TypeScript that it's okay to use functions and properties introduced in ES2021 or earlier. In practice, this means that you can use e.g.
Promise.any
andString.prototype.replaceAll
.
The full config would thus be:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"lib": ["ES2021"],
"module": "ES2022",
"moduleResolution": "node",
"target": "ES2021"
}
}
In addition to this, we also need to tell Node.js to treat .js
files in this project as ES Modules. The reason for this is that Node.js had to maintain backwards compatibility with code written for older Node.js versions. This can be done by adding "type": "module"
to your package.json
:
{
"type": "module"
}
Another change if you are coming from an earlier version of Node.js is that the file extensions when importing files are now mandatory. This means that you must write out .js
at the end of your local imports. Note that this is .js
even though you are importing a TypeScript file that actually has the file extension .ts
. This might seem a bit confusing but this comment from one of the TS contributors explains why that is.
Here are some examples of how to write your import
statements:
// Built-in Node.js modules
import { readFileSync } from 'fs'
// CommonJS packages from Npm
import md5File from 'md5-file'
// The local file "a.ts"
import { a } from './a.js'
If you want to stick with CommonJS for now, to avoid the caveats explained above, you can use the following config:
{
"compilerOptions": {
"lib": ["ES2021"],
"module": "CommonJS",
"target": "ES2021"
}
}
If you are running Node.js 18 you can see my similar answer for Node.js 18 here
If you are running Node.js 14 you can see my similar answer for Node.js 14 here
If you are running Node.js 12 you can see my similar answer for Node.js 12 here
If you are running Node.js 10 you can see my similar answer for Node.js 10 here
If you are running Node.js 8 you can see my similar answer for Node.js 8 here

- 23,234
- 15
- 74
- 89
-
Two important notes: You may need to include the `package.json` in your build files if your build files are in a different folder than your source files. Also, you can use `experimental-specifier-resolution=node` to avoid importing with explicit file extensions (`.js`) – Andy Nov 22 '21 at 01:09
-
to further expand on "--experimental-specifier-resolution=node" this is used as a node parameter when starting your node process. ALSO it only appears to apply to "relative imports" (those starting with / or ./ or ../) So if you are using tsconfig paths, you will need an additional build step. – redevill May 12 '22 at 19:30
-
Kindly requesting the next episode of this answer for Node.js 18 :) – Eddie R May 25 '22 at 13:40
-
3@EddieR ask and ye shall receive! https://stackoverflow.com/a/72380008/148072 – Linus Unnebäck May 25 '22 at 15:00
-
I've noticed that in tsconfig/bases they use `"module":"es2022"` instead (https://github.com/tsconfig/bases/blob/main/bases/node16-strictest-esm.combined.json) – user3125367 Sep 02 '22 at 19:46
-
@user3125367 at this point it isn't obvious what the difference is: https://github.com/microsoft/TypeScript-Website/issues/2492. Will update when there is more information available... – Linus Unnebäck Sep 06 '22 at 22:00
-
Found this section with more links which explains main differences (top level await), still have to check if that’s true, will report back soon: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#module-es2022 – user3125367 Sep 08 '22 at 00:13
-
@user3125367 Great find! I'll update the answer – Linus Unnebäck Sep 08 '22 at 09:55
-
How about `"moduleResolution": "node16"`? – akostadinov Apr 26 '23 at 09:19