TS+ESM项目导入自定义模块不加扩展名就报错的问题
折腾了两个小时,我基本弄明白了如何在 TypeScript 中使用自定义的 ESM 模块。
一开始以为直接导入文件名就行,不需要扩展名。但是,这样使用 TSC 编译出来之后运行报错,因为 ESM 规范要求导入自定义模块必须带有扩展名。然而,TSC 编译出来的项目中导入模块是不带扩展名的,所以 Node.js 会报错。
于是,我想当然地写了 import router from "./router.ts";,认为 TypeScript 会自动处理扩展名。然而,并不是这样的。
TypeScript 直接不允许导入 .ts 后缀的文件,要求启用 allowImportingTsExtensions。但是加了这个选项之后,出现了更多的错误:
error TS2691: An import path cannot end with a '.ts' extension. Consider importing './router.js' instead.
error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './event.js'?
关于这个问题,在 GitHub 上有相关的讨论:https://github.com/microsoft/TypeScript/issues/16577
解决方案
- 在
tsconfig.json中将moduleResolution设置为nodenext。设置之后,VS Code 导入模块时会自动加上.js后缀。 - 使用
import router from "./router.js";,注意这里扩展名为.js。
实际上,上面的报错也说明了问题:“An import path cannot end with a ‘.ts’ extension. Consider importing ’./router.js’ instead.”(导入路径不能以 .ts 结尾,考虑导入 ’./router.js’)
非常坑啊有没有,明明是 TypeScript,为什么要导入 JavaScript 呢?
ChatGPT 给出了解释:
您在 TypeScript 中导入模块时,使用的是相对路径或绝对路径来引用另一个文件。这与 JavaScript 中的导入语句类似。
当 TypeScript 编译器将 TypeScript 代码转换为 JavaScript 代码时,它会自动将
.ts扩展名更改为.js扩展名,并生成一个新的 JavaScript 文件,其中包含编译后的 JavaScript 代码。因此,即使您在 TypeScript 代码中导入的是
.ts文件,最终在运行时实际上会加载并执行.js文件,因此需要在导入语句中指定.js扩展名。