Skip to content

In "declare global" blocks, allow access of block-scoped variables in globalThisΒ #56442

Closed
@DanKaplanSES

Description

@DanKaplanSES

πŸ” Search Terms

globalThis, declare global block, let, const, var, property

βœ… Viability Checklist

⭐ Suggestion

This wouldn't be a breaking change in existing JavaScript code. When it comes to TypeScript code, I'm not as sure...

Before my request, here's an excerpt of #30477 for context:

TypeScript Version: 3.4.0-dev.20190316

Search Terms: 3.4 globalThis let const property

Code

// Compile with `tsc -t es2015`
const foo: number = 42;
const bar: null = globalThis.foo; // Type 'number' is not assignable to type 'null';

For reference, the following demonstrates how these bindings behave in browser and node, respectively:

<script>
const foo = 42;
alert(window.foo); // --> "undefined"
</script>
const foo = 42;
console.log(global.foo); // --> "undefined"

Expected behavior: TypeScript models types consistently with what's actually happening in VMs. Specifically, variables bound by let or const are not translated to properties on globalThis.

Actual behavior: TypeScript converts let/const bindings onto properties of globalThis, but these properties do not exist in ES2015+ output (when let and const are retained in the output).

The example and fix makes sense to me and I'm not requesting a change for that. But is there any chance #30510 / #30477 could work the way it used to within declare global blocks? In other words, I'm requesting for all of this code to compile:

export {}

declare global {
  const c: string;
  let l: string;
  var v: string;
}

globalThis.c = "s";  // errors: Property 'c' does not exist on type 'typeof globalThis'.
globalThis.l = "s";  // errors: Property 'l' does not exist on type 'typeof globalThis'.
globalThis.v = "s"; // compiles

πŸ“ƒ Motivating Example

The examples in the #30477 excerpt make sense to me because they're analogous to runtime behavior. e.g., I can intuit const foo: number = 42; shouldn't exist on globalThis.

But declare global { ... } doesn't exist at runtime in at all. It's a part of TypeScript that's erased at transpile. So if this is TypeScript-only, and it's named "declare global," I would assume my consts and lets apply to global scope.

Well it does.

Sometimes.

As a newb, it feels kind of arbitrary. For example, this code compiles like I would expect:

export {}

declare global {
  interface Window {
    foo: number;
  }

  function Y(): void;
}

globalThis.window.foo;
globalThis.Y();

And if I remove globalThis from my first example, it behaves the way I would expect:

export {}

declare global {
  const c: string;
  let l: string;
  var v: string;
}

c = "s";  // Cannot assign to 'c' because it is a constant.
l = "s";  // compiles
v = "s"; // compiles

So, it's conditionally global?

This stack overflow post shows some conversation around this topic..

πŸ’» Use Cases

  1. What do you want to use this for? I want it to be easier to learn how to declare global types.
  2. What shortcomings exist with current approaches? I think the current approach is nuanced and behaves in unexpected ways.
  3. What workarounds are you using in the meantime? I refer back to these links whenever global variable declarations don't work the way I expect

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions