How to Resolve FalseEsm and FalseCjs Type Errors When Moving to ESM‑Only

This article explains why sharing a single .d.ts file between ESM and CJS can cause FalseEsm and FalseCjs type errors, shows how recent Node and TypeScript updates mitigate the issue, and provides practical solutions such as generating separate .d.cts files or using TS 5.8 with "module": "nodenext".

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
How to Resolve FalseEsm and FalseCjs Type Errors When Moving to ESM‑Only

Node 20 and 22 support require ESM, signaling the final push from dual packages to ESM‑only

Antfu’s article “Move to ESM‑only” highlighted this shift, but many developers overlook the impact on type definition files (.d.ts, .d.mts, .d.cjs).

The current file is a CommonJS module whose imports will produce 'require' calls.

ESM and CJS sharing the same type causes FalseEsm issues

The history of using .d.ts files for ESM‑only predates .js files; before tsup PR 934 , dual packages generated a single type file, which is a misuse that leads to FalseEsm and FalseCjs problems.

What are FalseEsm and FalseCjs?

FalseEsm: https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md
FalseCjs: https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md

When a single .d.ts is shared, the outcome depends on whether package.json contains "type": "module":

1. With "type": "module"

├── dist
│   ├── index.cjs
│   ├── index.d.ts
│   └── index.js
└── package.json
{
  "name": "my-package",
  "type": "module",
  "exports": {
    "types": "./dist/index.d.ts",
    "require": "./dist/index.cjs",
    "import": "./dist/index.js"
  }
}

Because the package is marked as ESM, the .d.ts is treated as an ESM type. Tools like "are the types wrong?" then report errors such as the one shown in the image below.

2. Without "type": "module"

├── dist
│   ├── index.js
│   ├── index.d.ts
│   └── index.mjs
└── package.json
{
  "name": "my-package",
  "exports": {
    "types": "./dist/index.d.ts",
    "require": "./dist/index.js",
    "import": "./dist/index.mjs"
  }
}

FalseEsm errors are more severe because TypeScript assumes the package is pure ESM and refuses to require it, resulting in a runtime error.

Although the JavaScript output works (dual package), the type error is noisy.

Generating separate .d.ts files for ESM and CJS

The solution presented in tsup PR 934 creates distinct type files for each module format:

├── dist
│   ├── index.d.cts <---
│   ├── index.cjs
│   ├── index.d.ts <---
│   └── index.js
└── package.json
{
  "name": "my-package",
  "type": "module",
  "exports": {
    "import": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "require": {
      "types": "./dist/index.d.cts",
      "default": "./dist/index.cjs"
    }
  }
}

While the exact generation of .d.cts files is beyond this article’s scope, the file extensions are now correct.

Returning to a shared type file with newer Node and TypeScript

Node 20’s support for requiring ESM and TypeScript 5.8’s module: "nodenext" option eliminate the previous error, as documented in the TypeScript 5.8 release notes.

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#support-for-require-of-ecmascript-modules-in---module-nodenext

New ways to address FalseEsm now include:

Setting moduleResolution: bundler Upgrading to TS 5.8 and using module: nodenext This issue will mainly affect developers who compile to CommonJS and need to support Node ≤ 18; in most cases it is just noise, and keeping ESM‑only types is beneficial for future migration.

Generating .d.cts files can be considerably more complex.

Conclusion

In the industry’s move from dual packages to ESM‑only, many pain points—including FalseEsm type problems—have been resolved.

Two recommended practices are:

Pure ESM (.js + .d.ts, both outputs are ESM)

ESM + CJS (.js + .cjs + .d.ts, JavaScript output is dual, type output is ESM)

These are the default templates of Rslib :

There is no longer a need to consider .d.cts; move your types to ESM‑only.

References

[1] Are the types wrong?: https://arethetypeswrong.github.io/?p=%40rsbuild%2Fplugin-node-polyfill%401.3.2

Node.jsCJSESMtype definitionsdual packagesFalseEsm
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

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.