Announcing Scala.js 0.6.15

Mar 21, 2017.

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

This release was focused on preparations for Scala.js 1.0.0. It notably contains better replacements for some features that we were not 100% happy with, which are now deprecated and will be dropped in 1.0.0.

Thanks to @olafurpg and his great tool scalafix, you can automatically migrate your codebase for the first and most intrusive of those deprecations. See below for details.

Scala.js 0.6.15 also comes with a set of exciting new features, including several awesome JavaScript interoperability features:

  • Use @JSExportStatic to define static members on Scala.js-defined JS classes.
  • @JSExportTopLevel is not restricted to methods anymore: it can be used on fields and top-level classes/objects.
  • The standard library now contains the API for ECMAScript 2015 symbols (as js.Symbol) and iterables/iterators (as js.Iterable and js.Iterator).
  • In JS types (both native and non-native), @JSName can be given a reference to a js.Symbol (instead of a constant string) to declare members whose “name” is a JavaScript symbol.

Finally, the sbt plugin also features a replacement for the “launcher” files, so that the call to the main method of your program can be included directly inside the main -fastopt.js.

Read on for more details.

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.15 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.15 without change. However, it is not forward compatible: libraries compiled with 0.6.15 cannot be used by projects using 0.6.{0-14}.

Please report any issues on GitHub.

Main deprecations

@js.native classes and objects need to have @JSGlobal

Previously, one would declare facade types for native classes and objects as follows:

@js.native
object Foo extends js.Object

@js.native
@JSName("Foobar")
class Bar extends js.Object

This is now deprecated: @js.native objects and classes should now explicitly use the annotation @JSGlobal as follows:

import scala.scalajs.js.annotation._

@js.native
@JSGlobal // implied name "Foo"
object Foo extends js.Object

@js.native
@JSGlobal("Foobar")
class Bar extends js.Object

Note that @JSGlobal replaces @JSName if it is present, and is otherwise added.

Migration tips

You can temporarily disable the deprecation warnings with the following sbt setting:

scalacOptions += "-P:scalajs:suppressMissingJSGlobalDeprecations"

This will keep working in the 0.6.x cycle. You can use that to ease your migration and delay dealing with these deprecations until you have some time to allocate to that.

Automatically migrate your codebase with scalafix

Thanks to @olafurpg, we have a scalafix rewrite for you. You can use it to automatically migrate your codebase!

Here is a how-to:

  • Make sure you use sbt 0.13.13 or later (in project/build.properties)
  • Add the following sbt plugin to project/plugins.sbt:

    addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.3.2")
    
  • Create a file .scalafix.conf at the root of your project, and fill it with:

    rewrites = ["https://gist.githubusercontent.com/sjrd/ef8bb7c52be1451b3a3b9bab6a187549/raw/0b1d451d266bce20921bbff3a74722610d604509/ScalaJSRewrites.scala"]
    imports.organize = false
    imports.removeUnused = false
    

The first line specifies that we want to apply the @JSGlobal rewrite available in this gist, written by @olafurpg.

The last two lines are optional, but provide minimal diffs, at the cost of some manual intervention on imports. Consult the scalafix documentation for further details on those.

Once that is done, simply run

$ sbt scalafix

and enjoy the magic. If you use -Xfatal-warnings, you may have to disable it to run sbt scalafix, and re-enable it afterwards. Unless you do not use imports.organize = false, you will then have to manually adjust your imports to your liking so that scala.scalajs.js.annotation.JSGlobal is imported.

If you want a working example of this process, look at the PR on scalajs-dom, where we have successfully applied scalafix to the entire codebase. The commits of the PR provide explanations of the steps we performed, so it’s good to look at them (not just the PR diff).

@JSExport on objects and classes becomes @JSExportTopLevel

To make a top-level class or object accessible to JavaScript, we previously used the @JSExport annotation, like this:

package foo

@JSExport
object Foo

@JSExport("Bar")
class Bar

so that they can be accessed from JavaScript as:

var fooObject = foo.Foo(); // note the ()
var barObject = new Bar();

Starting with 0.6.15, @JSExportTopLevel should be used instead. On classes, it has exactly the same behavior as @JSExport. On objects however, it directly exports the object, rather than a 0-arg function. Moreover, at least for now, @JSExportTopLevel always demands an explicit string argument.

We would therefore update the previous example as follows:

package foo

@JSExportTopLevel("foo.Foo")
object Foo

@JSExportTopLevel("Bar")
class Bar

and use it from JavaScript as:

var fooObject = foo.Foo; // note the absence of ()
var barObject = new Bar();

If retaining the 0-arg function is desirable in your use case (so that you do not need to update the JavaScript code), you can do the following instead:

package foo

object Foo {
  @JSExportTopLevel("foo.Foo")
  protected def jsAccessor(): this.type = this
}

Migration tips

You can temporarily disable the deprecation warnings with the following sbt setting:

scalacOptions += "-P:scalajs:suppressExportDeprecations"

This will keep working in 0.6.x.

About js.JSApp

If you use js.JSApp, you need not change anything. We have seen people do the following (both extends js.JSApp and @JSExport):

@JSExport
object Foo extends js.JSApp {
  @JSExport
  def main(): Unit = { ... }
}

This is redundant. You can safely remove the two @JSExports while leaving the behavior unchanged:

object Foo extends js.JSApp {
  def main(): Unit = { ... }
}

@JSExportDescendentClasses and @JSExportDescendentObjects are deprecated without (direct) replacement

Those two annotations were basically only used by testing frameworks, to “reflectively” instantiate test classes and objects. For this use case (and similar ones), read further below about the introduction of the Reflective Instantiation API.

Migration tips

Are you maintaining a testing framework? If you use our TestUtils.newInstance and/or TestUtils.loadModule methods to perform reflective instantiation of @JSExportDescendent... things, all you need to do is:

  • replace @JSExportDescendentClasses and/or @JSExportDescendentObjects by @scala.scalajs.reflect.annotation.EnableReflectiveInstantiation
  • use the new overload of TestUtils.newInstance, which uses an explicit list of formal parameters (TestUtils.loadModule stays as it was)

You can also temporarily disable the deprecation warnings with the following sbt setting:

scalacOptions += "-P:scalajs:suppressExportDeprecations"

This will keep working in 0.6.x.

persistLauncher is deprecated in favor of scalaJSUseMainModuleInitializer

The sbt plugin of Scala.js has been providing the persistLauncher setting, to enable the automatic creation of a -launcher.js file, which calls the main method of your application. This convenient setting is being deprecated in favor of an even more convenient feature:

scalaJSUseMainModuleInitializer := true

will include the call to the main method (of a js.JSApp object) directly inside the -fastopt.js and -opt.js files produced by Scala.js. No need for a -launcher.js file at all anymore!

New features

@JSExportStatic

A long awaited feature was to be able to declare static methods and fields in Scala.js-defined JS classes. This is now possible, with @JSExportStatic. When defining a Scala.js-defined JS class, this annotation can be used on members of its companion object:

@JSExportTopLevel("Foo")
@ScalaJSDefined
class Foo extends js.Object

object Foo {
  @JSExportStatic
  val a: Int = 42

  @JSExportStatic
  def b(x: Int): Int = x + 1
}

The members will be available as static members of Foo. Assuming that Foo itself is exported to JavaScript (with @JSExportTopLevel), then we can access these members as follows:

console.log(Foo.a);    // 42
console.log(Foo.b(5)); // 6

vars, getters and setters can also be similarly exported as static.

js.Symbol, js.Iterable[+A] and js.Iterator[+A]

The standard library now includes definitions for some new ECMAScript 2015 types:

  • js.Symbol represents a JavaScript primitive symbol.
  • js.Iterable[+A] is an abstract type for JavaScript objects that can be iterated with for..of. In Scala.js, they can be iterated using a normal for comprehension, as expected.
  • js.Iterator[+A] is a JavaScript iterator, which is the result of the method [Symbol.iterator] of iterables.

