With Node.js bringing updated support for ES2020 and adding 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 14.0.0
, 100% of ES2020 is supported, and support for ES Modules has landed! If you know that you are targeting that version or newer, the optimal config would look like this:
"module": "ES2020"
&"moduleResolution": "node"
Node.js 14 support loading modules instead of the old CommonJS format, we do thave to tell TypeScript that we are using Node.js's rules for resolving modules.
"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": "ES2020"
This tells TypeScript that it's okay to output JavaScript syntax with features from ES2020. In practice, this means that it will e.g. output optional chaining operators & async/await syntax instead of embedding a polyfill.
"lib": ["ES2020"]
This tells TypeScript that it's okay to use functions and properties introduced in ES2020 or earlier. In practice, this means that you can use e.g.
Promise.allSettled
andBigInt
.
The full config would thus be:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"lib": ["ES2020"],
"module": "ES2020",
"moduleResolution": "node",
"target": "ES2020"
}
}
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 comming from an earlier version of Node.js is that the file extension 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 confisuing 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": ["ES2020"],
"module": "CommonJS",
"target": "ES2020"
}
}
If you are running Node.js 18 you can see my similar answer for Node.js 18 here
If you are running Node.js 16 you can see my similar answer for Node.js 16 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
-
3There's a way to omit the `.js` extension when importing, and I documented that in my [modern-typescript-project](https://github.com/dandv/typescript-modern-project#import-your-own-modules-without-specifying-an-extension) repo. Other than that, thanks for pushing the envelope on generating ES Modules code with TypeScript! Let's hope that TypeScript module authors start to adopt this and [publish proper ES Modules](https://github.com/typegoose/typegoose/issues/214). – Dan Dascalescu Apr 19 '20 at 19:44
-
@DanDascalescu yeah, I was debating on wether to include `--experimental-specifier-resolution=node` or not, but ultimately decided not to since I didn't want too many people to start using `--experimental-*` flags. Thanks for the link! :) – Linus Unnebäck Apr 20 '20 at 06:34
-
2What about `esModuleInterop`? I infer it can or must be false, since you are explicitly setting `allowSyntheticDefaultImports`. – Ed Staub Jul 10 '20 at 19:05
-
Your config seems more modern than https://github.com/tsconfig/bases/blob/master/bases/node14.json. Which should we be using, and when? – Gili Oct 19 '20 at 01:39
-
1@Gili This config uses the newly introduced (to Node.js) ES Modules, whereas the one you linked is sticking with CommonJS. I would personally recommend this one since I think that ES Modules are the future... – Linus Unnebäck Oct 19 '20 at 11:38
-
5This is exactly what I was looking for, thanks for being ahead of [Microsoft](https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping). – Andy Nov 04 '20 at 20:58
-
Afaik, `URLSearchParams` are available on the global object from Node v10+. I wonder why TypeScript still states `Cannot find name 'URLSearchParams'.`. Is the config missing something? – René Schubert Apr 14 '21 at 21:15
-
I am also finding I have to do a lot of imports using `import pkgOkta from '@okta/oidc-middleware';` and `const { ExpressOIDC } = pkgOkta;` as suggested by a Node runtime exception, though I am not yet aware of exactly why that is the case. Seems to occur with all CommonJS modules or at least those that don't have a type declaration file. – Richard Collette Jun 04 '21 at 15:18
-
How about `"moduleResolution": "NodeNext"`? – akostadinov Apr 26 '23 at 09:18