Search code examples
classconstructorscala.jsfacade

Scala.js does not see a JS *class* in the global scope, but sees a constructor function


While trying to write scalajs facade for javascript class, i am getting the following error: "error Uncaught TypeError: $g.MyRect2 is not a constructor" in chrome console.

My javascript class defination is as follows:

class MyRect2 {
    constructor(height, width) {
        this.height = height;
        this.width = width;
      }

      area() {
      return this.height * this.width
      }

}

Then I imported it as follows in scala

@js.native
class MyRect2(h:Int,w:Int) extends  js.Object {

  val height:Int = js.native
  val width:Int = js.native
  def area():Int = js.native
}

Finally I instantiated the class as follows

val rect2 = new MyRect2(2,2) //This is giving the above error.

However, when I write javascript class as below, the same import and instantiation works

function MyRect2(height,width) {
    this.height = height;
    this.width = width;
    this.area = function() {return this.height * this.width;};
}

Pls suggest what i am not doing right.


Solution

  • Edit: This has been fixed in Scala.js 1.x (as of this writing, Scala.js 1.0.0-M1 has been released).

    Ouch ... Well that just made my day a lot darker.

    It turns out that class declarations (along with lets and consts, but unlike vars and functions) do not add what they declare as properties of the global object of JavaScript. They are only available in the global scope.

    Until ECMAScript 5.1, these two things were equivalent: something was in the global scope if and only if it was a property of the global object. Now, there are things that are in the global scope but are not properties of the global object.

    See Variables and Scoping in ES 6, section 6: The Global object for more details on this issue. We can also experiment as follows in a browser's console (or in Node.js by replacing window by global):

    class MyRect2 {
        constructor(height, width) {
            this.height = height;
            this.width = width;
        }
    
        area() {
          return this.height * this.width
        }
    }
    
    new window.MyRect2(2, 2)
    

    results in:

    TypeError: window.MyRect2 is not a constructor [Learn More]
    

    but

    function MyRect3(height,width) {
        this.height = height;
        this.width = width;
        this.area = function() {return this.height * this.width;};
    }
    new window.MyRect3(2, 2)
    

    gives:

    Object { height: 2, width: 2, area: MyRect3/this.area() }
    

    This is at odds with how Scala.js was designed so far. By design, Scala.js only gives you access to the global object, and not the global scope. This was done so that the compiler would never shadow your accessing to a global variable by its own internally generated names. I.e., it was a good idea given the premise that everything you would access through the global scope can also be accessed through the global object.

    Now this premise is broken, which means that Scala.js has a severe limitation, which we will need to fix.

    The workaround is to add the following to your .js file:

    class MyRect2 { ... }
    window.MyRect2 = MyRect2;
    

    to force MyRect2 to become a property of the global object, and therefore let Scala.js access it.