I have a TypeScript NodeJS project with allowJS, and one of my CommonJS JavaScript files contains a class Logger. I would like to augment the JS file with an interface declaration ILogger, so that I can import {Logger, ILogger} from './logger'; in TypeScript files.
According to the answer to Using interface defined in JSDoc in Typescript, TypeScript does not support JSDoc @interface, so I should be using a declaration file .d.ts instead. But I can’t figure out how to combine a JS file containing a class with a declaration file containing an interface so that both of them can be imported from a typescript file.
Attempt 1: Both logger.js and logger.d.js: TypeScript only reads types from logger.d.ts
Putting the class in a js file and the interface in a d.ts file with the same name excluding extension does not work, because typescript only reads the types from the d.ts file and ignores the class in the JS file:
Error: 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.js
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"
}
}
Attempt 2: Augmenting module in a .d.ts file referenced by <reference path=…/>: TypeScript only reads types from logger.js
When I try module augmentation (declare module '…') of the ./logger JS module with a declaration file referenced in a triple-slash directive (<reference path="…"/>), the augmentation does not seem to work and TypeScript only reads the class from the JS file and ignores the the declare module:
app.ts:2:9 - error TS2724: '"./logger"' has no exported member named 'ILogger'. Did you mean 'Logger'?
TypeScript gives no error for a global declaration that I added, so I know that it is reading the types.d.ts file.
// logger.js, tsconfig.json, package.json: same as in Attempt 1
// 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);
}
Attempt 3: Augmenting logger.js module in app.ts does work but is not cleanly reusable
When I try module augmentation (declare module '…') of the ./logger JS module with a declaration in a TypeScript module, it does work without any TypeScript errors. I guess the only problem is that I have to duplicate this module extension or import the TypeScrpt module that does the extension each time I want to use the extension.
// logger.js, tsconfig.json, package.json: same as in Attempt 1
// app.js
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);
}
Is there a better way to add a TypeScript interface declaration to a JavaScript CommonJS module?