@JSName accepts JavaScript symbols

In JavaScript types (extending js.Any, both native and non-native), members can be annotated with @JSName("someName") to specify their JavaScript name, if it needs to be different than the Scala name. With this release, @JSName has been augmented to also accept a reference to a js.Symbol. This is useful/necessary to declare members whose “name” are JavaScript symbols.

For example, one can define a custom JavaScript iterable (which needs to implement a method [Symbol.iterator]) as follows:

@ScalaJSDefined
class SingletonIterable[+A](onlyItem: A) extends js.Iterable[A] {
  @JSName(js.Symbol.iterator)
  def jsIterator(): js.Iterator[A] = new js.Iterator[A] {
    private var done: Boolean = false

    def next(): js.Iterator.Entry[A] = {
      if (done) {
        new js.Iterator.Entry[A] {
          val done: Boolean = true
          def value: Nothing = ???
        }
      } else {
        done = true
        new js.Iterator.Entry[A] {
          val done: Boolean = false
          val value: A = onlyItem
        }
      }
    }
  }
}

An object of that class can be iterated in JavaScript with a for..of loop:

for (const item of someSingletonIterable) {
  console.log(item); // displays once the `onlyItem`
}

@JSExportTopLevel for fields

In addition to being able to use @JSExportTopLevel on methods, which was introduced in 0.6.14, as well as on objects and classes as described above, @JSExportTopLevel can also be used on fields (vals and vars) to export top-level variables to JavaScript. For example:

object Foo {
  @JSExportTopLevel("bar")
  val bar = 42

  @JSExportTopLevel("foobar")
  var foobar = "hello"
}

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

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

If you emit a CommonJS module, the variables are fields of the module instance:

const ScalaJSModule = require('foo-fastopt.js');
console.log(ScalaJSModule.bar);    // 42
console.log(ScalaJSModule.foobar); // "hello"

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

Reflective Instantiation API

When we examined the uses of @JSExportDescendentClasses and @JSExportDescendentObjects found in the wild, we noticed that every time, they were used as a hack to “reflectively” instantiate classes or load objects. No wonder, since we, the core team, showed the world how to do this for testing frameworks!

In this release, we have added an official, principled API to do so. The new API is more reliable and powerful, especially for classes inside objects, as well as classes with overloaded constructors.

Reflective instantiation is still not enabled by default for all classes in the world (as that would completely inhibit dead code elimination). Used on a trait, class or object, the annotation @EnableReflectiveInstantiation enables reflective instantiation for all the non-abstract classes and all the objects inheriting from the annotated entity. Afterwards, it is possible to use the API in scala.scalajs.reflect.Reflect to instantiate those classes and/or load those objects.

For example:

package foo

import scala.scalajs.reflect.annotation.EnableReflectiveInstantiation
import scala.scalajs.reflect.Reflect

@EnableReflectiveInstantiation
trait WithReflInstantiation

class Foobar extends WithReflInstantiation

object Foo {
  def doSomeReflInstantiation(): WithReflInstantiation = {
    val className = "foo.Foobar"
    val instantiatableClass = Reflect.lookupInstantiatableClass(className).get
    val instance = instantiatableClass.newInstance()
    instance.asInstanceOf[WithReflInstantiation]
  }
}

New JDK parts

  • #2739 Some methods of java.lang.{Float,Double} from JDK 8
  • #2764 java.io.DataOutputStream

Bug fixes

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

  • #2708 fastOptJS code not working in JavaScriptCore (JSC)
  • #2712 ScalaRunTime.isArray is buggy for instances of JavaScript classes
  • #2737 Failure when accessing protected buf in ByteArrayOutputStream
  • #2755 java.math.BigDecimal.divide fails when used with a scale and rounding mode

You can find the full list on GitHub.