Window in a web page serves a dual purpose. it implements the Window
interface representing the web page main view, but also acts as an alias to the global namespace. All global variables are accessible on the window
object at run-time; this applies to builtin JS declarations like Array
, Math
, JSON
, Intl
as well as global DOM declarations like HTMLElement
, Event
, HTMLCollection
, etc...
TypeScript today does not model this behavior. For instance window.Math
is not defined, so is all the HTMLElement
family of declarations.
Most of the patterns window
is used to access global declarations are anti-patterns (see a good article about window usage), the main exception is testing for API existence, e.g.: if (window.MutationObserver) { ... }
. There is not really a legal way to do this out-of-the-box in TS today.
Our recommendation has been to manually add a declaration for the global object on Window
, e.g.:
declare global {
interface Window {
MutationObserver?: typeof MutationObserver;
}
}
This is a. inconvenient and b. not easy to find if you are new to TS. Here is a list of issues we have so far related to this issue:
window.Blob
window.createImageBitmap
window.Math
window.URL
window.MutationObserver
window.PointerEvent
window.MouseEvent
window.Element
window.PerformanceObserver
It is also worth noting that the same problem occurs with self
in web workers and global
for node
Possible options here:
Window
to extend from it. Window
.Can I bring up a third option? I think option 1 is the more flexible way of reducing duplicate declarations. However both options 1 & 2 have limitations:
supporting a global type and having Window
extend it is pretty good because it is simple, and other environments (eg Global
in node.d.ts
) can use this too. But it would not solve the duplication problem in custom sandboxed environments, whereas the equally simple option 3 (below) would solve it.
generating lib.d.ts
with all globals mirrored on Window
- but then do other environments miss out? node.d.ts
has similar duplication of declared globals with the Global
interface, and then there are web workers, custom environments, etc.
A third option would be a declaration that tells the compiler to augment the global environment with all the declarations in a particular interface. Eg declare global extends Window
for lib.d.ts
, declare global extends Global
for node.d.ts
, etc.
This is similar to option 1 but in reverse, and allows additional scenarios like sandboxing. An example of this scenario is given in https://github.com/Microsoft/TypeScript/issues/10050#issuecomment-236596782. Note that if option 1 was used, then there would be no way to write a set of declarations that are just a normal interface in the host code but are are globals in the sandboxed code. They would all still have to be duplicated.
Custom environments may be niche, but it would be nice if the mechanism chosen here would work for them too.
window.Worker
, same story...
Most of the patterns window is used to access global declarations are anti-patterns (see a good article about window usage),
the main exception is testing for API existence, e.g.: if (window.MutationObserver) { ... }. There is not really a legal way to do this out-of-the-box in TS today.
One other valid usecase is if you are working with different contexts. For example we could be in another DOM (inside an HTMLObjectElement
) so SVGElement
is a different one:
if ((myElement instanceof (myElement.ownerDocument.defaultView as any).SVGElement)) {
// yeah, really a svg
}
I just hit on this when I tried to migrate js library that uses a lot of if(windows.Something)
test.
I though that it should be fixed in TS 3.4 after https://github.com/Microsoft/TypeScript/pull/29332
However I see that window
is still declared as declare var window: Window;
not as Window & typeof globalThis
as described in globalThis PR. @sandersn Are there plans to change it?
@mpawelski Yes, by 3.5. I just haven't had a chance yet.
What is the status on this?
Hey guys, so eventually, how nowadays with "typescript": "3.4.5" I can declare that inside window object, I have some variable with some type?
E.g. I have window.foo = { bar: 'baz' }, how I can declare it? Old way of doing that, I mean
declare global {
interface Window {
foo: { bar: string }
}
}
is now produces this error
TS2669: Augmentations for the global scope can only be directly nested in external modules or ambient module declarations. Alt+Shift+Enter Alt+Enter
UPD: it looks like just adding a record in some d.ts file is ok. Like this:
interface Window {
foo: { bar: string }
}
But as I understood in such case I can't import some types in that file and use them in declaration, instead of string
for example. So what if I want to do something like that:
import { MyTypeFromSomewhere } from './somewhere.d.ts'
interface Window {
foo: { bar: MyTypeFromSomewhere }
}
?
@Alendorff just to clarify: you had TS2669… error inside global.d.ts or index.d.ts?
@inoyakaigor sorry, didn't understand your question, what's the difference between these 2 files if both are .d.ts ? I've not mentioned any specific filenames or something like that.
@Alendorff I had the same TS2669 error for the code inside global.d.ts and I'm trying to figure out if the file name is relevant to this error. That's why I'm asking about the file name
@inoyakaigor I don't have global.d.ts so filename is irrelevant I think
For anyone else landing here from a search, the following works to define new things on window
and will get rid of Augmentations for the global scope can only be directly nested in external modules or ambient module declarations. ts(2669)
:
export {};
declare global {
interface Window {
// your types here!
}
}
I want the dynamic key in window object. How can I do this? For example
window.lastName
or window.surname
Can I do this?
declare global {
interface Window {
[type: string]: any
}
}
Most helpful comment
Hey guys, so eventually, how nowadays with "typescript": "3.4.5" I can declare that inside window object, I have some variable with some type?
E.g. I have window.foo = { bar: 'baz' }, how I can declare it? Old way of doing that, I mean
is now produces this error
UPD: it looks like just adding a record in some d.ts file is ok. Like this:
But as I understood in such case I can't import some types in that file and use them in declaration, instead of
string
for example. So what if I want to do something like that:?