-
-
Notifications
You must be signed in to change notification settings - Fork 542
Description
Search Terms
ts-node
, ESM
, exports
, default export
, import
, require
, wrong types
Expected Behavior
When using ts-node
in ESM mode, it should correctly resolve type definitions from the import.types
field in package.json
when importing a default export.
Actual Behavior
When exports
in package.json
is nested and contains both import
and require
, ts-node
appears to load type definitions from require.types
instead of import.types
. This causes default exports to fail, while named exports work correctly.
Steps to Reproduce the Problem
-
Create a TypeScript library
sample-library
with the followingindex.ts
:export function getMessage(): string { return "This is a named export"; } export default function getDefaultMessage(): string { return "This is the default export"; }
-
Compile the TypeScript files and set up
package.json
with the following nestedexports
configuration:"exports": { ".": { "import": { "types": "./dist/index.d.ts", // Expected to be used in ESM mode "import": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", // Suspected to be incorrectly loaded "require": "./dist/index.cjs" } } }
-
Install
sample-library
in another project and try to run the following code withts-node
in ESM mode:import getDefaultMessage, { getMessage } from "sample-library"; console.log(getMessage()); // Works console.log(getDefaultMessage()); // Fails
-
Run the script:
node --loader ts-node/esm src/index.ts
-
Observe that
getDefaultMessage()
fails, butgetMessage()
works correctly.
Minimal Reproduction
❌ DOES NOT WORK
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts", // Expected to be used in ESM mode
"import": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts", // Suspected to be incorrectly loaded
"require": "./dist/index.cjs"
}
}
}
✅ WORKS (if require.types
matches import.types
)
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts", // Expected to be used in ESM mode
"import": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.ts", // Now the same as "import.types", making it work
"require": "./dist/index.cjs"
}
}
}
❌ DOES NOT WORK (removing require
still fails)
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts", // Expected to work, but still fails
"import": "./dist/index.js"
}
}
}
✅ WORKS (partially nested structure)
"exports": {
".": {
"types": "./dist/index.d.ts", // Correctly resolved
"import": "./dist/index.js"
}
}
✅ WORKS (completely flat structure)
"exports": {
"types": "./dist/index.d.ts", // Correctly resolved
"import": "./dist/index.js"
}
Specifications
- ts-node version: 10.9.2
- node version: 22.2.0
- TypeScript version: 5.8.2
- tsconfig.json, if you're using one:
{}
- package.json:
{
"type": "module",
"dependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.8.2",
"sample-library": "file:../sample-library"
}
}
- Operating system and version: macOS 15.3
- If Windows, are you using WSL or WSL2?: (Fill in if applicable)
Workaround
There are two possible solutions:
-
Use the same declaration file for both
require.types
andimport.types
:"exports": { ".": { "import": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }, "require": { "types": "./dist/index.d.ts", // Use the same .d.ts file here "require": "./dist/index.cjs" } } }
-
Avoid nesting
import
andrequire
withinexports
. Instead, use a flat or minimally nested structure:"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } }