关键在于匹配运行时环境:commonjs用"node",原生esm必须用"node16"/"nodenext",打包场景推荐"bundler";错误使用"node"解析esm路径会导致ts不报错但运行时报错。

要让 TypeScript 正确识别 Node.js 的模块引入路径,关键在于匹配运行时环境的解析逻辑。Node.js 原生 ESM("type": "module")和 CommonJS 环境的解析规则不同,moduleResolution 必须与之对齐,否则会出现“Cannot find module”或运行时报错。
明确项目运行环境再选值
不是所有项目都适用同一个配置。你需要先确认:
- 项目最终在 Node.js 中以 CommonJS 运行(
package.json无"type": "module",且tsc输出为"module": "commonjs")→ 用"moduleResolution": "node" - 项目启用 Node 原生 ESM(
package.json有"type": "module",且tsc输出为"module": "esnext")→ 必须用"moduleResolution": "node16"或"nodenext" - 项目只做类型检查,由 Vite/Webpack/Rollup 打包 → 推荐
"moduleResolution": "bundler",它更贴近现代打包器行为,自动支持exports、imports字段和路径别名
常见错误:ESM 下仍用 "node" 导致路径失败
当项目是原生 ESM 时,如果错误地保留 "moduleResolution": "node",TypeScript 会按旧版 Node 规则尝试补全 .ts、.d.ts 等扩展名,但 Node.js 运行时拒绝加载无扩展名的 .ts 路径 —— 这就造成“TS 不报错,运行时报错”的典型割裂。
正确做法是:
- 启用
"moduleResolution": "node16" - 禁用
"allowImportingTsExtensions": true(该选项已不推荐,且与 ESM 冲突) - 确保所有本地
import显式带扩展名:import { util } from "./utils.ts"(注意是.ts,不是.js) - 第三方包导入保持不变:
import express from "express"
配合 baseUrl 和 paths 实现清晰路径别名
仅靠 moduleResolution 不足以解决深层嵌套导入问题。建议搭配路径映射:
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@lib/*": ["lib/*"]
}
}
此时需确保 moduleResolution 设置为 "node16" 或 "bundler" —— 它们才完整支持 package.json#exports 和 paths 的二次解析;"node" 对 paths 支持有限,容易漏匹配。
验证是否生效的小技巧
改完配置后,不要只看 IDE 是否补全成功。真正有效的验证方式是:
- 运行
tsc --noEmit --watch,观察是否有TS2307报错 - 用
node --experimental-specifier-resolution=node(Node v20+)启动,测试真实 ESM 加载行为 - 在
tsconfig.json中临时加"traceResolution": true,查看编译器实际查找路径(输出到控制台)











