This page applies to Scala.js 1.x only.

Unlike Scala, JavaScript has a global scope, where global variables are defined.For example, one can define a variable foo at the top-level of a script:

  1. var foo = 42;

which then makes it available in the global scope, so that another script can read or write it:

  1. console.log(foo);
  2. foo = 24;

The facade types reference explains how we can define facades for global variables.Here is a recap of the different ways:

  1. @js.native
  2. @JSGlobalScope
  3. object Globals extends js.Object {
  4. var foo: Int = js.native
  5. }
  6. @js.native
  7. @JSGlobal
  8. class Bar extends js.Object
  9. @js.native
  10. @JSGlobal
  11. object Bar extends js.Object
  • @JSGlobal specifies that the annotated entity (class or object) represents a global variable, in the JavaScript global scope.
  • @JSGlobalScope specifies that the annotated object represents the global scope itself, which means its members are global variables.

With the above definitions, the snippet

  1. val x = Globals.foo
  2. Global.foo = 24
  3. val y = new Bar
  4. val z = Bar

would “translate” to

  1. var x = foo;
  2. foo = 24;
  3. var y = new Bar();
  4. var z = Bar;

There are two “consequences” to that.

First, in any of the 4 above statements, if the referenced variable is not declared as a global variable, a ReferenceError will be thrown at run-time.This is also what would happen in JavaScript when accessing a non-existent global variable.

Second, whereas val x = Globals.foo translates to var x = foo, val g = Globals has no valid translation in JavaScript, and is a compile-time error.Indeed, since ECMAScript 2015, there is no JavaScript value that g could assume, such that g.foo would evaluate to the global variable foo (until ECMAScript 5.1, g could have been the global object, and this is what Scala.js 0.6.x did).In general, any “dynamic” reference to a global-scope object is a compile-time error in Scala.js 1.x.

Global-scope restrictions

After the above introduction, here is a reference of the compile-time restrictions of global-scope objects.

Assuming that Globals is an @JSGlobalScope object, then any use of Globals must satisfy all of the following requirements:

  • It is used as the left-hand-side of a dot-selection, i.e., in Globals.foobar or Globals.foobar(…)
  • Either of the 3 alternatives:
    • If foobar refers to a method annotated with @JSBracketAccess or @JSBracketCall, then the first actual argument must be a constant string which is a valid JavaScript identifier (e.g., Global.foobar("ident") is valid but Global.foobar(someVal) isn’t)
    • Otherwise, if foobar has an @JSName(jsName) then jsName must be a constant string which is a valid JavaScript identifier
    • Otherwise, foobar must be a valid JavaScript identifier different than apply

For the purposes of this test, the special identifier arguments is not considered as a valid JavaScript identifier.

Here are some concrete examples.Given the following definitions:

  1. import scala.scalajs.js
  2. import scala.scalajs.js.annotation._
  3. object Symbols {
  4. val sym: js.Symbol = js.Symbol()
  5. }
  6. @js.native
  7. @JSGlobalScope
  8. object Globals extends js.Any {
  9. var validVar: Int = js.native
  10. def validDef(): Int = js.native
  11. var `not-a-valid-identifier-var`: Int = js.native
  12. def `not-a-valid-identifier-def`(): Int = js.native
  13. def +(that: Int): Int = js.native
  14. def apply(x: Int): Int = js.native
  15. @JSBracketAccess
  16. def bracketSelect(name: String): Int = js.native
  17. @JSBracketAccess
  18. def bracketUpdate(name: String, v: Int): Unit = js.native
  19. @JSBracketCall
  20. def bracketCall(name: String)(arg: Int): Int = js.native
  21. @JSName(Symbols.sym)
  22. var symbolVar: Int = js.native
  23. @JSName(Symbols.sym)
  24. def symbolDef(): Int = js.native
  25. var arguments: js.Array[Any] = js.native
  26. @JSName("arguments") def arguments2(x: Int): Int = js.native
  27. }

Only the following uses of Globals would be valid:

  1. Globals.validVar
  2. Globals.validDef()
  3. Globals.bracketSelect("someConstantIdent")
  4. Globals.bracketUpdate("someConstantIdent", anyExpression)
  5. Globals.bracketCall("someConstantIdent")(anyExpression)

All of the following uses are compile-time errors:

  1. // Not used as the left-hand-side of a dot-selection
  2. val x = Globals
  3. someMethod(Globals)
  4. // Accessing something that is not a valid JS identifier
  5. Globals.`not-a-valid-identifier-var`
  6. Globals.`not-a-valid-identifier-def`()
  7. Globals + 0
  8. Globals.arguments
  9. Globals.arguments2(0)
  10. // Calling an `apply` method without `@JSName`
  11. Globals(0)
  12. // Accessing a bracket-access/call member with a non-constant string
  13. val str = computeSomeString()
  14. Globals.bracketSelect(str)
  15. Globals.bracketUpdate(str, 0)
  16. Globals.bracketCall(str)(0)
  17. // Accessing a bracket-access/call member with a non-valid JS ident
  18. Globals.bracketSelect("not an ident")
  19. Globals.bracketUpdate("not an ident", 0)
  20. Globals.bracketCall("not an ident")(0)
  21. // Accessing a member whose JS name is a symbol
  22. Globals.symbolVar
  23. Globals.symbolDef()

The case of js.Dynamic.global

js.Dynamic.global is an @JSGlobalScope object that lets you read and write any field and call any top-level function in a dynamically typed way.As with any other global-scope object, it must always be used at the left-hand-side of a dot-selection, with a valid JavaScript on the right-hand-side.

For example, the following uses are valid:

  1. val x = js.Dynamic.global.foo
  2. js.Dynamic.global.foo = 24
  3. js.Dynamic.global.bar(0)

but the following uses are not valid:

  1. val x = js.Dynamic.global
  2. js.Dynamic.global.`not-a-valid-identifier`
  3. val str = computeSomeString()
  4. val y = js.Dynamic.global.selectDynamic(str)

The example means that it is not possible to dynamically look up a global variable given its name.

Practical tips

Testing whether a global variable exists

In Scala.js 0.6.x, it was possible to test whether a global variable exists (e.g., to perform a feature test) as follows:

  1. if (!js.isUndefined(js.Dynamic.global.Promise)) {
  2. // Promises are supported
  3. } else {
  4. // Promises are not supported
  5. }

In Scala.js 1.x, accessing js.Dynamic.global.Promise will throw a ReferenceError if Promise is not defined, so this does not work anymore.Instead, you must use js.typeOf:

  1. if (js.typeOf(js.Dynamic.global.Promise) != "undefined")

Just like in JavaScript, where typeof Promise is special, so is js.typeOf(e) if e is a member of a global-scope object (i.e., a global variable).If the global variable does not exist, js.typeOf(e) returns "undefined" instead of throwing a ReferenceError.

Dynamically lookup a global variable given its name

In general, it is not possible to dynamically lookup a global variable given its name, even in JavaScript.If absolutely necessary, one typically has to detect the global object, and use a normal field selection on it.Assuming globalObject is a js.Dynamic representing the global object, we can do

  1. val x = globalObject.selectDynamic(dynVarName)

There is no fully standard way to detect the global object.However, most JavaScript environments fall into two categories:

  • Either the global object is available as the global variable named global (e.g., in Node.js)
  • Or the “global” this keyword refers to the global object.

The global variable global can of course be read with js.Dynamic.global.global (the double global is intended).The global this can be read with js.special.globalThis.Together, these can be used to correctly detect the global scope in most environments:

  1. val globalObject: js.Dynamic = {
  2. import js.Dynamic.{global => g}
  3. if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
  4. // Node.js environment detected
  5. g.global
  6. } else {
  7. // In all other well-known environment, we can use the global `this`
  8. js.special.globalThis.asInstanceOf[js.Dynamic]
  9. }
  10. }