使用checkJs构建多格式TypeScript声明文件的最佳实践
针对ESM/CJS双模块格式的声明文件生成最佳实践
1. 拆分双入口源文件(共享核心逻辑)
避开单源文件转译双格式带来的声明不一致问题,核心思路是将业务逻辑抽离到内部文件,再分别创建ESM和CJS的入口文件:
- 把核心代码放在无模块格式绑定的内部文件(比如
src/core.js),仅实现功能不处理导出格式 - 创建两个入口文件:
- ESM入口:
src/index.mjs,用ESM语法导出核心逻辑:export * from './core.js'; - CJS入口:
src/index.cjs,用CJS语法导出核心逻辑:const core = require('./core.js'); module.exports = core;
- ESM入口:
- 分别针对两个入口生成声明文件:
- 生成ESM声明:
tsc --module ESNext --declaration --emitDeclarationOnly src/index.mjs - 生成CJS声明:
tsc --module CommonJS --declaration --emitDeclarationOnly src/index.cjs
这样生成的.d.ts文件会准确对应各自的模块导出结构,不会出现ESM源生成CJS声明却保留ESM细节的问题
- 生成ESM声明:
2. 用JSDoc强化类型准确性
因为你使用checkJs,在源文件中补充JSDoc注释可以帮助TypeScript更精准地生成声明:
- 在核心文件中给导出成员添加类型注释,比如:
/** * 返回数字1的示例函数 * @returns {number} */ export function foo() { return 1; } - 在CJS入口文件中添加模块标注,明确模块类型:
这些注释能让TypeScript正确解析CJS导出的结构,避免声明文件出现偏差/** @module */ const core = require('./core.js'); module.exports = core;
3. 借助API Extractor生成精准声明
如果需要基于转译后的JS文件生成声明,可以使用@microsoft/api-extractor,它能处理转译后的模块结构,生成符合对应格式的声明:
- 先通过转译工具(如Rollup、esbuild)将源文件分别转译为ESM和CJS格式的JS文件
- 针对每个转译后的JS文件,配置API Extractor的
tsconfig.json(对应ESM/CJS的module设置),运行api-extractor run生成声明 - API Extractor会自动处理转译后的导出逻辑,确保声明文件和实际JS导出结构完全匹配
4. 正确配置package.json的exports字段
生成双格式声明后,需要在package.json中明确不同导入方式对应的声明文件,让TypeScript自动匹配:
{ "exports": { ".": { "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } } }
这样无论是用import(ESM)还是require(CJS)导入你的模块,TypeScript都会加载对应的声明文件
5. 校验声明文件的一致性
生成声明后,需要验证其准确性:
- 用
tsc --noEmit对声明文件和对应JS文件进行类型检查,确保没有类型不匹配 - 编写测试用例,分别用ESM和CJS方式导入模块,检查编辑器的类型提示是否符合实际导出
- 可以使用
dtslint工具检查声明文件是否符合TypeScript的规范
内容的提问来源于stack exchange,提问作者Brett Zamir
相关产品推荐
相关产品推荐

