Skip to main content

CommonJS vs native ECMAScript modules

TypeScript is almost always written using modern import syntax, but you can choose to either transform to CommonJS or use node's native ESM support. Configuration is different for each.

Here is a brief comparison of the two.

CommonJSNative ECMAScript modules
Write native import syntaxWrite native import syntax
Transforms import into require()Does not transform import
Node executes scripts using the classic CommonJS loaderNode executes scripts using the new ESM loader
Use any of:
ts-node CLI
node -r ts-node/register
NODE_OPTIONS="ts-node/register" node
require('ts-node').register({/* options */})
Must use the ESM loader via:
node --loader ts-node/esm
NODE_OPTIONS="--loader ts-node/esm" node

CommonJS#

Transforming to CommonJS is typically simpler and more widely supported because it is older. You must remove "type": "module" from package.json and set "module": "CommonJS" in tsconfig.json.

package.json
{
// This can be omitted; commonjs is the default
"type": "commonjs"
}
tsconfig.json
{
"compilerOptions": {
"module": "CommonJS"
}
}

If you must keep "module": "ESNext" for tsc, webpack, or another build tool, you can set an override for ts-node.

tsconfig.json
{
"compilerOptions": {
"module": "ESNext"
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}

Native ECMAScript modules#

Node's ESM loader hooks are experimental and subject to change. ts-node's ESM support is also experimental. They may have breaking changes in minor and patch releases and are not recommended for production.

For complete usage, limitations, and to provide feedback, see #1007.

You must set "type": "module" in package.json and "module": "ESNext" in tsconfig.json.

package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"module": "ESNext" // or ES2015, ES2020
}
}