Announcing Scala.js 0.6.14

Dec 21, 2016.

We are excited to announce the release of Scala.js 0.6.14!

This release features a few language enhancements for interoperability. Among others, it allows to export top-level functions, and provides language support for JavaScript “configuration objects”.

Getting started

If you are new to Scala.js, head over to the tutorial.

Release notes

If upgrading from Scala.js 0.6.12 or earlier, make sure to read the release notes of 0.6.13, which contain some breaking changes in sbt build definitions.

As a minor release, 0.6.14 is backward source and binary compatible with previous releases in the 0.6.x series. Libraries compiled with earlier versions can be used with 0.6.14 without change. However, it is not forward compatible: libraries compiled with 0.6.14 cannot be used by projects using 0.6.{0-13}.

Please report any issues on GitHub.

JavaScript configuration objects

Many JavaScript libraries expose APIs where options are given in what we call configuration objects. A well-known example would be the $.ajax() function of jQuery, which we can declare as:

@ScalaJSDefined
trait JQueryAjaxSettings extends js.Object {
  val data: js.Object | String | js.Array[Any]
  val contentType: Boolean | String
  val crossDomain: Boolean
  val success: js.Function3[Any, String, JQueryXHR, _]
  ...
}

def ajax(url: String, settings: JQueryAjaxSettings): JQueryXHR = js.native

The vals need to be declared abstract because, in previous versions of Scala.js, @ScalaJSDefined traits could not have concrete members. This makes it tedious to create an instance of JQueryAjaxSettings, since the Scala type system forces us to fill them in, although the fields are optional in JavaScript. Previous workarounds include a) using js.Dynamic.literal(...).asInstanceOf[JQueryAjaxSettings] or b) using JSOptionBuilder.

Scala.js 0.6.14 finally provides language support for this kind of API. It is now allowed to have concrete members in @ScalaJSDefined traits, if their right-hand-side is = js.undefined (this implies that their type must be a supertype of js.UndefOr[Nothing]). With this relaxed rule, we can define JQueryAjaxSettings as follows:

@ScalaJSDefined
trait JQueryAjaxSettings extends js.Object {
  val data: js.UndefOr[js.Object | String | js.Array[Any]] = js.undefined
  val contentType: js.UndefOr[Boolean | String] = js.undefined
  val crossDomain: js.UndefOr[Boolean] = js.undefined
  val success: js.UndefOr[js.Function3[Any, String, JQueryXHR, _]] = js.undefined
  ...
}

When calling ajax(), we can now give an anonymous object that overrides only the vals we care about:

jQuery.ajax(someURL, new JQueryAjaxSettings {
  override val crossDomain: js.UndefOr[Boolean] = true
  override val success: js.UndefOr[js.Function3[Any, String, JQueryXHR, _]] = {
    js.defined { (data: Any, textStatus: String, xhr: JQueryXHR) =>
      println("Status: " + textStatus)
    }
  }
})

Note that for functions, we use js.defined { ... } to drive Scala’s type inference. Otherwise, it needs to apply two implicit conversions, which is not allowed.

The explicit types are still quite annoying, but they are only necessary in Scala 2.10 and 2.11. If you use Scala 2.12, you can omit all the type annotations (but keep js.defined), thanks to improved type inference for vals and SAM conversions:

jQuery.ajax(someURL, new JQueryAjaxSettings {
  override val crossDomain = true
  override val success = js.defined { (data, textStatus, xhr) =>
    println("Status: " + textStatus)
  }
})

Export top-level functions

In Scala.js 0.6.13 and earlier, the only way to export something resembling a top-level function was to export the method and its enclosing object, as follows:

@JSExport
object Main {
  @JSExport
  def main(): Unit = { ... }
}

However, this requires to call it as Main().main() from JavaScript, which shows that it is not actually a top-level function.

In Scala.js 0.6.14, you can use the @JSExportTopLevel annotation instead:

object Main {
  @JSExportTopLevel("main")
  def main(): Unit = { ... }
}

which allows to call main() from JavaScript. @JSExportTopLevel methods must be declared in top-level objects.

Bug fixes

Among others, the following bugs have been fixed in 0.6.14:

  • #2587 Incorrect result for BigDecimal multiplication (corner case)
  • #2639 The linker should refuse to build if @JSImport is used with NoModule
  • #2640 TimeoutException when running PhantomJS with macOS Sierra
  • #2643 js.ThisFunction passing Scala-this instead of JS-this when using Scala 2.12.0
  • #2655 Inconsistent behavior in NumericRange.min method between JVM and JS
  • #2689 The optimizer does something wrong with return inside finally

You can find the full list on GitHub.