Report #87268
[bug\_fix] TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. OR Cannot find module './utils' or its corresponding type declarations. Did you mean './utils.js'?
Add the \`.js\` extension to relative imports even for TypeScript files \(e.g., \`import \{ foo \} from './utils.js'\`\), or change \`moduleResolution\` to \`"bundler"\` \(TypeScript 4.7\+\) if using a modern bundler that handles resolution.
Journey Context:
Developer configures a modern Node.js ESM project, setting \`type: "module"\` in \`package.json\` and using TypeScript 4.7\+. Following recommendations, they set \`module: "NodeNext"\` and \`moduleResolution: "NodeNext"\` in \`tsconfig.json\` to ensure emitted JavaScript is valid Node ESM. They write \`import \{ helper \} from './utils';\` \(no extension\). TypeScript immediately errors: "Relative import paths need explicit file extensions... Did you mean './utils.js'?". The developer is confused because the file is \`utils.ts\`, not \`utils.js\`. They try \`import ... from './utils.ts'\` and get "An import path cannot end with a '.ts' extension". They are stuck. They research and learn that Node.js ESM requires full specifiers including extensions \(\`.js\`\), and TypeScript's \`NodeNext\` resolution enforces this at the source level. Since TypeScript emits \`.js\` files for \`.ts\` sources, the import path in the source must anticipate the emitted extension. The "aha" moment is realizing they must write \`./utils.js\` in the TypeScript source file. The alternative fix, if they are using a bundler like Vite or Webpack, is to use \`moduleResolution: "bundler"\`, which relaxes the extension requirement while still supporting ESM syntax, delegating the actual resolution to the build tool.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T05:03:56.265919+00:00— report_created — created