Reputation: 24077
I have a declaration file in my TypeScript project like so:
// myapp.d.ts
declare namespace MyApp {
interface MyThing {
prop1: string
prop2: number
}
}
This works great and I can use this namespace anywhere in my project without having to import it.
I now need to import a type from a 3rd party module and use it in my ambient declaration:
// myapp.d.ts
import {SomeType} from 'module'
declare namespace MyApp {
interface MyThing {
prop1: string
prop2: number
prop3: SomeType
}
}
The compiler now complains that it can't find namespace 'MyApp', presumably because the import prevents it from being ambient.
Is there some easy way to retain the ambient-ness of the declaration whilst utilising 3rd-party types?
Upvotes: 55
Views: 18222
Reputation: 153
In my case, I have a global ambient variable created by electron for the API to interact with the node process. I needed to define an interface with a property type that referenced a class from a third-party dependency rather than a type/interface.
The TS 2.9 import() syntax worked for me when using a type/interface, but not a class. Specifically I am trying to use rsjx.Observable in my interface. I was able to find a way to handle this that I haven't seen mentioned anywhere. I hope it can help someone else.
You can combine module + ambient by making a module with types you want to export, then use the TS 2.9 import() syntax to pull that into an ambient declaration.
// types/electronAPI.ts
import { IpcRendererEvent } from 'electron'
import { Observable } from 'rxjs'
export type ThingStatus = 'on' | 'off'
export interface MyElectronAPI {
thingObservable: Observable<[IpcRendererEvent, ThingStatus]>
}
// types/global.d.ts
declare type MyElectronAPI = import('./electronAPI').MyElectronAPI
const electronAPI: MyElectronAPI
// someFile.tsx
// I get all my types :)
elecronAPI.thingObservable.subscribe(([_e, status]) => { ... } )
Upvotes: 0
Reputation: 171
Don't know if you're still looking for an answer, but this is the correct way to handle it and still be able to e.g. define generic modules, not just named namespaces: (original answer this is based on)
// myapp.d.ts
declare namespace MyApp {
import {SomeType} from 'module'
interface MyThing {
prop1: string
prop2: number
prop3: SomeType
}
}
Upvotes: 1
Reputation: 61382
Yes, there is a way. It has become easier in TypeScript 2.9 by using import() a type but it's also possible in earlier versions of TypeScript.
The following file is a script. Things declared in a script are added to the global scope. This is why you can use MyApp.MyThing
without importing it.
// myapp.d.ts
declare namespace MyApp {
interface MyThing {
prop1: string;
prop2: number;
}
}
Scripts are limited in that they cannot import anything; when you add an import
, the script becomes a module. I think it's weird to say the least, but it is what it is. What matters is that things defined in a module are scoped to that module, and are not added to the global scope.
However, modules can add declarations to the global scope too, by putting them inside global
:
// myapp.d.ts
import {SomeType} from 'module';
declare global {
namespace MyApp {
interface MyThing {
prop1: string;
prop2: number;
prop3: SomeType;
}
}
}
This file is a module, but it adds a declaration of MyApp.MyThing
to the global scope, so you can still use MyApp.MyThing
in other TypeScript code without importing it.
Note that using the .d.ts
extension has nothing to do with you being able to access the interface without importing it. Both of the above files could have been .ts
files and would still behave exactly the same.
Upvotes: 64
Reputation: 2506
Since TS 2.9 this is possible with import()
:
// myapp.d.ts
declare type SomeType = import('module').SomeType;
declare type SomeDefaultType = import('module-with-default-export').default;
declare namespace MyApp {
interface MyThing {
prop1: string;
prop2: number;
prop3: SomeType | SomeDefaultType;
}
}
Upvotes: 31
Reputation: 9522
Unfortunately, no. As you already figured out this only works with internal code, e.g. without external dependencies. You should either go with exporting your namespace, or go for export of classes and use ES6 modules. But both will result in you requiring to import
your stuff. Something that you're trying to avoid, as I believe.
Personally, I find it more comforting to actually use imports (even internals) throughout the code. This for the simple reason that when opening a specific file (class), all its dependencies are immediately visible.
A thorough example was already addressed in the question "How to use namespaces with import in TypeScript".
Note for others: the "namespace being available internally" is also the reason why I'm not considering this a duplicate question.
Upvotes: 2