TypeScript: Unable to extend window in TS 3.6
TypeScript Version: 3.6.2
Search Terms:
- globalThis
Code
This code passed the type checker in TS 3.5:
interface MyWindow extends Window {
foo: string;
}
(window as MyWindow).foo = 'bar';
but fails with this error in TS 3.6:
Conversion of type 'Window & typeof globalThis' to type 'MyWindow' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Property 'foo' is missing in type 'Window & typeof globalThis' but required in type 'MyWindow'.ts(2352)
Following the release notes, I can work around the issue by changing from interface
to type
and using an intersection:
type MyWindow = (typeof window) & {
foo: string;
}
(window as MyWindow).foo = 'bar';
but this feels more obscure than the old way. Is this WAI? What’s the rationale for the non-extendable Window?
Expected behavior:
An interface should be able to extend Window
.
Actual behavior:
The error above.
Playground Link: 3.6 isn’t on the playground yet.
Related Issues:
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 28
- Comments: 18 (10 by maintainers)
Commits related to this issue
- tainted ref: don't use TS 3.6 just yet Typescript 3.6 causes an incompatibility in eslint (or at least the risk of it) which makes for quite verbose warnings. Cf. Ie0bfd9871384c4daf9a3d4a8e5801edcc0a... — committed to wikimedia/mediawiki-extensions-Wikibase by wiese 5 years ago
- Update git submodules * Update Wikibase from branch 'master' to 0f1c1752140e77f6c90fcc21271178a5d9bf9358 - Merge "tainted ref: don't use TS 3.6 just yet" - tainted ref: don't use TS 3.6 just ye... — committed to wikimedia/mediawiki-extensions by deleted user 5 years ago
- bridge: update to TypeScript 3.7 Apply latest TypeScript version[0] and fix resulting complaints. The question of how to extend window may be noteworthy[1] as the `declare global` in MwWindow does so... — committed to wikimedia/mediawiki-extensions-Wikibase by wiese 4 years ago
- Update git submodules * Update Wikibase from branch 'master' to a5e6df0da5fa9b4cd9af63cb187a8477dc289775 - Merge "bridge: update to TypeScript 3.7" - bridge: update to TypeScript 3.7 A... — committed to wikimedia/mediawiki-extensions by deleted user 4 years ago
Augmentation of
window
is usually done by using interface merging:or
For non-module scenarios.
quick and dirty 💥
(window as any).foo = "bar";
@fatcerberus I don’t believe this statement is correct:
I put this in a new file in my project (
foo.ts
) that’s not imported anywhere:Then, in another file, I can write:
without importing
foo
. There’s no error and I see the documentation. So I do not believe the augmentation is scoped to the module at all. It is as global as thedeclare global
implies.@orta Can I ask why it needs to be registered as a module?
For me this doesn’t work until the add the
export {}
- I’m just trying to understand why.Why not include
globalThis
at the point of the assertion?This is indeed working as intended.
window.foo = 'bar'
's runtime behaviour is best represented by an interface merge, not a subtype. Can you actually even extend window? It seems like a convenient lie to the compiler.As for the weird error, Window has a numeric index signature
{ [n: number]: Window }
so thatwindow[0] : Window
. But when making sure anextends
is legal, the compiler checks that numeric properties from the other side of the intersection have typeWindow
. Normally, that’s properties with names like'0'
and'1'
, but guess whatNaN
andInfinity
are also numbers, so we check that their types are compatible with Window’s index signatures. SinceNaN: number
andnumber
is not assignable toWindow
, the compiler reports that as an error.@fatcerberus the property is named
NaN
, which is definitely a number. The funniest number.To force the file to be registered as a module, not a script
The solution we recommend at Google is to make a local subtype for explicit casts, which avoids polluting the global type definitions. Because of the
NaN
/Infinity
issue, we have a library withwhich can then be used like so: (playground)
If you want the type augmentation to be global and ambient, this works fine:
The trick is, you must put this in a file with the extension
.d.ts
(and there must not be a same-named file with just the extension.ts
in the same directory. Then this will function as an ambient type and declaration merging will kick in. No import required.