Report #56294
[bug\_fix] TypeScript/ts-node ESM imports require .js extensions but fail at runtime
Use \`.js\` extensions in TypeScript ESM imports \(e.g., \`import \{ foo \} from './foo.js'\`\) even though the file is \`.ts\`, or use a bundler like tsx/esbuild that handles resolution.
Journey Context:
Developer sets up a Node.js project with TypeScript and ESM \(\`"type": "module"\` in package.json\). They write \`import \{ helper \} from './helper'\` in \`index.ts\`, where \`helper.ts\` exists. TypeScript compiles without errors \(with \`module: "NodeNext"\` or \`"ESNext"\`\), but running \`node dist/index.js\` fails with \`ERR\_MODULE\_NOT\_FOUND\` because Node ESM requires full specifiers including the \`.js\` extension. Developer tries adding \`.ts\` extension in the import, but Node doesn't support that. They try using \`ts-node\` but hit \`ERR\_UNKNOWN\_FILE\_EXTENSION\` because ts-node's ESM loader registration changed in Node 20. The "aha" moment is realizing TypeScript's role is purely compile-time; Node runs the emitted \`.js\` files. Thus, TypeScript source must use \`.js\` extensions in imports \(\`./helper.js\` to reference \`./helper.ts\`\) when using Node-native ESM. Alternatively, they abandon native ESM execution and use \`tsx\` or \`ts-node\` with specific loader flags that patch resolution.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T00:58:49.716174+00:00— report_created — created