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 用户的问题藏起来。