先记住这个答案
package.json 的 types 指向包内主声明文件,运行时入口则由 main 或 exports 等规则提供,两者应描述同一组公开 API。使用支持 exports 的模块解析模式时,应在相应导出条件中明确提供类型入口,并让 types 条件出现在适合的优先位置;根 types 不能自动补齐所有子路径。typesVersions 用于按 TypeScript 版本选择声明,但读取 exports 的解析场景不会再读取该字段。最终应检查真实打包文件,并在独立消费者里验证类型解析与运行时导入,不能只在源码仓库中测试。
- 类型路径与运行入口要对应同一个公开模块
- exports 子路径和条件需要各自完整描述
- 发布产物与独立消费测试比源码目录存在更有证明力
先把单一入口的路径和模块格式写清楚
包根目录、源码目录和最终 dist 是不同位置,types 应相对于 package.json 指向实际分发的声明。若 JavaScript 已迁移到 dist,而 types 仍指向未打包的 src,开发时可能偶然可用,安装后就会丢失类型。
示例用 type: module 表示 .js 的模块语境,并给根导出提供 types 与 default 条件。files 收集 dist,但它不会执行编译,入口文件仍需先生成。声明里的相对引用也要存在于发布集合,不能只检查 index.d.ts 这一份文件。
{
"name": "typed-greeting-kit",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist"]
}支持类型条件的 TypeScript 解析器可选择声明,普通运行时可选择 JavaScript。此配置没有声明任何额外子路径,也没有提供独立 CommonJS 入口;消费者不能据此假定任意深层文件都属于公开 API。
exports 与版本选择需要按真实解析模式理解
现代模块解析会按照 exports 匹配入口和条件,未开放的子路径可能被阻止。根 types 并不意味着包内所有文件都可导入,也不能修复子路径缺失声明。应为公开子入口配置一致的运行文件与类型文件,并核对扩展名和模块格式。
typesVersions 可以为不同编译器语法版本选择不同声明集合,但不能假设它与 exports 永远同时参与。官方模块解析规则明确区分这些场景;如需在 exports 下提供版本化类型条件,应采用该模式实际支持的机制并用目标编译器验证。
检查安装产物而不是只检查仓库源码
发布前查看真实包文件列表,再在独立消费目录解析包名,能发现漏打包、错误大小写和相对类型依赖缺失。测试环境应尽量不继承开发仓库的 paths 别名,否则消费者可能绕过发布入口直接读取源码,掩盖入口配置错误。
公开声明依赖的外部类型也需要在消费者环境可用。升级 TypeScript 语法或模块格式时,应按承诺支持的版本分别验证;源码 tsc 通过只能证明生产方能编译,不能证明所有承诺的消费方式都成立。
容易答错的地方
- types 指向源码就能保证用户获得类型
- 源码可能没有进入发布清单,且路径别名与构建目录在用户环境不同;应指向真实分发的声明并验证完整依赖闭包。
- 有根 types 就不需要给 exports 子路径配类型
- 每个公开子路径有自己的解析过程,根入口不能自动描述全部子模块;必须核对对应声明、运行文件和条件是否完整。
面试官还会怎么问?
typings 和 types 有什么关系?
它们都是主声明入口字段,types 是常见写法。无论使用哪个名称,关键仍是路径真实存在、模块格式匹配且声明描述实际导出。
ESM 和 CommonJS 可以共享同一份声明吗?
要结合导出形状与解析语境判断,双格式包经常需要分别匹配的声明入口和扩展名。不能只复制一个路径就承诺所有加载方式都兼容。
修改 files 字段会自动生成 dist 吗?
不会,它只参与包文件收集。编译和声明生成必须由构建流程完成,再检查实际打包结果,避免发布只有入口配置却缺少文件的包。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。