先记住这个答案
已有 .d.ts 是编译器读取的类型输入,没有需要转译执行的函数体,因此 tsc 不会为它生成对应 JavaScript,也不会把它当作普通资源自动复制到 outDir。declaration 主要让可生成声明的实现输入产生 .d.ts,emitDeclarationOnly 进一步只保留声明类输出,noEmit 则禁止输出。发布包若依赖手写声明、图片或其他资源,应在打包流程中明确收集它们,并核对最终产物里的引用路径。文件参与检查并不证明消费者安装后也能得到它。
- 声明输入负责检查,不是待转译的实现
- declaration 生成实现的类型产物,不是目录复制开关
- 发布前要检查手写声明及其依赖是否真正进入包
include 为什么不能保证文件出现在 dist
include 和 files 影响编译程序的输入集合,而 outDir 指定实际生成文件的位置。声明文件即使在这个集合中,也没有对应 JavaScript 需要生成;图片和任意文本资源同样不会因为项目看得到就自动成为 tsc 的复制对象。
下面实现目录作为 rootDir,额外读取一个环境声明。编译后可以生成 index.js 与 index.d.ts,但不会复制原来的 environment.d.ts。环境声明位于 rootDir 外也不等于必须移动它,rootDir 主要约束需要输出的实现文件布局。
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"declaration": true,
"rootDir": "src",
"outDir": "dist"
},
"files": ["src/index.ts", "types/environment.d.ts"]
}在示例输入齐全且检查通过时,src/index.ts 会产生相应代码和声明;types/environment.d.ts 只提供类型信息。若发布后的公开声明需要引用该手写文件,必须另行把它纳入打包产物并保持路径正确。
几个输出选项不能混为同一件事
declaration 允许生成声明,emitDeclarationOnly 适合 JavaScript 由其他构建器处理、tsc 只负责类型产物的流程。noEmit 则常用于只检查类型的应用项目。开启这些选项之前,应先确认哪一个工具负责最终代码和类型文件。
排查时可以检查实际生效配置与输出文件清单,避免误把另一个 tsconfig 的设置当成当前命令。命令直接指定源文件和通过项目配置编译也可能采用不同输入前提。缓存或旧 dist 残留会干扰判断,验证应在受控临时输出目录核对新增产物。
发布问题需要从消费者能获得什么倒查
手写 index.d.ts 在仓库里存在,但 files 白名单、忽略规则或复制步骤漏掉它,安装者仍然找不到类型。另一种情况是入口声明被打包了,里面的相对引用却指向未发布的内部文件,开发仓库能通过并不能证明发布包完整。
应查看实际打包清单,并在独立消费项目中安装受控产物,分别验证类型解析和运行时导入。不要通过把路径指回开发源码来掩盖漏包,因为用户安装后的目录结构不同。类型产物的完整性也是发布契约的一部分。
容易答错的地方
- 开启 declaration 就会复制全部 .d.ts
- 它负责生成声明产物,已有声明文件的分发仍需要打包流程安排;应检查消费者实际需要的入口和相对依赖。
- dist 没有声明文件说明该文件没参与检查
- 输入是否被读取与是否输出不同,可以通过编译程序文件列表或有针对性的类型错误验证读取情况,不能只看输出目录推断。
面试官还会怎么问?
emitDeclarationOnly 会禁止所有文件输出吗?
不会,它保留声明及相关声明映射输出,避免生成 JavaScript;完全不输出应使用 noEmit,并确认没有其他工具独立写入产物。
手写声明可以放在 src 之外吗?
可以,只要被正确纳入类型程序并符合模块或全局作用域需求;位置选择还需兼顾发布后的引用路径,而不是只为绕过输出目录限制。
打包工具会不会帮忙复制声明?
有些插件能够生成或收集类型,但这是该工具的能力和配置,不能归因于 tsc 默认行为。应核对实际版本、输入和最终打包清单。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。