By default, Scala.js classes, objects, methods and properties are not availableto JavaScript. Entities that have to be accessed from JavaScript must beannotated explicitly as exported, using @JSExportTopLevel and @JSExport.

A simple example

  1. package example
  2. import scala.scalajs.js.annotation._
  3. @JSExportTopLevel("HelloWorld")
  4. object HelloWorld {
  5. @JSExport
  6. def sayHello(): Unit = {
  7. println("Hello world!")
  8. }
  9. }

This allows to call the sayHello() method of HelloWorld like this inJavaScript:

  1. HelloWorld.sayHello();

The @JSExportTopLevel on HelloWorld exports the object HelloWorld itselfin the JavaScript global scope. It is however not sufficient to allow JavaScriptto call methods of HelloWorld. This is why we also have to export themethod sayHello() with @JSExport.

In general, things that should be exported on the top-level, such as top-levelobjects and classes, are exported with @JSExportTopLevel, while things thatshould be exported as properties or methods in JavaScript are exported with@JSExport.

Exporting top-level objects

Put on a top-level object, the @JSExportTopLevel annotation exports thatobject to the JavaScript global scope. The name under which it is to be exportedmust be specified as an argument to @JSExportTopLevel.

  1. @JSExportTopLevel("HelloWorld")
  2. object HelloWorld {
  3. ...
  4. }

exports the HelloWorld object in JavaScript.

Pre 0.6.15 note: Before Scala.js 0.6.15, objects were exported as 0-argumentfunctions using @JSExport, rather than directly with @JSExportTopLevel. Thisis deprecated in 0.6.x, and not supported anymore in Scala.js 1.x.

Exporting under a namespace (deprecated)

Note: Deprecated since Scala.js 0.6.26, and not supported anymore in Scala.js 1.x.

The export name can contain dots, in which case the exported object is namespaced in JavaScript.For example,

  1. @JSExportTopLevel("myapp.foo.MainObject")
  2. object HelloWorld {
  3. ...
  4. }

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

Exporting classes

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

  1. @JSExportTopLevel("Foo")
  2. class Foo(val x: Int) {
  3. override def toString(): String = s"Foo($x)"
  4. }

exposes Foo as a constructor function to JavaScript:

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

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

Pre 0.6.15 note: Before Scala.js 0.6.15, classes were exported using@JSExport instead of @JSExportTopLevel, with the same meaning. This isdeprecated in 0.6.x, and not supported anymore in Scala.js 1.x.

Exports with modules

When emitting a module for Scala.js code, top-level exports are not sent to the JavaScript global scope.Instead, they are genuinely exported from the module.In that case, an @JSExportTopLevel annotation has the semantics of an ECMAScript 2015 export.For example:

  1. @JSExportTopLevel("Bar")
  2. class Foo(val x: Int)

is semantically equivalent to this JavaScript export:

  1. export { Foo as Bar };

Exporting methods

Similarly to objects, methods of Scala classes, traits and objects can beexported with @JSExport. Unlike for @JSExportTopLevel, the name argument isoptional for @JSExport, and defaults to the Scala name of the method.

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

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

  1. console.log(foo.square());
  2. console.log(foo.foobar(5));
  3. // 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 becausethey have the same name in Scala, or because they have the same explicitJavaScript name as parameter of @JSExport). In that case, run-time overloadresolution will decide which method to call depending on the number and run-timetypes of arguments passed to the the method.

For example, given these definitions:

  1. class Foo(val x: Int) {
  2. @JSExport
  3. def foobar(): Int = x
  4. @JSExport
  5. def foobar(y: Int): Int = x+y
  6. @JSExport("foobar")
  7. def bar(b: Boolean): Int = if (b) 0 else x
  8. }

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

  1. console.log(foo.foobar());
  2. console.log(foo.foobar(5));
  3. console.log(foo.foobar(false));

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

  1. class Foo(val x: Int) {
  2. @JSExport
  3. def foobar(): Int = x
  4. @JSExport
  5. def foobar(y: Int): Int = x+y
  6. @JSExport("foobar")
  7. def bar(i: Int): Int = if (i == 0) 0 else x
  8. }

