Description
🔎 Search Terms
"ESM", "CJS", "ES Module", "CommonJS", "dual", "module", "Node16", "module resolution"
🕗 Version & Regression Information
- This is the behavior in every version I tried (4.8.4, 4.9.5, 5.0.4, 5.1.6, 5.2.2, 5.3.2, git main), and I reviewed the FAQ for entries about moduleResolution, Node16
⏯ Playground Link
https://github.com/djcsdy/node-resolution
(This bug can't be reproduced on Playground because it requires a package.json, so I've provided a minimal git repository instead).
💻 Code
Consider the npm package "unknown" (https://www.npmjs.com/package/unknown).
This package exports a dual ESM/CJS module with bundled type definitions.
unknown's package.json includes:
{
"type": "module",
"main": "./dist/cjs/unknown.js",
"module": "./dist/unknown.js",
"exports": {
".": {
"require": "./dist/cjs/unknown.js",
"default": "./dist/unknown.js"
}
},
"types": "./dist/unknown.d.ts"
}
This bug manifests when importing a project like unknown from a CommonJS project with
tsconfig.json:
{
"compilerOptions": {
"module": "node16",
"strict": true
}
}
index.ts:
import {hasProperty} from "unknown";
hasProperty({}, "foo");
🙁 Actual behavior
On import {hasProperty} from "unknown";
, TypeScript reports:
index.ts:1:27 - error TS1479: The current file is a CommonJS module whose
imports will produce 'require' calls; however, the referenced file is an
ECMAScript module and cannot be imported with 'require'. Consider writing a
dynamic 'import("unknown")' call instead.
🙂 Expected behavior
TypeScript should recognise that the package "unknown" can be imported as a CommonJS module from a CommonJS context, or as an ES Module from an ESM context, and treat it appropriately in each case.
Additional information about the issue
It is possible to work around the problem by editing the "unknown" module's package.json to remove "type": "module"
.
This seems to indicate that TypeScript is treating the module as ESM-only because of that line.
This does not match node's behaviour. My understanding is that for node, "exports": { ".": { "require": "./dist/cjs/unknown.js", ... } } }
takes precedence, and that node treats the module as CJS when imported using require, even though "type": "module"
is set and the imported file has a plain ".js" extension.