如何为CommonJS JS模块添加TS接口,实现类与接口同时导入
我有一个开启了allowJS配置的TypeScript NodeJS项目,其中一个CommonJS规范的JavaScript文件包含Logger类。我希望为这个JS文件扩展ILogger接口声明,这样就能在TypeScript文件中使用import {Logger, ILogger} from './logger';的方式导入。
根据相关技术文档的说明,TypeScript不支持JSDoc的@interface注解,因此需要使用.d.ts声明文件实现。但我未找到将包含类的JS文件和包含接口的声明文件结合、让二者都能从TS文件导入的方法,以下是我的三次尝试及对应问题:
尝试1:同命名logger.js + logger.d.ts,TypeScript仅读取d.ts类型
将类放在js文件,接口放在同名d.ts文件的方案不生效,TypeScript只会从d.ts文件读取类型,忽略JS文件中的类:
报错信息:app.ts:1:18 - error TS2724: '"./logger"' has no exported member named 'Logger'. Did you mean 'ILogger'?
相关代码:
// logger.js //@ts-check exports.Logger = class Logger { /** * @param {string} msg * @returns {void} */ info(msg) { console.log(msg); } }
// logger.d.ts export interface ILogger { info(msg: string): void }
// app.ts import {ILogger, Logger} from './logger'; export function f(logger: ILogger) { logger.info('f'); } export function main() { const logger = new Logger(); f(logger); }
// tsconfig.json { "extends": "@tsconfig/recommended/tsconfig.json", "compilerOptions": { "allowJs": true } }
// package.json { "devDependencies": { "@tsconfig/recommended": "^1.0.1", "typescript": "^4.4.3" } }
尝试2:通过三斜杠指令引用.d.ts做模块扩展,TypeScript仅读取logger.js的类
我参考模块扩展(declare module '…')方案,用三斜杠指令(<reference path="…"/>)引用声明文件扩展./logger模块,但扩展不生效,TypeScript仅读取JS文件中的类,忽略declare module的声明:
报错信息:app.ts:2:9 - error TS2724: '"./logger"' has no exported member named 'ILogger'. Did you mean 'Logger'?
我添加的全局声明没有触发TypeScript报错,说明types.d.ts文件被正常读取了。
相关代码:
// types.d.ts: declare module './logger' { export interface ILogger { info(msg: string): void } } declare function myGlobalFunc(): void
// app.ts: /// <reference path="./types.d.ts"/> import {ILogger, Logger} from './logger'; export function f(logger: ILogger) { logger.info('f'); } export function main() { const logger = new Logger(); myGlobalFunc(); f(logger); }
其余logger.js、tsconfig.json、package.json代码同尝试1。
尝试3:在app.ts内直接扩展logger.js模块可生效,但复用性差
我在TypeScript模块内直接对./logger做模块扩展,没有触发TypeScript报错,可以正常运行。但该方案的问题是每次使用时都需要重复写模块扩展代码,或者额外导入做了扩展的TS模块,复用性差。
相关代码:
// app.ts import {ILogger, Logger} from './logger'; declare module './logger' { export interface ILogger { info(msg: string): void } } export function f(logger: ILogger) { logger.info('f'); } export function main() { const logger = new Logger(); f(logger); }
其余logger.js、tsconfig.json、package.json代码同尝试1。
请问有没有更优的方案,可以为JavaScript CommonJS模块添加TypeScript接口声明?
方案1:补全同名声明文件(推荐)
尝试1失效的核心原因是:当存在同名.d.ts文件时,TypeScript会优先读取声明文件的类型定义,不会再自动推导JS文件的导出内容,只需要在声明文件中补全JS文件已有导出的类型即可:
修改logger.d.ts内容如下:
// 声明JS文件中实际导出的Logger类的类型 export class Logger { info(msg: string): void } // 声明新增的ILogger接口 export interface ILogger { info(msg: string): void }
不需要额外修改其他配置,即可直接在任意TS文件中通过import {Logger, ILogger} from './logger'正常导入,类型检查和JS运行逻辑都不会受影响,后续Logger类有方法更新时同步修改声明文件即可。
方案2:JSDoc定义接口(无需额外声明文件)
如果不想单独维护.d.ts文件,也可以直接在logger.js中通过JSDoc的@typedef定义接口,TypeScript可直接识别该类型:
修改logger.js内容如下:
// logger.js //@ts-check /** * @typedef {object} ILogger * @property {(msg: string) => void} info */ exports.Logger = class Logger { /** * @param {string} msg * @returns {void} */ info(msg) { console.log(msg); } }
该方案不需要额外的声明文件,也可以正常导入ILogger和Logger使用。
内容的提问来源于stack exchange,提问作者yonran

