Node.js "ts-node ESM loader error" in CI
Running TypeScript as native ESM with ts-node requires the ESM loader and matching tsconfig module settings. Without them, ts-node either throws on .ts extensions or fails to resolve ESM imports.
What this error means
A script run via ts-node under "type": "module" fails in CI with a loader or unknown-extension error, even though plain tsc compiles the same files.
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension
".ts" for /work/repo/src/main.ts
at Object.getFileProtocolModuleFormatCommon causes
The ESM loader is not enabled
ts-node defaults to CommonJS. Under ESM you must register ts-node/esm as a loader, or Node does not know how to handle .ts.
tsconfig module settings mismatch
If module/moduleResolution are not ESM-aware, ts-node emits or resolves modules in a way that conflicts with the ESM runtime.
How to fix it
Register the ts-node ESM loader
Use the module loader flag so Node hands .ts files to ts-node under ESM.
node --loader ts-node/esm src/main.ts
# or on newer Node:
node --import ts-node/esm/transpile-only src/main.tsAlign tsconfig with ESM
Set ESM-aware module resolution so ts-node and the runtime agree.
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext"
},
"ts-node": { "esm": true }
}How to prevent it
- Consider
tsxinstead of ts-node for simpler ESM execution. - Pin Node and ts-node versions so loader flags stay consistent.
- Keep tsconfig module settings ESM-aware when running ESM.