This error occurs when ts-node tries to load an ES module (ESM) using CommonJS require(). It happens when mixing module systems, typically when a package uses ESM exports but your tsconfig.json or runtime expects CommonJS. The fix involves aligning module systems across your configuration.
The ERR_REQUIRE_ESM error is thrown by ts-node when it attempts to use Node.js's require() function to load a module that is an ES Module (ESM). This happens because: 1. **Module System Mismatch**: Node.js has two module systems: CommonJS (using require()) and ES Modules (using import/export). They are not directly compatible. 2. **Package.json Type**: Many modern npm packages now use "type": "module" in their package.json, making them ES Modules by default. 3. **ts-node Configuration**: By default, ts-node uses CommonJS. When it encounters an ESM package, require() fails with this error. 4. **TypeScript Compiler Options**: Your tsconfig.json might be set to "module": "commonjs" while dependencies use ESM. The error typically appears when: - Importing a library that recently switched to ESM - Using TypeScript with mixed module systems - Running tests or scripts with ts-node in a project with ESM dependencies
First, verify your TypeScript configuration. Open tsconfig.json and check the "module" field:
{
"compilerOptions": {
"module": "commonjs", // This might need to change
// ... other options
}
}If you need to use ESM, change it to:
{
"compilerOptions": {
"module": "ESNext", // or "ES2020", "ES2022"
// ... other options
}
}For CommonJS projects, keep "module": "commonjs" but ensure proper ESM interop.
Add or modify the "type" field in your package.json:
For ESM projects:
{
"type": "module",
"scripts": {
"start": "node --loader ts-node/esm ./src/index.ts"
}
}For CommonJS projects:
{
"type": "commonjs",
"scripts": {
"start": "ts-node ./src/index.ts"
}
}If you're mixing module systems, you might need additional configuration.
If using ESM, update your ts-node configuration. Create or update tsconfig.json:
{
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"compilerOptions": {
"module": "ESNext"
}
}Or use command-line flags:
# For ESM
ts-node --esm --experimental-specifier-resolution=node index.ts
# Or with loader
node --loader ts-node/esm index.tsIf you need to import an ESM package in a CommonJS file, use dynamic import():
// Instead of:
// import { something } from 'esm-package';
// Use dynamic import:
const { something } = await import('esm-package');
// Or wrap in async function
async function useEsmPackage() {
const esmModule = await import('esm-package');
return esmModule.something();
}Note: Dynamic imports return Promises, so you need to handle them asynchronously.
ESM requires specific file extensions. Rename your files:
- Change .ts to .mts for ESM TypeScript files
- Change .js to .mjs for ESM JavaScript files
- Or keep extensions but ensure package.json has "type": "module"
Example structure:
src/
├── index.mts # ESM TypeScript
├── utils.mts
└── commonjs/
└── legacy.cjs # CommonJS JavaScriptUpdate imports to use full extensions:
import { helper } from './utils.mjs'; // Not './utils'If tests fail, update Jest configuration in package.json or jest.config.js:
// jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\.{1,2}/.*)\.js$': '$1',
},
transform: {
'^.+\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};Or use ts-jest with ESM:
npm install --save-dev ts-jest @types/jest
npx ts-jest config:init### Understanding Node.js Module Systems
Node.js supports two module systems:
1. CommonJS (CJS): The traditional system using require() and module.exports
2. ES Modules (ESM): The modern standard using import and export
Key differences:
- ESM is asynchronous, CJS is synchronous
- ESM has static analysis, CJS is dynamic
- ESM files need .mjs extension or "type": "module"
- CJS files need .cjs extension or "type": "commonjs"
### ts-node ESM Support
ts-node added ESM support in version 10+. Key requirements:
1. Node.js 12.20+ or 14.13+ (for stable ESM)
2. --loader ts-node/esm flag or "esm": true in tsconfig
3. Proper file extensions (.mts, .mjs)
### Common Pitfalls
1. Dual Package Hazard: When a package provides both CJS and ESM builds, Node.js might load the wrong one. Check package.json for "exports" field.
2. Circular Dependencies: ESM handles these differently than CJS. They might work in CJS but fail in ESM.
3. __dirname and __filename: These are not available in ESM. Use:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);4. Top-level await: Only available in ESM, not CJS.
### Migration Strategy
If migrating from CJS to ESM:
1. Start with "type": "module" in package.json
2. Rename files to .mjs or keep .js with module type
3. Update all imports to use full extensions
4. Replace require() with import
5. Update module.exports to export
6. Handle dynamic imports for conditional loading
### Tools for Debugging
1. Check module type:
node -p "require('fs').readFileSync('node_modules/some-package/package.json', 'utf8')" | grep type2. Force CJS loading (not recommended for production):
node --input-type=commonjs script.js3. Use node --loader flag for custom module resolution.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript