Node、bundler 与 TypeScript 模块解析的关系

说明 TypeScript 为什么要在 nodenext 和 bundler 之间二选一,以及这背后对应的是 Node 运行时和现代 bundler 的两套不同世界观。

#tech / dev #resource / typescript #type / synthesis #status / growing

[!info] related notes

Node、bundler 与 TypeScript 模块解析的关系

范围

这篇笔记解释的不是单个 tsconfig 选项,而是为什么同样的 import 写法,在 Node 项目和 bundler 项目里会有不同约束。

为什么要放在一起理解

TypeScript 的模块解析不是凭空设计的,它必须模拟一个“真实会运行你代码的系统”。

通常这个系统只有两大类:

  • Node.js
  • bundler

依赖路径 / 调用链 / 演进链

Node 世界

如果代码最终会变成很多独立 .js 文件,再直接交给 Node 执行,那么:

  • Node 自己是最终解析者
  • TypeScript 更应该模拟 Node
  • 这时 nodenext 更自然

bundler 世界

如果代码最终会先交给 Vite / Webpack / Rollup / esbuild,再由它们统一打包:

  • bundler 才是最终解析者
  • TypeScript 更应该模拟 bundler
  • 这时 bundler 更自然

对比与易混淆点

相对路径扩展名

  • Node ESM 更强调写出 .js
  • bundler 项目通常允许省略扩展名

package exports / imports

两种模式都支持现代 package.json exports / imports,但使用哪条条件、要求多严格,语境并不一样。

发布库时为什么更保守

如果你写的是 npm library,你的消费者不一定都在 bundler 环境里,所以只在 bundler 模式下类型检查,有可能把未来 Node 用户的问题藏起来。

创建于 2026/5/15 更新于 2026/5/27