假设在 ./utils/calcute.ts 中有一个工具函数 add() export function add(a: number, b: number): number { return a + b; }
然后我们在 main.ts 中需要使用这个 add 函数 写法 1, import 不带扩展名: tsconfig 配置 module=esnext ,然后假设有如下 main.ts 文件 import { add } from "./utils/calcute";
add(1,2)
使用 tsc 编译后使用 node 运行编译后的 js 文件会报错
node ./dist/main.js
... 省略
code: 'ERR_UNSUPPORTED_DIR_IMPORT', url: 'file:///home/xxxxxx/dist/utils/calcute'
原因是现在的 node 处理 esm 的 import 需要指定具体文件名(即类似 import ./utils/calcute.js )。不写扩展名的 import 会报错 而 typescript 编译代码对 import 内 from "xxxx" 的部分是不会做任何处理直接保留的。按照 ts 官方的意思就是这部分是模块解析,不应该是 typescript 的工作而应交给 js 运行时(如 node 、浏览器)自己处理,所以 tsc 编译 ts 文件是会完整保留这部分不做任何变动的 基于这种方针,于是就有了两种解法
放弃 tsc 编译使用 bundle 下面的写法 2
写法 2:import .js tsconfig 配置 module=nodenext 和 moduleResolution=nodenext ,然后 main.ts 内容如下 import { add } from "./utils/calcute.js"; // 需要添加 .js 扩展名
add(1,2)
说真的,当年我接触到这种写法的时候是大受震撼的。 在 ts 文件中写 import .js 实在过于丑陋了。我不解、我不适应、我无法接受 但这样的代码经过 tsc 编译后就能正常被 node 执行了,我也只能捏着鼻子用了 本来以为 esm 的问题也就这样了,但没想到到了 2025 年就乱套了 写法 3: import .ts 因为 bun, deno 的竞争,不思进取的 node 终于开始迭代起功能了。甚至还破天荒地添加了直接执行 typescript 代码的功能(运行的时候直接丢弃类型信息把 ts 当 js 跑) 这个功能现在在在新 node 中已经默认开启可用了,并且 typescript 也为了这个功能添加多个更新。所以可以预见今后用 node 直接执行 ts 会多起来 然后,这个功能在 esm 上就不出意外得出意外了。还是上面的代码 main.ts 内容如下: import { add } from "./utils/calcute.js"; // 需要添加 .js 扩展名
add(1,2)
使用 node main.ts 执行后直接报错
node main.ts
... 省略
code: 'ERR_MODULE_NOT_FOUND', url: 'file:///home/xxxxxxxx/utils/calcute.js'
嗯,因为模块的代码位于文件 utils/calcute.ts 中,而 import 语句中写的是 ./utils/calcute.js,所以 node 理所当然的找不到对应的模块文件报错了 所以为了解决这个问题,tsconfig 后来添加了一个选项 allowImportingTsExtensions ,开启后在 main.ts 中需要将 import 改写成 import .ts 的形式 import { add } from "./utils/calcute.ts"; // 需要 import .ts ,而不是.js
add(1,2)
嗯,当年 typescript 的回旋镖就这么砸了回来,现在我们又必须在 ts 文件中写 import .ts 了。并且为了兼容这种写法 typesript 现在还不得不添加新的编译选项 allowImportingTsExtensions 来允许在 ts 文件中 import .ts 但是,这有个问题,启用这个选项必须也启用 noEmit ,也就是说在 typescript 官方那的说法是:我们没有被打脸啊,我们依旧不处理 import 的内容,你想 import .ts 可以,但是你这样写了的话就别用我们的 tsc 来把这种代码编译成 js 了 但问题是实际上开发中,使用 node 直接执行 ts 文件测试,然后在生产环境中使用 tsc 或其他工具编译成 js 运行会很常见 于是如果你想直接 node 执行 ts 代码,那就得放弃将使用 tsc 将代码编译为 js 所以大家怎么选 目前这 esm import 写法已经乱成这样了,大家平时会怎么选?