When writing an application with Scala.js, it is expected that the mainapplication logic be written in Scala.js, and that existing JavaScript librariesare leveraged. Calling JavaScript from Scala.js is therefore the most importantdirection of interoperability.

Facade types are zero-overhead typed APIs for JavaScript libraries. They aresimilar in spirit toTypeScript type definitions.

Defining JavaScript interfaces with native JS traits

Most JavaScript APIs work with interfaces that are defined structurally. InScala.js, the corresponding concept are traits. To mark a trait as being arepresentative of a JavaScript API, it must inherit directly or indirectlyfrom js.Any (usually from js.Object).

JS traits can be native or not.The present page describes native JS traits, which must be annotated with @js.native.There are also non-native JS traits (aka Scala.js-defined JS traits), documented in the Scala.js-defined JS types guide.The latter have more restrictions, but can be implemented from Scala.js code.Native JS traits as described here should only be used for interfaces that are exclusively implemented by the JavaScript library–not for interfaces/contracts meant to be implemented by the user of said library.

Scala.js 0.6.x: In Scala.js 0.6.x, unless using the -P:scalajs:sjsDefinedByDefault compiler option, the annotation @js.native is assumed by default, with a deprecation warning.You might still find old code that does not yet use it to annotate native JS types.

In native JS types, all concrete definitions must have = js.native as body.Any other body will be handled as if it were = js.native, and a warning will be emitted.(In Scala.js 1.x, this is an error.)

Here is an example giving types to a small portion of the API of Windowobjects in browsers.

  1. @js.native
  2. trait Window extends js.Object {
  3. val document: HTMLDocument = js.native
  4. var location: String = js.native
  5. def innerWidth: Int = js.native
  6. def innerHeight: Int = js.native
  7. def alert(message: String): Unit = js.native
  8. def open(url: String, target: String,
  9. features: String = ""): Window = js.native
  10. def close(): Unit = js.native
  11. }

Remarks

var, val and def definitions without parentheses all map to field accessin JavaScript, whereas def definitions with parentheses (even empty) mapto method calls in JavaScript.

The difference between a val and a def without parentheses is that theresult of the former is stable (in Scala semantics). Pragmatically, use valif the result will always be the same (e.g., document), and def whensubsequent accesses to the field might return a different value (e.g.,innerWidth).

Calls to the apply method of an object x map to calling x, i.e., x(…)instead of x.apply(…).

Methods can have parameters with default values, to mark them as optional.However, the actual value is irrelevant and never used. Instead, the parameteris omitted entirely (or set to undefined). The value is only indicative, asimplicit documentation.

Fields, parameters, or result types that can have different, unrelated types, can be accurately typed with thepseudo-union type A | B.

Methods can be overloaded. This is useful to type accurately some APIs thatbehave differently depending on the number or types of arguments.

JS traits and their methods can have type parameters, abstract type membersand type aliases, without restriction compared to Scala’s type system.

However, inner traits, classes and objects don’t make sense and are forbidden.It is however allowed to declare a JS trait in a top-level object.

Methods can have varargs, denoted by * like in regular Scala. They map toJavaScript varargs, i.e., the method is called with more arguments.

isInstanceOf[T] is not supported for any trait T inheriting from js.Any.Consequently, pattern matching for such types is not supported either.

asInstanceOf[T] is completely erased for any T inheriting from js.Any,meaning that it does not perform any runtime check.It is always valid to cast anything to such a trait.

JavaScript field/method names and their Scala counterpart

Sometimes, a JavaScript API defines fields and/or methods with names that donot feel right in Scala. For example, jQuery objects feature a method namedval(), which, obviously, is a keyword in Scala.

They can be defined in Scala in two ways. The trivial one is simply to usebackquotes to escape them in Scala:

  1. def `val`(): String = js.native
  2. def `val`(v: String): this.type = js.native

