Export Scala.js APIs to JavaScript

By default, Scala.js classes, objects, methods and properties are not available to JavaScript. Entities that have to be accessed from JavaScript must be annotated explicitly as exported. The @JSExport annotation is the main way to do this.

A simple example

package example

import scala.scalajs.js
import js.annotation.JSExport

@JSExport
object HelloWorld {
  @JSExport
  def main(): Unit = {
    println("Hello world!")
  }
}

This allows to call the main() method of HelloWorld like this in JavaScript:

HelloWorld().main();

Note the () when accessing the object, HelloWorld is a function.

You have probably already used an @JSExport without knowing it through the JSApp trait in the Main class of the bootstrapping skeleton (or any other template of Scala.js application). In fact, any Scala.js application must export at least a class or an object and a method in order to be invokable at all.

Most of the time, however, it is sufficient to just extend the JSApp trait:

package example

import scala.scalajs.js
import js.annotation.JSExport

object HelloWorld extends js.JSApp {
  def main(): Unit = {
    println("Hello world!")
  }
}

And call like this (see documentation about @JSExportDescendentObjects below for internal workings):

example.HelloWorld().main();

Exporting top-level objects

Put on a top-level object, the @JSExport annotation exports a zero-argument function returning that object in JavaScript’s global scope. By default, the function has the same name as the object in Scala (unqualified).

@JSExport
object HelloWorld {
  ...
}

exports the HelloWorld() function in JavaScript.

@JSExport takes an optional string parameter to specify a non-default name for JavaScript. For example,

@JSExport("MainObject")
object HelloWorld {
  ...
}

exports the HelloWorld object under the function MainObject() in JavaScript.

The name can contain dots, in which case the exported function is namespaced in JavaScript.

@JSExport("myapp.foo.MainObject")
object HelloWorld {
  ...
}

will be accessible in JavaScript using myapp.foo.MainObject().

Exporting classes

The @JSExport annotation can also be used to export Scala.js classes to JavaScript (but not traits), or, to be more precise, their constructors. This allows JavaScript code to create instances of the class.

@JSExport
class Foo(val x: Int) {
  override def toString(): String = s"Foo($x)"
}

exposes Foo as a constructor function to JavaScript:

var foo = new Foo(3);
console.log(foo.toString());

will log the string "Foo(3)" to the console. This particular example works because it calls toString(), which is always exported to JavaScript. Other methods must be exported explicitly as shown in the next section.

As is the case for top-level objects, classes can be exported under custom names, including namespaced ones, by giving an explicit name to @JSExport.

Exporting methods

Similarly to objects, methods of Scala classes, traits and objects can be exported with @JSExport, with or without an explicit name.

class Foo(val x: Int) {
  @JSExport
  def square(): Int = x*x // note the (), omitting them has a different behavior
  @JSExport("foobar")
  def add(y: Int): Int = x+y
}

Given this definition, and some variable foo holding an instance of Foo, you can call:

console.log(foo.square());
console.log(foo.foobar(5));
// console.log(foo.add(3)); // TypeError, add is not a member of foo

Overloading

Several methods can be exported with the same JavaScript name (either because they have the same name in Scala, or because they have the same explicit JavaScript name as parameter of @JSExport). In that case, run-time overload resolution will decide which method to call depending on the number and run-time types of arguments passed to the the method.

For example, given these definitions:

class Foo(val x: Int) {
  @JSExport
  def foobar(): Int = x
  @JSExport
  def foobar(y: Int): Int = x+y
  @JSExport("foobar")
  def bar(b: Boolean): Int = if (b) 0 else x
}

the following calls will dispatch to each of the three methods:

console.log(foo.foobar());
console.log(foo.foobar(5));
console.log(foo.foobar(false));

If the Scala.js compiler cannot produce a dispatching code capable of reliably disambiguating overloads, it will issue a compile error (with a somewhat cryptic message):

class Foo(val x: Int) {
  @JSExport
  def foobar(): Int = x
  @JSExport
  def foobar(y: Int): Int = x+y
  @JSExport("foobar")
  def bar(i: Int): Int = if (i == 0) 0 else x
}

gives:

[error] HelloWorld.scala:16: double definition:
[error] method $js$exported$meth$foobar:(i: Int)Any and
[error] method $js$exported$meth$foobar:(y: Int)Any at line 14
[error] have same type
[error]   @JSExport("foobar")
[error]    ^
[error] one error found

Hint to recognize this error: the methods are named $js$exported$meth$ followed by the JavaScript export name.

Exporting for call with named parameters

It is customary in Scala to call methods with named parameters if this eases understanding of the code or if many arguments with default values are present:

def foo(x: Int = 1, y: Int = 2, z: Int = 3) = ???

foo(y = 3, x = 2)

A rough equivalent in JavaScript is to pass an object with the respective properties:

foo({
  y: 3,
  x: 2
});

The @JSExportNamed annotation allows to export Scala methods for use in JavaScript with named parameters:

class A {
  @JSExportNamed
  def foo(x: Int, y: Int = 2, z: Int = 3) = ???
}

Note that default parameters are not required. foo can then be called like this:

var a = // ...
a.foo({
  y: 3,
  x: 2
});

Not specifying x in this case will fail at runtime (since it does not have a default value).

Just like @JSExport, @JSExportNamed takes the name of the exported method as an optional argument.

Exporting properties

vals, vars and defs without parentheses, as well as defs whose name ends with _=, have a single argument and Unit result type, are exported to JavaScript as properties with getters and/or setters using, again, the @JSExport annotation.

Given this weird definition of a halfway mutable point:

@JSExport
class Point(_x: Double, _y: Double) {
  @JSExport
  val x: Double = _x
  @JSExport
  var y: Double = _y
  @JSExport
  def abs: Double = Math.sqrt(x*x + y*y)
  @JSExport
  def sum: Double = x + y
  @JSExport
  def sum_=(v: Double): Unit = y = v - x
}

JavaScript code can use the properties as follows:

var point = new Point(4, 10)
console.log(point.x);   // 4
console.log(point.y);   // 10
point.y = 20;
console.log(point.y);   // 20
point.x = 1;            // does nothing, thanks JS semantics
console.log(point.x);   // still 4
console.log(point.abs); // 20.396078054371138
console.log(point.sum); // 24
point.sum = 30;
console.log(point.sum); // 30
console.log(point.y);   // 26

As usual, explicit names can be given to @JSExport. For def setters, the JS name must be specified without the trailing _=.

def setters must have a result type of Unit and exactly one parameter. Note that several def setters with different types for their argument can be exported under a single, overloaded JavaScript name.

In case you overload properties in a way the compiler cannot disambiguate, the methods in the error messages will be prefixed by $js$exported$prop$.

Export fields directly declared in constructors

If you want to export fields that are directly declared in a class constructor, you’ll have to use the @field meta annotation to avoid annotating the constructor arguments (exporting an argument is nonsensical and will fail):

import scala.annotation.meta.field

class Point(
    @(JSExport @field) val x: Double,
    @(JSExport @field) val y: Double)

// Also applies to case classes
case class Point(
    @(JSExport @field) x: Double,
    @(JSExport @field) y: Double)

Automatically exporting descendent objects

Sometimes it is desirable to automatically export all descendent objects of a given trait or class. You can use the @JSExportDescendentObjects annotation. It will cause all descendent objects to be exported to their fully qualified name.

This feature is especially useful in conjunction with exported abstract methods and is used by the test libraries of Scala.js and the scala.scalajs.js.JSApp trait. The following is just an example, how the feature can be used:

package foo.test

@JSExportDescendentObjects
trait Test {
  @JSExport
  def test(param: String): Unit
}

// automatically exported as foo.test.Test1
object Test1 extends Test {
  // exported through inheritance
  def test(param: String) = {
    println(param)
  }
}

Automatically export all members

Instead of writing @JSExport on every member of a class or object, you may use the @JSExportAll annotation. It is equivalent to adding @JSExport on every public (term) member directly declared in the class/object:

class A {
  def mul(x: Int, y: Int): Int = x * y
}

@JSExportAll
class B(val a: Int) extends A {
  def sum(x: Int, y: Int): Int = x + y
}

This is strictly equivalent to writing:

class A {
  def mul(x: Int, y: Int): Int = x * y
}

class B(@(JSExport @field) val a: Int) extends A {
  @JSExport
  def sum(x: Int, y: Int): Int = x + y
}

It is important to note that this does not export inherited members. If you wish to do so, you’ll have to override them explicitly:

class A {
  def mul(x: Int, y: Int): Int = x * y
}

@JSExportAll
class B(val a: Int) extends A {
  override def mul(x: Int, y: Int): Int = super.mul(x,y)
  def sum(x: Int, y: Int): Int = x + y
}