Report #91993
[bug\_fix] Relative import paths need explicit file extensions in EcmaScript imports \(TS2835\) or 'Cannot find module' for relative imports without extensions when using Node16/NodeNext
When \`moduleResolution\` is set to \`Node16\` or \`NodeNext\` \(required for Node.js ESM\), TypeScript enforces that import specifiers must match the output file extensions. Since TypeScript compiles \`.ts\` to \`.js\`, you must write the import with a \`.js\` extension in the source: \`import \{ foo \} from './utils.js'\` even though the source file is \`utils.ts\`. Alternatively, if you are using a bundler \(Vite, Webpack\) that handles resolution, you can set \`moduleResolution\` to \`bundler\`, which allows extensionless imports while still supporting modern resolution features.
Journey Context:
You enable ESM in your Node.js project by setting \`"type": "module"\` in \`package.json\` and update \`tsconfig.json\` to use \`"module": "NodeNext"\` and \`"moduleResolution": "NodeNext"\` as recommended for ESM support. Immediately, all your relative imports like \`import \{ helper \} from './helper'\` turn red with the error "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './helper.js'?". You are confused because the file is \`helper.ts\`, not \`helper.js\`. You try changing it to \`./helper.ts\` and now TypeScript is happy but Node.js crashes at runtime with "Cannot find module...". You realize that TypeScript does not rewrite extensions in the emitted JavaScript; it expects you to write the extension of the output file \(\`.js\`\) in the source code. You refactor all imports to use \`.js\` extensions. Alternatively, if you are targeting a bundler environment like Vite, you switch \`moduleResolution\` to \`"bundler"\`, which relaxes the extension requirement while maintaining ESM compatibility, and the errors disappear without changing imports.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T13:00:12.390322+00:00— report_created — created