However, it becomes annoying very quickly. An often better solution is to usethe scala.scalajs.js.annotation.JSName annotation to specify the JavaScript name touse, which can be different from the Scala name:

  1. @JSName("val")
  2. def value(): String = js.native
  3. @JSName("val")
  4. def value(v: String): this.type = js.native

If necessary, several overloads of a method with the same name can have different@JSName’s. Conversely, several methods with different names in Scala can havethe same @JSName.

Members with a JavaScript symbol “name”

@JSName can also be given a reference to a js.Symbol instead of a constantstring. This is used for JavaScript members whose “name” is actually a symbol.For example, JavaScript iterable objects must declare a method whose name is thesymbol Symbol.iterator:

  1. @JSName(js.Symbol.iterator)
  2. def iterator(): js.Iterator[Int] = js.native

The argument to @JSName must be a reference to a static, stable field. Inpractice, this means a val in top-level object. js.Symbol.iterator is sucha val, declared in the top-level object js.Symbol.

Scala methods representing bracket access (obj[x])

The annotation scala.scalajs.js.annotation.JSBracketAccess can be used on methods tomark them as representing bracket access on an object. The target method musteither have one parameter and a non-Unit result type (in which case itrepresents read access) or two parameters and a Unit result type (in which caseit represents write access).

A typical example can be found in the js.Array[A] class itself, of course:

  1. @JSBracketAccess
  2. def apply(index: Int): A = js.native
  3. @JSBracketAccess
  4. def update(index: Int, v: A): Unit = js.native

The Scala method names are irrelevant for the translation to JavaScript. Theduo apply/update is often a sensible choice, because it gives array-likeaccess on Scala’s side as well, but it is not required to use these names.

Native JavaScript classes

It is also possible to define native JavaScript classes as Scala classes inheriting,directly or indirectly, from js.Any (like traits, usually from js.Object).The main difference compared to traits is that classes have constructors, hencethey also provide instantiation of objects with the new keyword.

Unlike traits, classes actually exist in the JavaScript world, often astop-level, global variables. They must therefore be annotated with the@JSGlobal annotation. For example:

  1. @js.native
  2. @JSGlobal
  3. class RegExp(pattern: String) extends js.Object {
  4. ...
  5. }

Pre 0.6.15 note: Before Scala.js 0.6.15, the @JSGlobal annotation did notexist, so you will find old code that does not yet use it to annotate native JSclasses.

The call new RegExp("[ab]") will map to the obvious in JavaScript, i.e.,new RegExp("[ab]"), meaning that the identifier RegExp will be looked upin the global scope.

If it is impractical or inconvenient to declare the Scala class with thesame name as the JavaScript class (e.g., because it is defined in a namespace,like THREE.Scene), a constant string can be given as parameter to @JGlobalto specify the JavaScript name:

  1. @js.native
  2. @JSGlobal("THREE.Scene")
  3. class Scene extends js.Object

Remarks

If the class does not have any constructor without argument, and it has to besubclassed, you may either decide to add a fake protected no-arg constructor,or call an inherited constructor with ???s as parameters.

isInstanceOf[C] is supported for classes inheriting from js.Any.It is implemented with an instanceof test.Pattern matching, including ClassTag-based matching, work accordingly.

As is the case for traits, asInstanceOf[C] is completely erased for any classC inheriting from js.Any, meaning that it does not perform any runtimecheck.It is always valid to cast anything to such a class.

Top-level JavaScript objects

JavaScript APIs often expose top-level objects with methods and fields.For example, the JSON object provides methods for parsing and emitting JSONstrings.These can be declared in Scala.js with object’s inheriting directly orindirectly from js.Any (again, often js.Object).As is the case with classes, they must be annotated with @js.native and@JSGlobal.

  1. @js.native
  2. @JSGlobal
  3. object JSON extends js.Object {
  4. def parse(text: String): js.Any = js.native
  5. def stringify(value: js.Any): String = js.native
  6. }

A call like JSON.parse(text) will map in JavaScript to the obvious, i.e.,JSON.parse(text), meaning that the identifier JSON will be looked up in theglobal scope.

