DefinitelyTyped has type definitions for many libraries, but often enough I cannot find a good way to use them when the Javascript implementation is separated from the Typescript, like when a library assigns itself to a property of the window through a
<script src="https://example.com/library.js">
tag, and when the JS bundle I'm managing is in another separate script. (Even though bundling everything together including the library is the standard and reliable method, assume for the sake of the question that I do not have the option of importing the library into my TS project proper.) For example, say I find a nice looking definition file for a library named myLib
:
// my-lib.d.ts
export const doThing1: () => number;
export const doThing2: () => string;
export const version: string;
export interface AnInterface {
foo: string;
}
export as namespace myLib;
In JS, I can use myLib by calling window.myLib.doThing1()
and window.myLib.doThing2()
. How may I import the shape of the whole window.myLib
object so that I can declare it as a property of window
? I can see that I can import the exported interfaces, eg:
// index.ts
import { AnInterface } from './my-lib';
const something: AnInterface = { foo: 'foo' };
console.log(something.foo);
This works, but I want access to the shape of the actual library object and its property values (the functions and strings and such), not just the interfaces. If I do
import * as myLib from './my-lib';
then the myLib
identifier becomes a namespace, from which I can reference the exported interfaces, but just like above, I still have no access to the export const
and export function
shapes from my-lib.d.ts
. (And, of course, trying to use the imported namespace to declare the library object doesn't work: Cannot use namespace 'myLib' as a type.
Even if I could do that, that wouldn't necessarily be safe, because the library packaged for the browser may well be structured slightly differently from the library's Node export object)
If I manually copy and paste parts of the d.ts
into my own script, I can hack together something that works:
// index.ts
declare global {
interface Window {
myLib: {
doThing1: () => number;
doThing2: () => string;
version: string;
};
}
}
But this is messy, time-consuming, and surely not the proper way to go about doing something like this. When I encounter this sort of situation, I would like do be able to do something short and elegant like:
// index.ts
import myLibObjectInterface from './my-lib.d.ts'; // this line is not correct
declare global {
interface Window {
myLib: myLibObjectInterface
}
}
Some definition files include an interface for the library object, like jQuery, which does:
// index.d.ts
/// <reference path="JQuery.d.ts" />
// jQuery.d.ts
interface JQuery<TElement = HTMLElement> extends Iterable<TElement> {
// lots and lots of definitions
Then everything's just fine - I can just use interface Window { $: jQuery }
, but many libraries not originally created for browser consumption do not offer such an interface.
As mentioned before, the best solution would be for the library's implementation to be integrated with the TS project, allowing for both the library and its types to be import
ed and used without fuss, but if that's not possible, do I have any good options left? I could examine the properties on the real library object and add an interface to the definition file which includes all such properties and their types, but having to modify the semi-canonical source definition file(s) accepted by DT and used by everyone else feels wrong. I'd prefer to be able to import the shapes of the definition file's exports, and create an interface from them without modifying the original file, but that may not be possible.
Is there a more elegant solution, or are the definition files I've happened to come across simply insufficiently suited for my goal, and thus must be modified?