2025 年 node 项目,乱成一锅粥的 typescript ESM import 写法该怎么选? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
BeautifulSoap
V2EX    Node.js

2025 年 node 项目,乱成一锅粥的 typescript ESM import 写法该怎么选?

  •  
  •   BeautifulSoap 1 天前 2165 次点击

    假设在 ./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 文件是会完整保留这部分不做任何变动的

    基于这种方针,于是就有了两种解法

    1. 放弃 tsc 编译使用 bundle
    2. 下面的写法 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 写法已经乱成这样了,大家平时会怎么选?

    43 条回复    2025-10-19 23:03:39 +08:00
    irrigate2554
        1
    irrigate2554  
       1 天前   2
    我选择少用 nodejs 生态
    shakaraka
        2
    shakaraka  
    PRO
       1 天前
    现在经受过的好几个新旧项目全部切换成 bun 了,再次也是选 deno 。业务上基本依赖的第三方包基本全是 esm ,如果没有的话我们会把项目拉下来,用 AI 修改为 esm 的方式,作为一个本地依赖
    craftsmanship
        3
    craftsmanship  
       1 天前 via Android
    ESM 确实是痛点问题 很乱很麻烦
    craftsmanship
        4
    craftsmanship  
       1 天前 via Android   1
    我的建议是
    - import 的扩展名为 .ts
    - tsconfig 里 module 和 moduleResolution 都设为 NodeNext
    无需 allowImportingTsExtensions 和 noEmit 且不存在你说的问题
    yooomu
        5
    yooomu  
       1 天前
    遇到了同样的问题,所以换了 deno
    SDYY
        6
    SDYY  
       1 天前
    我在 utils/index.ts 中 export
    使用 import x from "./utils"
    Ketteiron
        7
    Ketteiron  
       1 天前
    tsc 虽然能编译成 js ,但这不是它该干的活,毕竟它只是老老实实地把 ts 翻译成 js 没有任何优化,tsc 用来检查类型就行了。
    我的做法是 "moduleResolution": "bundler",后端使用 tsup/tsdown ,前端使用 vite 。
    虽然官方推荐显示指定扩展名,但说实话完全没必要,未来真有必要也可以写个脚本全加上。
    learnshare
        8
    learnshare  
       1 天前
    看来大家经验都差不多,生态很乱,还频繁遇到这些状况。
    统一成 ESM 挺好的,但执行起来不太顺利
    stinkytofux
        9
    stinkytofux  
       1 天前
    前端真的是乱成了一锅粥了.
    Ketteiron
        10
    Ketteiron  
       1 天前
    另外现阶段还是建议用 tsx(不是 react 的那个 tsx) 运行 ts 文件,直到 nodejs 没有这些问题了再说。
    Cbdy
        11
    Cbdy  
       1 天前
    import { add } from "./utils/calcute.ts";

    add(1,2)

    我是使用这种写法的,返璞归真,简单明了
    july1995
        12
    july1995  
       1 天前
    写了几天 Python ,我觉得 js 的生态还挺好的。Python 给我的感觉更混乱。
    Donahue
        13
    Donahue  
       23 小时 50 分钟前
    @july1995 明明是 js 更乱
    SingeeKing
        14
    SingeeKing  
    PRO
       23 小时 49 分钟前
    我选择不带扩展名 + 不用 tsc 做编译(只用它做类型检查)
    root71370
        15
    root71370  
       23 小时 26 分钟前 via Android
    别吵了 明明是 java 最乱
    XCFOX
        16
    XCFOX  
       21 小时 43 分钟前
    我选 tsx: https://tsx.is/
    facebook47
        17
    facebook47  
       18 小时 14 分钟前 via Android
    @yooomu 这个现在可用了嘛?出来有些时间了,但是好像没什么浪花
    rick13
        18
    rick13  
       17 小时 11 分钟前
    @shakaraka 现在 bun 生产可用了吗,我看这几天才发 1.3
    mercury233
        19
    mercury233  
       16 小时 0 分钟前
    import { add } from "./utils/calcute";
    这种写法就不应该支持 calcute.* 是文件的情况,只支持 calcute/package.json 就会清晰很多
    subframe75361
        20
    subframe75361  
       15 小时 45 分钟前
    tsup 停止维护了,nodejs 只跑 tsdown 构建的代码,其他情况用 bun
    lqm
        21
    lqm  
       15 小时 29 分钟前
    用 tsx 执行
    fds
        22
    fds  
       13 小时 58 分钟前
    @rick13 #18 我拿来跑脚本还挺稳定的。
    nomagick
        23
    nomagick  
       13 小时 54 分钟前
    恕我直言 node.js 的 esm loader 写了十年还是半成品,基本算是做死了
    就当 node.js 就只能运行纯 commonjs, tsc 的时候永远翻译成 cjs 。
    如果想运行 esm 那就用其他运行时。
    JamesMackerel
        24
    JamesMackerel  
       13 小时 23 分钟前
    所以那个 go 写的 tsc 还有没有消息……
    shakaraka
        25
    shakaraka  
    PRO
       13 小时 6 分钟前
    @rick13 #18 我都在 5 、6 个商业项目上用了 1 、2 年
    opengg
        26
    opengg  
       12 小时 54 分钟前 via Android
    node 很多方面都是狗屎
    craftsmanship
        27
    craftsmanship  
       12 小时 38 分钟前 via Android
    @opengg 愿闻其详
    uni
        28
    uni  
       12 小时 30 分钟前
    5202 年了正确的方法是放弃 node 换 bun
    Ketteiron
        29
    Ketteiron  
       11 小时 42 分钟前   1
    @nomagick #23 很多项目已经逐渐完全放弃 cjs ,也不提供 cjs 产物,全面转向 esm 是必然的事。
    这跟 esm loader 没多大关系,主要是几万个 package 一开始不愿意支持 esm ,毕竟它还能跑对吧。
    有些库作者激进地 esm-only ,用户又要问为什么不支持 cjs ,这十年是用户与作者们在拉扯,nodejs 对此是没什么办法的。
    esbuild 之类的工具尽量解决历史遗留问题,nodejs 没必要重新实现一遍,因为未来某个时间点会放弃 cjs 。
    Terry05
        30
    Terry05  
       11 小时 11 分钟前
    这东西都喷到前端身上,这跟前端有一点关系吗
    molvqingtai
        31
    molvqingtai  
       10 小时 29 分钟前
    我的建议 tsc 检查类型,打包不要用 tsc
    xu33
        32
    xu33  
       9 小时 35 分钟前
    直接用 nextjs 有啥问题没,全 ts
    musi
        33
    musi  
       9 小时 13 分钟前
    我选择用专业的工具进行打包,比如 esbuild/vite
    zogwosh
        34
    zogwosh  
       8 小时 57 分钟前
    nodejs 这种垃圾只配当一个纯粹的 js 运行时使用,
    Zhousiru
        35
    Zhousiru  
       7 小时 39 分钟前
    可以尝试下 extensionless: https://www.npmjs.com/package/extensionless
    pursuer
        36
    pursuer  
       7 小时 10 分钟前
    用 AMD ,想怎么解析你可以自己写,不适合工具链打包场景

    https://github.com/partic2/partic2-iamdee

    t/1104713
    mengshouer
        37
    mengshouer  
       7 小时 5 分钟前
    现有项目有什么就用什么
    nomagick
        38
    nomagick  
       6 小时 47 分钟前   1
    @Ketteiron 不你不懂,node.js 的 esm loader 指的是从硬盘网络或文本 buff 加载 js 代码数据并最终转化成 js 对象的过程,其中涉及静态和动态加载,esm 文件中使用 require, 被 require 的文件中使用 import ,被加载的可能是硬盘文件,url 或者代码文本 buff 。 在简单加载之外又涉及到多个切面的插件,专门的加载线程,以及 node.js native binding 的特殊处理。 整个过程比你想像得复杂得多,具体流程一锅粥,代码写得一团乱麻,内部推不动外部看不懂,功能没写完就发布,标记成 Beta/RC ,根本用不了。
    FlashEcho
        39
    FlashEcho  
       5 小时 51 分钟前
    我感觉解法一是不是其实是最常见的,为啥你不用解法一?放弃 tsc 编译使用 bundle 不行吗
    rocmax
        40
    rocmax  
       3 小时 38 分钟前 via Android
    前一阵整理了一下正在开发的 monorepo ,把/ackages 下面所有内部模块都改为只用 tsc 输出 d.ts 不编译 js ,/apps 下全部用 bundler 打包。
    streamrx
        41
    streamrx  
       3 小时 26 分钟前 via iPhone
    @rick13 可以
    Ketteiron
        42
    Ketteiron  
       2 小时 24 分钟前
    @nomagick #38 稍微研究了一下,确实很蛋疼
    https://github.com/nodejs/node/issues/52697
    https://github.com/nodejs/node/issues/55782
    更蛋疼的是目前依旧需要为 cjs 浪费大量人力,而 cjs loader 看起来越来越好
    https://github.com/nodejs/node/issues/52219
    esm loader 确实需要重构(顺便重构成以 C++ 为主),但阅读了相关 issue 几乎可以下结论除非不再支持生态中的 cjs ,不然重构无法开头,有点理解你说的永远翻译成 cjs
    https://github.com/nodejs/node/issues/50356
    如果不准备放弃对 cjs 的支持,esm loader 理论上无法进行有效重构,且 nodejs 团队其中的一部分人员在 v24 依旧认为 cjs 不应该被放弃
    https://github.com/nodejs/node/pull/57460
    顺便还发现了 typescript 也放弃了 esm-only
    https://github.com/microsoft/TypeScript/pull/58419
    v2AKS
        43
    v2AKS  
       2 小时 11 分钟前
    esnext 是给 web 项目用的,环境是浏览器,你用 node 执行环境是 Node.js 就要换成 nodenext
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1182 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 17:14 PVG 01:14 LAX 10:14 JFK 13:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86