Similarly to classes, the JavaScript name can be specified as an explicitargument to @JSGlobal, e.g.,

  1. @js.native
  2. @JSGlobal("jQuery")
  3. object JQuery extends js.Object {
  4. def apply(x: String): JQuery = js.native
  5. }

Unlike classes and traits, native JS objects can have inner native JS classes, traits and objects.Inner classes and objects will be looked up as fields of the enclosing JS object.

Variables and functions in the global scope

Besides object-like top-level definitions, JavaScript also defines variablesand functions in the global scope. Scala does not have top-level variables andfunctions. Instead, in Scala.js, top-level objects annotated with@JSGlobalScope are considered to represent the global scope.

  1. import js.annotation._
  2. @js.native
  3. @JSGlobalScope
  4. object DOMGlobalScope extends js.Object {
  5. val document: HTMLDocument = js.native
  6. def alert(message: String): Unit = js.native
  7. }

Prior to 0.6.13, extends js.GlobalScope was used instead of @JSGlobalScope.js.GlobalScope is now deprecated.

Scala.js 1.x: Also read access to the JavaScript global scope.

Imports from other JavaScript modules

Important: Importing from JavaScript modules requires that you emit a module for the Scala.js code.

The previous sections on native classes and objects all refer to global variables, i.e., variables declared in the JavaScript global scope.In modern JavaScript ecosystems, we often want to load things from other modules.This is what @JSImport is designed for.You can annotate an @js.native class or object with @JSImport instead of @JSGlobal to signify that it is defined in a module.For example, in the following snippet:

  1. @js.native
  2. @JSImport("bar.js", "Foo")
  3. class Foobaz(val x: Int) extends js.Object
  4. val f = new Foobaz(5)

the annotation specifies that Foobaz is a native JS class defined in the module "bar.js", and exported under the name "Foo".Semantically, @JSImport corresponds to an ECMAScript 2015 import, and the above code is therefore equivalent to this JavaScript code:

  1. import { Foo as Foobaz } from "bar.js";
  2. var f = new Foobaz(5);

In CommonJS terms, this would be:

  1. var bar = require("bar.js");
  2. var f = new bar.Foo(5);

The first argument to @JSImport is the name of the JavaScript module you wish to import.The second argument denotes what member of the module you are importing.It can be one of the following:

  • A string indicating the name of member.The string can be a .-separated chain of selections (e.g., "Foo.Babar").
  • The constant JSImport.Default, to select the default export of the JavaScript module.This corresponds to import Foobaz from "bar.js".
  • The constant JSImport.Namespace, to select the module itself (with its exports as fields).This corresponds to import * as Foobaz from "bar.js".

The latter is particularly useful if you want to import members of the modules that are neither classes nor objects (for example, functions):

  1. @js.native
  2. @JSImport("bar.js", JSImport.Namespace)
  3. object Bar extends js.Object {
  4. def exportedFunction(x: Int): Int = js.native
  5. }
  6. val y = Bar.exportedFunction(5)

In CommonJS terms, this would be:

  1. var bar = require("bar.js");
  2. var y = bar.exportedFunction(5);

If the previous example had used JSImport.Default instead of JSImport.Namespace, the current translation into CommonJS terms would be the following:

  1. function moduleDefault(m) {
  2. return (m && (typeof m === "object") && "default" in m) ? m["default"] : m;
  3. }
  4. var bar = require("bar.js");
  5. var y = moduleDefault(bar).exportedFunction(5);

This is subject to change in future versions of Scala.js, to better reflect the evolution of specifications in ECMAScript itself, and its implementations.

Important: @JSImport is completely incompatible with jsDependencies.You should use a separate mechanism to manage your JavaScript dependencies.Scala.js does not provide any facility to do so, at the moment.

Translating ES imports to Scala.js @JSImport

When the documentation of a library specifies how to write ES imports to use it, use the following table to translate those into Scala.js @JSImports:

