microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.86k stars 12.36k forks source link

Assign existing interface to global (globalThis) #57353

Open reece-white opened 6 months ago

reece-white commented 6 months ago

🔍 Search Terms

✅ Viability Checklist

⭐ Suggestion

Currently, to provide types to globalThis, I can update the global.d.ts similar to:

declare global {
    interface Navigator {
        ...
    }
}

However, if I want to set the interface of the global object (e.g. to give it the an interface that extends theWindow interface, I'd have to do something like this at the top of each .ts file that references globalThis like the below:

interface MyWindow extends Window {}

declare let globalThis: MyWindow;

I'd like to see a way where I can modify the whole interface of the global object.

📃 Motivating Example

Override the whole ` interface.

💻 Use Cases

Working across multiple runtimes which share the same global interface without having a large global.d.ts with copy/pasted interfaces from utility types.

ScottMorse commented 3 months ago

I ran into a need for something like this, because I'm trying to create very accurate types for the runtime environment of KWin scripts, which uses QtScript for running JavaScript, which as this document states, follows ECMA spec, but in reality it does not fit neatly into any ES version. I'm working on this mainly for myself at the moment, but I want a portable, clean solution regardless, as I will publish my packages if they feel complete enough.

Given what I've introspected via scripts in latest KWin, QtScript's globals seem to match ES3, but there are a handful of globals available only in later versions present. One way I've done this introspection is by simply manually checking every key in globalThis that's available for each lib and printing whether the global is present.

Thanks to QtScript not neatly falling into any given "lib" option allowed in tsconfig.json, I started looking for a way to either override globalThis entirely, cherry picking which globals are available, or removing certain globals or re-typing them as undefined or never. Neither of these seem possible.

As context, I already have a package I'm currently calling @kwin-ts/types that is used primarily for including declarations via "types": ["@kwin-ts/types"] in a tsconfig. Its original use was just for declaring globals unique to KWin scripts, such as workspace.

I tried setting my "lib" to ES3 so I could add declarations for only the extra ECMA globals into @kwin-ts/types by importing them from node_modules (e.g. node_modules/typescript/lib/lib.es5), but this didn't work, as importing from any of these lib modules means including all of their global declarations as well, so only including specific types was not possible. If it was simply possible to import types without hitting the global declarations, that may have been good enough for me, but the way the exports are organized doesn't allow this.

I started looking into the supported ability to override lib modules themselves, but I didn't see this as a good solution, because it doesn't seem that I can create my own lib name to reference, so I don't want to introduce the confusion of overriding a lib like "es5," when I'm not really patching ES5 but defining what should really be considered a custom lib. It's also not portable, as it would require consumers of my packages to use this in their package.jsons, which is a strange pattern to ask of a package consumer, and it will have side effects on other tsconfigs in a project not intending to provide KWin typings. On top of this, I realized I would never get around the same issues I had in the previous paragraph when it came to customizing the ES_ declarations.

My current solution has been to have the tsconfig for KWin scripts specify "lib": [] so that no globals are defined from a lib, and then my package is still included in "types": ["@kwin-ts/types"], whose declaration includes what is essentially a modified version of lib.es5.d.ts from TypeScript. This way, I can have full control over what's in the global scope, and this seems like a common enough requirement for package consumers dealing with a special runtime environment, as other situations also demand a specific combination of the "lib" and "types" options, such as React tests written in TypeScript running in Jest.

However, if it was possible to be given more control over the global scope, the code for @kwin-ts/types's global declarations could be cleaner, as only the handful of modifications to the lib's globals would need to be present, rather than a managed copy of an entire lib module. I do understand this is a rare use case with TypeScript, as the number of people running into niche runtimes is probably very low, and my current solution seems good enough for what I'm looking for at the moment.