How TypeScript 4.5 Simplifies Node.js ESM Module Integration

This article explains how TypeScript 4.5 adds native support for Node.js ES modules, details the evolution of Node's module system from "main" to "exports", shows the required tsconfig and package.json settings, and provides practical solutions for common import‑export pitfalls.

Alibaba Terminal Technology
Alibaba Terminal Technology
Alibaba Terminal Technology
How TypeScript 4.5 Simplifies Node.js ESM Module Integration

Node.js module evolution

Before Node v12.7.0 the entry point of a package was defined by the main field in package.json. Starting with v12.7.0 the exports field allows fine‑grained export definitions and conditional entry points, enabling separate commonjs and esmodule paths.

{
  "name": "demo",
  "main": "demo.js"
}
{
  "name": "demo",
  "exports": "./demo.js"
}

When type": "module" is set, .js files are treated as ES modules and require, module.exports are unavailable; only import / export can be used.

TypeScript 4.5 adds module options

TS 4.5 introduces two new values for compilerOptions.module: nodenext and node12, which align the TypeScript compiler with Node’s ESM implementation.

{
  "compilerOptions": {
    "module": "nodenext"
  }
}
{
  "compilerOptions": {
    "module": "node12"
  }
}

Using ESM modules in TS before 4.5

Prior to 4.5, a pure‑ESM package required a custom moduleResolution and explicit type declarations. Example pure‑esm package:

{
  "name": "pure-esm",
  "exports": "./esm.js",
  "type": "module",
  "types": "./esm.d.ts"
}

In tsconfig.json you had to set:

{
  "compilerOptions": {
    "moduleResolution": "node",
    "module": "esnext"
  }
}

Without these settings, imports like import * as pure from "pure-esm" would fail.

Break change in TS 4.5

TS 4.5 fully supports Node’s exports field, including conditional exports. Packages that expose both CommonJS and ES‑module entry points must provide type declarations for each export. Otherwise the compiler reports missing declaration files.

Example of a dual‑export package multi-mod that fails in TS 4.5:

node_modules
└── multi-mod
    ├── foo.js
    ├── foo.mjs
    ├── foo.d.ts
    └── package.json
// foo.js
exports.hello = function() {
  console.log('module loaded!');
};
// foo.mjs
export * from './foo.js';
// foo.d.ts
export function hello(): void;
{
  "exports": {
    ".": {
      "import": "./foo.mjs",
      "require": "./foo.js"
    }
  },
  "types": "./foo.d.ts"
}

TS 4.5 expects a declaration file matching the ES‑module entry ( foo.d.mts) or a types field inside the exports". object.

Solutions

1. Add a .d.mts file for the ES‑module entry:

// foo.d.mts
export function hello(): void;

2. Declare the type file inside the exports object:

{
  "exports": {
    ".": {
      "import": "./foo.mjs",
      "require": "./foo.js",
      "types": "./foo.d.ts"
    }
  },
  "types": "./foo.d.ts"
}

Both approaches allow the TypeScript compiler to locate the correct type information.

Further capabilities

Beyond ESM support, TS 4.5 adds many other features. See the official release notes for details: TypeScript 4.5 Beta .

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptNode.jsESMmoduleTS4.5
Alibaba Terminal Technology
Written by

Alibaba Terminal Technology

Official public account of Alibaba Terminal

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.