Report #52081
[bug\_fix] TypeScript ESM ERR\_MODULE\_NOT\_FOUND with .ts extensions
Use \`.js\` extensions in import specifiers even when importing \`.ts\` files \(TypeScript ESM requirement\), or use a loader like \`tsx\`/\`ts-node-esm\` to handle extension resolution at runtime.
Journey Context:
Developer sets up a new Node.js project with \`"type": "module"\` and TypeScript. Writes \`import \{ helper \} from './helper.ts'\` in \`index.ts\`. Compiles with \`tsc\`, then runs \`node dist/index.js\` \(where .ts becomes .js\). Gets ERR\_MODULE\_NOT\_FOUND pointing to \`./helper.ts\`. Developer thinks "but I compiled to .js, why is it looking for .ts?" Realizes the compiled \`dist/index.js\` still contains \`import ... from './helper.ts'\` because TypeScript doesn't rewrite extensions. Reads TypeScript ESM documentation. Learns the counter-intuitive rule: in TypeScript ESM, you must write \`import ... from './helper.js'\` \(yes, .js\) even though the source file is .ts. TypeScript allows this and emits it verbatim, and Node.js resolves the .js to the compiled file. Developer changes all imports to .js extensions. Alternatively, discovers \`tsx\` \(TypeScript Execute\) which handles this mapping automatically via loader hooks, or uses \`node --loader ts-node/esm\` to resolve .ts extensions at runtime without pre-compilation. The .js extension fix works because TypeScript's ESM implementation follows the browser spec requiring full specifiers, leaving extension resolution to the runtime.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T17:54:55.204748+00:00— report_created — created