How to merge instance with a namespace in a declaration file?

Viewed 341

How can I create a .d.ts file that represents the following JavaScript API?

  • Loader is a class which is not exported globally
  • loader is exported globally, and is an instance of Loader
  • loader.Loader is the Loader class itself
  • Loader has a static method create, which creates an instance of Loader

So essentially loader behaves like a namespace that includes the class Loader, but is also a instance of Loader. The following doesn't work, because TypeScript won't merge a namespace with a value:

declare namespace loader {
    class Loader {
        id: number;
        name: string;
        load(url: string): any;

        static create(name: string, parent?: Loader): Loader;
    } 
}

// This doesn't work, because a namespace cannot be merged with a value:
declare const loader: loader.Loader;

Using this API from JavaScript looks like this:

// Call load() on the default Loader instance
loader.load('/foo/index'); 

// loader.Loader is a class, and loader is an instance of it
if(loader instanceof loader.Loader) {  
    console.log('Yes, loader is a Loader');
}

// Create a new Loader instance
var myLoader = loader.Loader.create('my-loader'); 

// Call load on this Loader instance
myLoader.load('/bar/something'); 

// myLoader is also an instance loader.Loader
if(myLoader instanceof loader.Loader) {  
    console.log('Yes, myLoader is a Loader');
}
1 Answers

I don't quite understand what do you mean when you say "Loader is not exported globally" and "loader is exported globally". A thing can either be exported from module and available for importing in other modules, or not exported - there is no concept of "global export". On the other hand, a thing can be declared to exist in global namespace, like global variables do, to be available in every module without importing anything explicitly.

If you want loader with extra Loader property to be exported from a module, there is no need to declare a namespace - you can add Loader property using intersection type when declaring loader instance:

loader.d.ts

declare class Loader {
    id: number;
    name: string;
    load(url: string): any;

    static create(name: string, parent?: Loader): Loader;
}


declare const loader: Loader & { Loader: typeof Loader };

export {loader};  // Loader is not exported

All your examples compile after importing loader like this:

import {loader} from './loader';


// Call load() on the default Loader instance
loader.load('/foo/index');

// loader.Loader is a class, and loader is an instance of it
if(loader instanceof loader.Loader) {
    console.log('Yes, loader is a Loader');
}

// Create a new Loader instance
var myLoader = loader.Loader.create('my-loader');

// Call load on this Loader instance
myLoader.load('/bar/something');

// myLoader is also an instance loader.Loader
if(myLoader instanceof loader.Loader) {
    console.log('Yes, myLoader is a Loader');
}
Related