Scala.js

  1. @JSExportTopLevel("foo")
  2. object Bar

ECMAScript

  1. export { Bar as foo }

Scala.js

  1. @js.native
  2. @JSImport("mod.js", "foo")
  3. object Bar extends js.Object

ECMAScript

  1. import { foo as Bar } from "mod.js"

Scala.js

  1. @js.native
  2. @JSImport("mod.js", JSImport.Namespace)
  3. object Bar extends js.Object

ECMAScript

  1. import * as Bar from "mod.js"

Scala.js

  1. @js.native
  2. @JSImport("mod.js", JSImport.Default)
  3. object Bar extends js.Object

ECMAScript

  1. import Bar from "mod.js"

Default import or namespace import?

The default export accessible with JSImport.Default, specified in terms of ECMAScript 2015 modules, is somewhat underspecified when it comes to CommonJS, at the moment.This is because it is not entirely clear yet what default exports are supposed to be with respect to “legacy” module systems (such as CommonJS).It seems that the intention is that a legacy module (such as a CommonJS) would appear to an ECMAScript 2015 module as exporting a single member: the default export.For a CommonJS module, the value of the default export would be the value of exports.This intention is not clearly specified anywhere, though, and existing definitions are known to slightly conflict on the matter (e.g., what Rollup.js does compared to what Node.js would do in the future).There seems to be an emergent behavior that members of a legacy module (e.g., fields of the exports object) will also be exposed as if they were top-level exports, so that they can be imported as import { Foo } from "bar.js".

What does it all mean to you?How to choose between Namespace, Default and named imports?At present, we recommend to follow these rules of thumb:

  • Does the documentation of the module specify how to import it with ECMAScript 2015 syntax?If yes, translate the ES syntax into @JSImport as specified above.
  • Otherwise, is the exports value of a legacy module not an object (e.g., it is a class or a function)?If yes, use a default import with JSImport.Default.
  • Otherwise, use a named import with a string or a namespace import with JSImport.Namespace.

Dynamic import

ECMAScript 2020’s dynamic import is exposed in Scala.js as the method js.importA <: js.Any, which returns a js.Promise[A].The parameter A should be a JS trait describing the API of the module, and be given explicitly.Since import is a keyword in Scala, it must be called with backticks:

  1. import scala.scalajs.js
  2. trait FooAPI extends js.Any {
  3. def bar(x: Int): Int
  4. }
  5. val moduleName = "foo.js"
  6. val promise = js.`import`[FooAPI](moduleName)
  7. val future = promise.toFuture
  8. for (module <- future) {
  9. println(module.bar(5))
  10. }

Monkey patching

In JavaScript, monkey patching is a common pattern, where some top-levelobject or class’ prototype is meant to be extended by third-party code. Thispattern is easily encoded in Scala.js’ type system with implicit conversions.

For example, in jQuery, $.fn can be extended with new methods that will beavailable to so-called jQuery objects, of type JQuery. Such a plugin can bedeclared in Scala.js with a separate trait, say JQueryGreenify, and animplicit conversions from JQuery to JQueryGreenify.The implicit conversion is implemented with a hard cast, since in effect wejust want to extend the API, not actually change the value.

  1. @js.native
  2. trait JQueryGreenify extends JQuery {
  3. def greenify(): this.type = ???
  4. }
  5. object JQueryGreenify {
  6. implicit def jq2greenify(jq: JQuery): JQueryGreenify =
  7. jq.asInstanceOf[JQueryGreenify]
  8. }

Recall that jq.asInstanceOf[JQueryGreenify] will be erased when mapping toJavaScript because JQueryGreenify is a JS trait.The implicit conversion is therefore a no-op and can be inlined away, whichmeans that this pattern does not have any runtime overhead.

Reflective calls

Scala.js does not support reflective calls on any subtype ofjs.Any. This is mainly due to the @JSName annotation. Since wecannot statically enforce this restriction, reflective calls onsubtypes of js.Any will fail at runtime. Therefore, we recommendto avoid reflective calls altogether.

