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 following index.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 nested exports 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 with ts-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, but getMessage() 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:
{
"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 and import.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 and require within exports. Instead, use a flat or minimally nested structure:
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
Search Terms
ts-node,ESM,exports,default export,import,require,wrong typesExpected Behavior
When using
ts-nodein ESM mode, it should correctly resolve type definitions from theimport.typesfield inpackage.jsonwhen importing a default export.Actual Behavior
When
exportsinpackage.jsonis nested and contains bothimportandrequire,ts-nodeappears to load type definitions fromrequire.typesinstead ofimport.types. This causes default exports to fail, while named exports work correctly.Steps to Reproduce the Problem
Create a TypeScript library
sample-librarywith the followingindex.ts:Compile the TypeScript files and set up
package.jsonwith the following nestedexportsconfiguration:Install
sample-libraryin another project and try to run the following code withts-nodein ESM mode:Run the script:
Observe that
getDefaultMessage()fails, butgetMessage()works correctly.Minimal Reproduction
❌ DOES NOT WORK
✅ WORKS (if
require.typesmatchesimport.types)❌ DOES NOT WORK (removing
requirestill fails)✅ WORKS (partially nested structure)
✅ WORKS (completely flat structure)
Specifications
{}{ "type": "module", "dependencies": { "ts-node": "^10.9.2", "typescript": "^5.8.2", "sample-library": "file:../sample-library" } }Workaround
There are two possible solutions:
Use the same declaration file for both
require.typesandimport.types:Avoid nesting
importandrequirewithinexports. Instead, use a flat or minimally nested structure: