yurique
yurique

Reputation: 43

@JSGlobalScope in scala.js 1.0 (JavaScriptException, ReferenceError, var is not defined)

After migrating from scala.js 0.6.x to 1.0, I've got some code related to @JSGlobalScope broken.

My use case is like this:

The code looks like this:

  @js.native
  @JSGlobalScope
  object Globals extends js.Object {
    var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
  }

then I set this var like this:

Globals.callbackFunctionFor3rdPartyLib = () => {
   // do things
}

and then I add the script into the DOM.

This was working with scala.js 0.6.x, but with 1.0 I'm getting an exception like the following:

scala.scalajs.js.JavaScriptException: ReferenceError: callbackFunctionFor3rdPartyLib is not defined

In the changelog for 1.0.0 there's a "Breaking changes" section that mentions this:

Accessing a member that is not declared causes a ReferenceError to be thrown ...

js.Dynamic.global.globalVarThatDoesNotExist = 42

would previously create said global variable. In Scala.js 1.x, it also throws a ReferenceError.

My question is:

what is the right way to do something like this (create a new global var) in scala.js 1.0?

Upvotes: 3

Views: 288

Answers (1)

sjrd
sjrd

Reputation: 22085

If you know you'll always be in a browser context, you can use @JSGlobal("window") instead of @JSGlobalScope on your Globals, which will then be equivalent to doing window.myGlobalVarFor3rdPartyLib in JS. So that will work.

@js.native
@JSGlobal("window")
object Globals extends js.Object {
  var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}

If not, but you are using a script (so not a CommonJS nor an ES module), the best thing is actually to use

object Globals {
  @JSExportTopLevel("myGlobalVarFor3rdPartyLib")
  var foo: js.Function[Unit] = ...
}

Note that Globals is a normal Scala object now, not a JS one.

The @JSExportTopLevel creates a top-level var myGlobalVarFor3rdPartyLib at the top of the script, and then assigning Globals.foo will also assign that top-level var.


If you're not using a script nor know that you're going to always be in a browser, then you need to figure out the global object yourself. Scala.js 0.6.x tried to do that for you, but could fail, so we don't do that anymore. You can at least follow the "instructions" on the documentation of js.special.fileLevelThis to reproduce what Scala.js 0.6.x was doing. I repeat the instructions here:

Using this value should be rare, and mostly limited to writing code detecting what the global object is. For example, a typical detection code--in case we do not need to worry of ES modules--looks like:

val globalObject = {
  import js.Dynamic.{global => g}
  if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
    // Node.js environment detected
    g.global
  } else {
    // In all other well-known environment, we can use the global `this`
    js.special.fileLevelThis
  }
}

Note that the above code is not comprehensive, as there can be JavaScript environments where the global object cannot be fetched neither through global nor this. If your code needs to run in such an environment, it is up to you to use an appropriate detection procedure.

Upvotes: 1

Related Questions