What is a reflective call?

Calling a method on a structural type in Scala creates a so-calledreflective call. A reflective call is a type-safe method call thatuses Java reflection at runtime. The following is an example of areflective call:

  1. // A structural type
  2. type T = { def foo(x: Int): String }
  3. def print(obj: T) = obj.foo(100)
  4. // ^ this is a reflective call

Any object conforming structurally to T can now be passed toprint:

  1. class A { def foo(x: Int) = s"Input: $x" }
  2. print(new A())

Note that A does not extend T but only conforms structurally(i.e., it has a method foo with a matching signature).

The Scala compiler issues a warning for every reflective call, unlessthe scala.language.reflectiveCalls is imported.

Why do reflective calls not work on js.Any?

Since JavaScript is dynamic by nature, a reflective method lookup asin Java is not required for reflective calls. However, in order togenerate the right method call, the call-site needs to know the exactfunction name in JavaScript. The Scala.js compiler generates proxymethods for that specific purpose.

However, we are unable to generate these forwarder methods on js.Anytypes without leaking prototype members on non-Scala.js objects. Thisis something which – in our opinion – we must avoid at allcost. Lack of forwarder methods combined with the fact that aJavaScript method can be arbitrarily renamed using @JSName, makes itimpossible to know the method name to be called at the call-site. Thereflective call can therefore not be generated.

Calling JavaScript from Scala.js with dynamic types

Sometimes, it is more convenient to manipulate JavaScript values in a dynamically typed way.Although it is not recommended to do so for APIs that are used repetitively, Scala.js lets you call JavaScript in a dynamically typed fashion if you want to.The basic entry point is to grab a dynamically typed reference to the global scope, with js.Dynamic.global, which is of type js.Dynamic.

Scala.js 1.x: In Scala.js 1.x, js.Dynamic.global is a global scope object instead of an actual value of type js.Dynamic.

You can read and write any field of a js.Dynamic, as well as call any methodwith any number of arguments. All input types are assumed to be of typejs.Any, and all output types are assumed to be of type js.Dynamic. Thismeans that you can assign a js.Array[A] (or even an Int, through implicitconversion) to a field of a js.Dynamic. And when you receive something, youcan chain any kind of call and/or field access.

For example, this snippet taken from the Hello World example uses thedynamically typed interface to manipulate the DOM model.

  1. val document = js.Dynamic.global.document
  2. val playground = document.getElementById("playground")
  3. val newP = document.createElement("p")
  4. newP.innerHTML = "Hello world! <i>-- DOM</i>"
  5. playground.appendChild(newP)

In this example, document, playground and newP are all inferred to be oftype js.Dynamic. When calling getElementById or assigning to the fieldinnerHTML, the String is implicitly converted to js.Any.

And since js.Dynamic inherits from js.Any, it is also valid to pass newPas a parameter to appendChild.

Remarks

Calling a js.Dynamic, like in x(a) will be treated as calling x inJavaScript, just like calling the apply method with the statically typedinterface. Parameters are assumed to be of type js.Any and the result typeis js.Dynamic, as for any other method.

All the JavaScript operators can be applied to js.Dynamic values.

To instantiate an object of a class with the dynamic interface, you need toobtain a js.Dynamic reference to the class value, and call thejs.Dynamic.newInstance method like this:

  1. val today = js.Dynamic.newInstance(js.Dynamic.global.Date)()

If you use the dynamic interface a lot, it is convenient to importjs.Dynamic.global and/or newInstance under simple names, e.g.,

  1. import js.Dynamic.{ global => g, newInstance => jsnew }
  2. val today = jsnew(g.Date)()

When using js.Dynamic, you are very close to writing raw JavaScript withinScala.js, with all the warts of the language coming to haunt you.However, to get the full extent of JavaScriptish code, you can import theimplicit conversions injs.DynamicImplicts.Use at your own risk!