使用声明合并创建的TypeScript接口编译后合并内容丢失,相关类型导出异常
使用声明合并创建的TypeScript接口编译后合并内容丢失,相关类型导出异常
嘿,我之前也踩过这个坑!这其实是tsup处理声明文件的机制导致的——它在聚合各个文件的类型声明时,没有正确把模块扩展块里的接口合并到顶层接口上,而是原封不动保留了declare module的结构,使得最终的顶层MyType还是空接口,自然keyof MyType就变成never了。
下面给你几个亲测有效的解决思路:
一、改用TypeScript官方工具生成声明文件
tsup自带的声明生成逻辑在处理模块式声明合并时不够完善,不如直接用tsc来生成声明文件,步骤如下:
- 修改tsup配置文件(比如
tsup.config.ts),禁用自动生成声明:
import { defineConfig } from "tsup"; export default defineConfig({ entry: ["src/index.ts"], format: ["esm"], dts: false, // 关闭tsup的声明生成功能 });
- 在
package.json的构建脚本里,先让tsup编译代码,再用tsc单独生成声明:
{ "scripts": { "build": "tsup && tsc --emitDeclarationOnly" } }
这样TypeScript会自行处理所有声明合并逻辑,生成的index.d.ts里会出现完整合并后的MyType接口,MyTypeKeys也能正确识别foo字段。
二、调整声明合并的实现方式
如果不想改动构建流程,也可以通过调整声明写法来规避这个问题:
方式1:将合并内容集中到同一文件
把merged.ts里的接口扩展直接移到MyType.ts中:
// MyType.ts export interface MyType {} declare module "./MyType" { interface MyType { foo: string; } }
这样tsup生成声明时,能直接拿到合并后的完整接口。
方式2:使用全局声明合并(仅适用于全局类型场景)
如果你的MyType是全局可用的类型,可以改成全局接口的形式:
// MyType.ts declare global { interface MyType {} } export type MyType = globalThis.MyType;
// merged.ts declare global { interface MyType { foo: string; } }
这种方式不需要依赖模块扩展,合并逻辑更稳定,但只适合全局类型的场景。
三、优化tsconfig配置
你的tsconfig.json里rootDir设为.可能会引发路径解析问题,建议改成源码所在的src目录(如果你的代码都在src下):
{ "$schema": "https://json.schemastore.org/tsconfig", "include": ["src"], "compilerOptions": { "rootDir": "./src", "declaration": true, "baseUrl": "./src", "target": "esnext", "module": "ESNext" } }
试试上面的方法,应该就能解决你的问题啦!
备注:内容来源于stack exchange,提问作者Danny Delott
相关产品推荐
相关产品推荐

