Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a union of libraries, for code that runs in multiple environments #52433

Open
5 tasks done
jakearchibald opened this issue Jan 26, 2023 · 8 comments
Open
5 tasks done
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@jakearchibald
Copy link

Suggestion

πŸ” Search Terms

webworker, dom, node, isomorphic

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Sometimes code is designed to run in multiple environments. For example, code may run in both nodejs and the browser, or a web page and a web worker.

Currently, you can use tsconfig to say your environment is both a 'DOM' and 'webworker', but that's never true in the wild. It's 'DOM' or 'webworker'.

Take this example (playground link):

localStorage.set('foo', 'bar');
importScripts('hello.js');

TypeScript is fine with this if you include both 'DOM' and 'webworker' libs, but this code will fail in a webworker, because localStorage doesn't exist, and it will fail in a page, because importScripts doesn't exist.

This would be solved by a feature that allows developers to specify the environment as A or B. The code above would show errors, because localStoage and importScripts may not exist.

πŸ“ƒ Motivating Example

Hopefully the above description provides this.

πŸ’» Use Cases

Hopefully the above description provides this.

@MartinJohns
Copy link
Contributor

You can already have multiple tsconfig and change them in VSCode (or editor of your choice). Otherwise it sounds like a duplicate of #37884.

@jakearchibald
Copy link
Author

Having to switch configs is a bad developer experience here. Although, if the editor performed the checking with multiple configs and presented a union of the errors, that might satisfy this feature request.

Although, a single config that knows that the types are DOM or webworker would be faster than running two checks.

@oliverdunk
Copy link

To take the example a bit further, imagine wanting to write this and tell developers it is safe to run in both workers and normal scripts:

if ("localStorage" in globalThis) {
  localStorage.set('foo', 'bar');
}

This should compile. localStorage doesn't exist in a worker, but this code is fine because of the condition check.

Right now, for this code to build you have to include the DOM library. But as soon as you do that, TypeScript will happily let you remove the condition and write potentially unsafe code!

@RyanCavanaugh
Copy link
Member

The problem we've had here in the past is that a huge amount of code can be seen to be running in one environment by inspection, but not in a way that's susceptible to static analysis. An example would be something like writing

if ("document" in globalThis) {
  window.addEventListener("click", () => {
    localStorage.set("foo", "bar");
  }
}

This code is obviously fine, but TS would think that window and localStorage are possibly-not-present because there's no linkage between one DOM thing being present and everything else.

There's nothing today stopping someone from auto-genning variant DOM/webworker/etc libraries with every top-level declaration marked possibly-undefined. Real-world usage of such output would be good evidence that this kind of feature would be usable in practice.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Jan 26, 2023
@jakearchibald
Copy link
Author

@RyanCavanaugh in those cases, I think it's reasonable to put a type guard on the global object, then access the APIs via the global object.

Although, typescript could be smart and realise that if the type of globalThis is guarded, global access could reflect that.

@jakearchibald
Copy link
Author

As in:

function globalIsWindow(global: any): global is Window {
  return 'document' in global;
}

if (globalIsWindow(self)) {
  self.addEventListener("click", () => {
    self.localStorage.set("foo", "bar");
  });
}

But if TypeScript could determine that self represents the global-this, then:

if (globalIsWindow(self)) {
-  self.addEventListener("click", () => {
+  addEventListener("click", () => {
-    self.localStorage.set("foo", "bar");
+    localStorage.set("foo", "bar");
  });
}

@davidbarratt
Copy link

Perhaps this is what @jakearchibald is saying, but I noticed that predicates on globalThis don't work:
https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDAzgdRmAJnA7gCkwAcQoAuRcAazDzAEoLjTkVENs9EBvAKEUQAnAKZQQgpFACeRYXGDIwJKIgC86xAHI4AIwBWw6JsQAyE4uWIAhBrAgANvdPnNOCCAC2wsFGOYLpADcvAC+vLwwCvhWqBw4BADm9roAhvYAKgAWqPT0PPyISakZ2SgAdG6e3lBlKVhYAKIAbtUAMqhQ3sKC+JrAcO4omgA0iPh5qgB8+QUh9MEhQA

If that could work, then it could be simplified to something like this:

function isClient(): globalThis is Window {
  return 'document' in globalThis;
}

where global keywords can be used in predicates.

@robbiespeed
Copy link

Something like #50424 would make it a lot easier to create these kinds of isomorphic type guards, without needing to fork global type libs as @RyanCavanaugh suggests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants