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
This allows to call the main()
method of HelloWorld
like this in JavaScript:
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:
And call like this (see documentation about
@JSExportDescendentObjects
below for internal workings):
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).
exports the HelloWorld()
function in JavaScript.
@JSExport
takes an optional string parameter to specify a non-default name
for JavaScript. For example,
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.
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.
exposes Foo
as a constructor function to JavaScript:
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.
Given this definition, and some variable foo
holding an instance of Foo
,
you can call:
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:
the following calls will dispatch to each of the three methods:
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):
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:
A rough equivalent in JavaScript is to pass an object with the respective properties:
The @JSExportNamed
annotation allows to export Scala methods for use in JavaScript with named parameters:
Note that default parameters are not required. foo
can then be called like this:
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
val
s, var
s and def
s without parentheses, as well as def
s 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:
JavaScript code can use the properties as follows:
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):
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:
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:
This is strictly equivalent to writing:
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: