先记住这个答案
declarationMap 在生成声明时额外输出 .d.ts.map,记录声明位置到原始 TypeScript 源文件位置的映射,并由声明文件关联这份 map。支持该能力的语言工具可以据此把导航从生成声明引向源码,尤其适合多包项目和库开发。它与 JavaScript 的 sourceMap 用途不同,也不会自动把源码发布给消费者。跳转是否成功还取决于 map 是否随包分发、引用路径是否可解析、源码是否可访问,以及所用编辑器和语言服务的行为。
- 声明映射连接 .d.ts 位置与原始 TypeScript 文件
- map 存在不代表源码已经随包分发
- 类型导航映射与运行时 JavaScript 调试映射分开配置
生成声明时为什么需要另一份位置映射
实现文件里的函数体、局部变量和部分注释不会原样出现在 .d.ts,因此声明里的行列位置与源码位置通常不同。只把文件名从 .d.ts 换成 .ts 无法精确定位原定义,映射文件负责保存这种生成前后的对应关系。
下面开启 declaration 与 declarationMap,并使用 emitDeclarationOnly。对 src/index.ts 编译后会得到 dist/index.d.ts 与对应 map,声明末尾关联映射文件。它不会在这个配置下生成 index.js,也不是让浏览器直接执行 TypeScript 源码。
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}在简单目录布局中,map 可通过相对位置指向 src/index.ts。必须检查实际输出的 sources 和声明中的映射关联;移动 dist 或发布时遗漏源码,都可能让导航链条失效。
为什么消费者拿到 map 仍找不到源码
包只发布 dist 时,map 可能仍指向包内并不存在的 src。开发机器上的工作区恰好保留源文件,所以导航成功;安装到独立目录后这项前提消失,就可能回到声明文件或无法继续跳转。源码是否公开需要由包的分发方案明确决定。
检查时依次确认消费者解析到哪份 .d.ts、它关联哪份 map、map 中的 sourceRoot 与 sources 如何组合,以及目标文件是否存在。不要在未检查路径之前反复重启编辑器,也不要把某台机器的成功跳转当成所有消费环境都可用的证明。
类型导航与运行时调试不能互相替代
sourceMap 常用于把生成 JavaScript 的位置映射到源码,服务于调试器和错误定位;declarationMap 面向类型声明导航。一个项目可以需要两者,也可能只负责生成其中一种,取决于 JavaScript 是否由 tsc 或其他工具产出。
语言服务还可能通过工作区项目引用或源码重定向等机制直接找到实现,所以没有 map 时偶然跳转成功,不证明发布包已经具备同等体验。应在隔离消费目录验证声明、map 与源码的完整链路,并注明实际使用的 TypeScript 和工具版本。
容易答错的地方
- 开启 declarationMap 就会自动把源码一起打包
- 编译器生成位置映射,但发布文件收集是另一项工作;map 指向的源码若没有被分发或无法读取,消费者仍可能不能跳转。
- 有 d.ts.map 就能调试浏览器中的 JavaScript
- 声明映射服务于类型导航,不是运行时代码位置的替代品。浏览器调试需要对应 JavaScript 产物与适当的源码映射支持。
面试官还会怎么问?
不想公开源码还能发布 declarationMap 吗?
可以评估实际价值,但不能承诺消费者能够读取未分发的源码。应检查映射是否泄露不必要路径,并选择符合包分发目标的导航体验。
只生成声明时 declarationMap 还有效吗?
可以与 emitDeclarationOnly 配合生成 .d.ts 和 .d.ts.map。它们的用途独立于 JavaScript 是否由同一次 tsc 命令输出。
为什么 monorepo 内能跳,安装包后不能跳?
工作区可能提供源码和项目关系,而发布包缺少这些条件。应在不依赖原仓库路径的消费环境检查实际入口、map 和源码文件。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。