gives:

  1. [error] HelloWorld.scala:16: double definition:
  2. [error] method $js$exported$meth$foobar:(i: Int)Any and
  3. [error] method $js$exported$meth$foobar:(y: Int)Any at line 14
  4. [error] have same type
  5. [error] @JSExport("foobar")
  6. [error] ^
  7. [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 (deprecated)

Note: Since Scala.js 0.6.11, @JSExportNamed is deprecated, and is not supported anymore in Scala.js 1.x.Refer to the Scaladoc for migration tips.

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:

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

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

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

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

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

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

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

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 top-level methods

While an @JSExported method inside an @JSExportTopLevel object allows JavaScript code to call a “static” method,it does not feel like a top-level function from JavaScript’s point of view.@JSExportTopLevel can also be used directory on a method of a top-levelobject, which exports the method as a truly top-level function:

  1. object A {
  2. @JSExportTopLevel("foo")
  3. def foo(x: Int): Int = x + 1
  4. }

can be called from JavaScript as:

  1. const y = foo(5);

Exporting properties

vals, vars and defs without parentheses, as well as defs whose nameends with _=, have a single argument and Unit result type, areexported to JavaScript as properties with getters and/or settersusing, again, the @JSExport annotation.

Given this weird definition of a halfway mutable point:

  1. @JSExport
  2. class Point(_x: Double, _y: Double) {
  3. @JSExport
  4. val x: Double = _x
  5. @JSExport
  6. var y: Double = _y
  7. @JSExport
  8. def abs: Double = Math.sqrt(x*x + y*y)
  9. @JSExport
  10. def sum: Double = x + y
  11. @JSExport
  12. def sum_=(v: Double): Unit = y = v - x
  13. }

JavaScript code can use the properties as follows:

  1. var point = new Point(4, 10)
  2. console.log(point.x); // 4
  3. console.log(point.y); // 10
  4. point.y = 20;
  5. console.log(point.y); // 20
  6. point.x = 1; // does nothing, thanks JS semantics
  7. console.log(point.x); // still 4
  8. console.log(point.abs); // 20.396078054371138
  9. console.log(point.sum); // 24
  10. point.sum = 30;
  11. console.log(point.sum); // 30
  12. console.log(point.y); // 26

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

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

In case you overload properties in a way the compiler cannotdisambiguate, 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):

  1. import scala.annotation.meta.field
  2. class Point(
  3. @(JSExport @field) val x: Double,
  4. @(JSExport @field) val y: Double)
  5. // Also applies to case classes
  6. case class Point(
  7. @(JSExport @field) x: Double,
  8. @(JSExport @field) y: Double)

Export fields to the top level

Similarly to methods, fields (vals and vars) of top-level objects can beexported as top-level variables using @JSExportTopLevel:

  1. object Foo {
  2. @JSExportTopLevel("bar")
  3. val bar = 42
  4. @JSExportTopLevel("foobar")
  5. var foobar = "hello"
  6. }

exports bar and foobar to the top-level, so that they can be used fromJavaScript as

  1. console.log(bar); // 42
  2. console.log(foobar); // "hello"

Note that for vars, the JavaScript binding is read-only, i.e., JavaScriptcode cannot assign a new value to an exported var. However, if Scala.js codesets Foo.foobar, the new value will be visible from JavaScript. This isconsistent with exporting a let binding in ECMAScript 2015 modules.

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:

  1. class A {
  2. def mul(x: Int, y: Int): Int = x * y
  3. }
  4. @JSExportAll
  5. class B(val a: Int) extends A {
  6. def sum(x: Int, y: Int): Int = x + y
  7. }

This is strictly equivalent to writing:

  1. class A {
  2. def mul(x: Int, y: Int): Int = x * y
  3. }
  4. class B(@(JSExport @field) val a: Int) extends A {
  5. @JSExport
  6. def sum(x: Int, y: Int): Int = x + y
  7. }

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:

  1. class A {
  2. def mul(x: Int, y: Int): Int = x * y
  3. }
  4. @JSExportAll
  5. class B(val a: Int) extends A {
  6. override def mul(x: Int, y: Int): Int = super.mul(x,y)
  7. def sum(x: Int, y: Int): Int = x + y
  8. }

Deprecated: Automatically exporting descendent objects or classes

Pre 0.6.15 note: Before Scala.js 0.6.15, this deprecated feature used to beoften used to “reflectively” instantiate classes and load objects. This use casehas been replaced by thescala.scalajs.reflect.ReflectAPI.This feature is not supported anymore in Scala.js 1.x.

When applied to a class or trait, @JSExportDescendentObjects causes allobjects extending it to be automatically exported as 0-arg functions, undertheir fully qualified name. For example:

  1. package foo.test
  2. @JSExportDescendentObjects
  3. trait Test {
  4. @JSExport
  5. def test(param: String): Unit
  6. }
  7. // automatically exported as foo.test.Test1
  8. object Test1 extends Test {
  9. // exported through inheritance
  10. def test(param: String): Unit = {
  11. println(param)
  12. }
  13. }

can be used from JavaScript as:

  1. foo.test.Test1().test("hello"); // note the () in Test1()

Similarly, @JSExportDescendentClasses causes all non-abstract classesthe annotated class or trait to be exported under their fully qualified name.