太长了不想看?直接用 Rslib 就完事了
Move to esm-only (type)
在 node22 中支持了 require esm 之后,算是吹响了 dual 包(同时发 esm + cjs 产物) 到 esm-only 的最后号角,比如 antftu 的这篇文章 《Move to ESM-only》。但是大家似乎都没有关注到 .d.ts .d.mts .d.cjs,即类型文件关于 esm-only 的事情,本文是一些补充。
主要是解决这个报错:

The current file is a CommonJS module whose imports will produce 'require' calls
esm 和 cjs 共用同一份类型造成 FalseEsm 问题
.d.ts 文件 "使用 esm-only" 的历史比 .js 文件更早,在 github.com/egoist/tsup... 之前,大家已经不约而同的让 dual 包生成同一份类型,这实际上是一种错误用法,会造成 FalseEsm 和 FalseCjs 问题。
什么是 FalseEsm 和 FalseCjs 问题?
当共用一份 .d.ts 时,根据 package.json 中是否含有 "type": "module" 有两种产物
1. 含有 "type": "module"
sh
├── dist
│ ├── index.cjs
│ ├── index.d.ts
│ └── index.js
└── package.json
json
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js"
}
}
由于设置了 "type": "module",需要 .js 和 .cjs 扩展名产物,对应到类型产物应该是 .d.ts 和 .d.cts。 .d.ts 会被认为是 esm,因此该包只发布了 esm 的 type ,在 are the types wrong? 会报以下错误


2. 不含有 "type": "module"
sh
├── dist
│ ├── index.js
│ ├── index.d.ts
│ └── index.mjs
└── package.json
json
// package.json
{
"name": "my-package",
"exports": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
}


FalseEsm 问题会更严重一些,因为会抛出一个这个错误

因为 ts 只拿到了 esm type,认为它是一个 pure esm 包,不能被 require 导入,所以无奈只能报错。
但其实这是个 dual 包,js 产物是对的且可正常运行,这个类型错误是一个噪声。
为 esm 和 cjs 都生成对应的 dts 文件
如何解决 FalseEsm 和 FalseCjs 问题?
在 github.com/egoist/tsup... 中,为 esm 和 cjs 都生成了 dts 文件
对应的产物为
sh
├── dist
│ ├── index.d.cts <---
│ ├── index.cjs
│ │
│ ├── index.d.ts <---
│ └── index.js
└── package.json
json
// 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"
}
}
}
忽略 .d.cts 的生成细节和正确性不谈,这样修改起码扩展名是对的。
还是回到 esm 和 cjs 共用同一份类型
随着 node 22 中支持了 require esm,ts 也有了对应更改,在 TS 5.8 中,无法 require esm 的这个错误在 module: "nodenext"
中不会再报
www.typescriptlang.org/docs/handbo...
这一改变,FalseEsm 问题的报错有了更多解决方案,
- moduleResolution: bundler
- 升级 ts 5.8 并设置 module: nodenext
这个问题后续只会影响到 "使用 tsc 编译到 commonjs" 并且 "需要支持 node <= 18" 的开发者,甚至大多数情况下只是噪声,而且使用 esm 的 type 为未来切到 pure esm 有利。
反而去生成 .d.cts 会困难许多。
总结
综上,在社区从 dual 包到 esm only 的大趋势里,esm 之前的诸多痛点得到了解决,包括 FalseEsm 类型问题。
我认为的两种最佳实践应是:
- pure esm(.js + .d.ts, js 和 dts 产物均是 esm)
- esm + cjs (.js + .cjs + .d.ts,js 产物是 dual,dts 产物是 esm)
而且这也是 Rslib 的两套默认模板

不需要再考虑 .d.cts 了,让 type 先 move to esm-only 吧。
参考