
在 typescript 项目启用 esm("type": "module" + "module": "esnext")后,.ts 文件间导入若省略扩展名会报错;核心解法是禁用 allowimportingtsextensions,而非添加 .ts 后缀或修改文件路径。
在 typescript 项目启用 esm("type": "module" + "module": "esnext")后,.ts 文件间导入若省略扩展名会报错;核心解法是禁用 allowimportingtsextensions,而非添加 .ts 后缀或修改文件路径。
当项目从 CommonJS 迁移至原生 ESM 时,TypeScript 编译器与 Node.js 模块解析机制协同发生变化:Node.js 的 ESM 加载器严格遵循「完整文件路径」语义,不支持自动解析 .ts 扩展名;而 TypeScript 默认允许 .ts 扩展名导入(需显式启用 allowImportingTsExtensions),这反而与 ESM 运行时冲突。
你遇到的错误链本质是双重矛盾:
- 首先,省略扩展名(如 import { Button } from "../../../objects/Button";)→ Node.js ESM 解析失败,报 Cannot find module;
- 其次,手动补 .ts(如 .../Button.ts)→ TypeScript 检查触发 An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled. —— 因为该选项默认为 false,而你的配置(或继承自基础配置)可能意外启用了它。
✅ 正确且最小侵入的解决方案是:在 tsconfig.json 中明确禁用 allowImportingTsExtensions:
{
"extends": "..",
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"allowImportingTsExtensions": false,
"types": [ /* ... */ ]
}
}⚠️ 注意事项:
- allowImportingTsExtensions: false 是 TypeScript 5.0+ 的默认值,但若项目继承了旧版配置(如某些脚手架或 @tsconfig/recommended),该值可能被设为 true,必须显式覆盖;
- 禁用后,所有导入路径必须指向 已编译输出的 .js 文件(由 TypeScript 编译生成),即源码中仍写 Button,但运行时实际加载的是 Button.js(对应 Button.ts 编译结果);
- 确保构建流程(如 tsc --build 或 vite build)正常产出 .js 文件,并且目录结构与导入路径一致;
- 不要尝试通过 resolve.extensions(Webpack/Vite)或 --extensions(ts-node)绕过——这违背 ESM 规范,且在纯 Node.js ESM 环境中无效。
? 总结:ESM 下 TypeScript 导入的本质是「源码编写 → 编译为 JS → 运行时加载 JS」。路径应面向输出产物设计,而非源码扩展名。关闭 allowImportingTsExtensions 是对齐这一模型的关键开关,既保持代码简洁性(无需 .ts 后缀),又符合标准 ESM 行为规范。









