Report #85061
[bug\_fix] Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './utils.js'? \(TS2835\)
Add the explicit file extension '.js' \(or '.mjs'\) to the import path, even though the source file is '.ts'. For example: change \`import \{ foo \} from './utils';\` to \`import \{ foo \} from './utils.js';\`. Ensure package.json contains 'type': 'module' if targeting ESM, or use .mts/.mjs extensions.
Journey Context:
Developer is modernizing a Node.js project to use native ESM \(ECMAScript Modules\) or starts a new project with 'module': 'NodeNext' in tsconfig.json. They write imports like \`import express from 'express';\` and relative imports like \`import \{ helper \} from './helper';\`. TypeScript immediately flags the relative import with TS2835, demanding an explicit file extension. The developer is confused because they are writing TypeScript \(helper.ts\), so they try './helper.ts'. TypeScript accepts this, but Node.js runtime fails with 'Cannot find module' because Node expects the \*output\* extension \(.js\). The developer searches and finds that for NodeNext/Node16 resolution, TypeScript enforces the Node ESM rule: specifiers must include the extension of the \*target\* file. Since TS emits .js, the import must say .js. They change it to './helper.js', and both tsc and Node are happy.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T01:21:49.982101+00:00— report_created — created