Chapter 12
Scala.js

Scala.js is a relatively new feature in Scala that allows you to compile Scala code straight to JavaScript, providing you with the full power of the Scala language, while being able to target JavaScript. It also allows you to share code between the server and the client, which can dramatically reduce boilerplate, especially if you use one of the many pickling/serialization libraries available in Scala.

This chapter also covers Webjars, which are Maven packages with installed resources. Using Webjars, plus the powerful interoperability features that Scala.js provides, provides you with a compelling and fresh way to tackle the highly complex area of web programming.

SCALA.JS AND ITS DESIGN

Scala.js, foremost, is a Scala compiler. That is, it takes .scala files, and compiles them to an intermediate bytecode specifically designed for JavaScript (.sjsir) files. When combined with .sbt, you can then produce an actual .js file.

This means that Scala.js does not work with Java source code, nor does it work with JVM bytecode. This means that when using Scala.js, you must use pure Scala code and any dependencies that you may have also have to be written in pure Scala. For this reason, you may have difficulties in using Scala.js in a project that heavily uses Java (either directly or indirectly). Strong integration with Scala.js and SBT means that it's quite easy to cross compile Scala projects. Although, you may think that this limitation is quite severe, it also means that Scala.js provides many benefits that are critical in providing practical success in modern web programming.

One of these advantages is that Scala.js uses a very accurate DCE, which is a requirement for minimizing assets for the client. The reason for this accuracy is that since it works on Scala source code, Scala.js is able to determine which methods are being used and which methods aren't, with a much higher dividend compared to just dealing with pure bytecode. The usage of DCEs is nothing new for web programming (or programming in general); however how much code can be eliminated depends on how strongly statically typed the language is and since idiomatic Scala is very strongly typed, this is a very big benefit. On top of this, Scala.js also uses the Google Closure Compiler for production output. This combination means that it's possible to reduce very large codebases (with a lot of dependencies) to sizes that are smaller than what is possible in JavaScript.

The above however also means that Scala.js isn't completely suited to lazy modules (i.e. modules that are loaded lazily for a specific section of the site). Due to how the Scala DCE works, one would have to include the Scala library (such as the collections library) in each lazy module you would define. Scala.js is much more suited to creating a single .js file, which encompasses your project, rather than many separate .js files.

The usage of Scala source files also means that Scala.js combined with SBT also provides a module system, which is exactly the same module system that is used for using Scala normally with the JVM. This means that Scala.js provides an established solution to a problem that plagues the JavaScript fragmentation in module sytems, due to there not being an established standard (AMD/ Common.js /npm/bower/require.js etc). Libraries created in Scala.js are packaged in JAR files, and they use the same directory layout as the standard JVM jars. You don't need to worry about how to load files with their dependency trees (a problem that Require.js resolves), because this is all handled by SBT.

Arguably the strongest strength of Scala.js, which is a notable difference from many other language -> JavaScript compilers (particularly static languages), is that Scala.js has an easy-to-use FFI for libraries written in JavaScript. This means that you don't have to reimplement a huge amount of the JavaScript ecosystem in Scala. There are Scala.js bindings for many popular web frameworks, such as React and Angular. This is possible due to scala.dynamic (which allows you to dynamically define classes and methods) along with Scala facades (this allows you to enforce types on current JavaScript libraries).

This combination allows you to provide a Scala experience while still being able to interoperate with the current JavaScript ecosystem, as well as offering benefits that aren't available in other languages.

GETTING STARTED: SCALA.JS WITH SBT

One of the easiest ways to learn how Scala.js works is to see how a cross build is set up from scratch. Scala.js hosts a bare repo on Github at https://github.com/scala-js/scalajs-cross-compile-example, which shows the basic structure of a Scala.js application, so let's get started by cloning it:

git clone https://github.com/scala-js/scalajs-cross-compile-example

Before starting, it's important to note that Scala.js is implemented as an SBT plugin, which can be seen if you open project/plugins.sbt:

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.3")

The first thing you see is that the structure is slightly different compared to a typical project. In this case, you have a root project and two subprojects (one for JVM and one for Scala.js). Code that is within the JVM directory is only compiled for the JVM, and code that is contained within the js directory is only compiled for JavaScript, and code in shared is compiled for all targets. If you examine the build.sbt, you can see how this is set up.

name := "Foo root project"

lazy val root = project.in(file(".")).
  aggregate(fooJS, fooJVM).
  settings(
    publish := {},
    publishLocal := {}
  )

lazy val foo = crossProject.in(file(".")).
  settings(
    name := "foo",
    version := "0.1-SNAPSHOT",
    scalaVersion := "2.11.6"
  ).
  jvmSettings(
    // Add JVM-specific settings here
  ).
  jsSettings(
    // Add JS-specific settings here
  )

lazy val fooJVM = foo.jvm
lazy val fooJS = foo.js

This template makes use of the sbt aggregate feature (http://www.scala-sbt.org/0.13/docs/Multi-Project.html). The interesting thing to note here is that you can override publish and publishLocal. This is because publishing the “root” project doesn't make sense. When you publish, you want to publish only the subprojects in root (which is the JVM and JavaScript build). Beneath this, you see jsSettings and jvmSetting, which respectively allow you to specify settings for only JavaScript, or for only the JVM. JVM settings such as “-target:jvm-1.8” when used with scalacOptions are pointless when compiling for JavaScript, whereas options such as “-Ywarn-dead-code” can be placed in shared settings.

The most obvious usecase for splitting out the settings is for dependencies. In Scala.js, dependencies for JavaScript are packaged separately from the standard JVM Maven packages (this is also due to the fact that Scala.js has its own notion of binary compatibility, which is separate from JVM binary compatibility). The easiest way to see this is to run publishLocal on this current project.

[info] Done packaging.
[info]      published foo_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_2.11/0.1-SNAPSHOT/poms/foo_2.11.pom
[info]      published foo_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_2.11/0.1-SNAPSHOT/jars/foo_2.11.jar
[info]      published foo_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_2.11/0.1-SNAPSHOT/srcs/foo_2.11-sources.jar
[info]      published foo_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_2.11/0.1-SNAPSHOT/docs/foo_2.11-javadoc.jar
[info]      published ivy to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_2.11/0.1-SNAPSHOT/ivys/ivy.xml
[info]      published foo_sjs0.6_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_sjs0.6_2.11/0.1-SNAPSHOT/poms/foo_sjs0.6_2.11.pom
[info]      published foo_sjs0.6_2.11 to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_sjs0.6_2.11/0.1-SNAPSHOT/jars/foo_sjs0.6_2.11.jar
[info]      published foo_sjs0.6_2.11 to /Users/matthewdedetrich/.ivy2/local/
  foo/foo_sjs0.6_2.11/0.1-SNAPSHOT/srcs/foo_sjs0.6_2.11-sources.jar
[info]      published foo_sjs0.6_2.11 to /Users/matthewdedetrich/.ivy2/local/
  foo/foo_sjs0.6_2.11/0.1-SNAPSHOT/docs/foo_sjs0.6_2.11-javadoc.jar
[info]      published ivy to /Users/matthewdedetrich/.ivy2/local/foo/
  foo_sjs0.6_2.11/0.1-SNAPSHOT/ivys/ivy.xml

As you can see, the main JVM package is specified as /foo/foo_2.11/0.1-SNAPSHOT/, and then for the JavaScript you have foo/foo_sjs0.6_2.11/0.1-SNAPSHOT. The foo_sjs0.6_2.11 means that this package is binary compatible with Scala.js version 0.6.x.

For the example here, let's add Scalatags as a dependency. Scalatags is a library for HTML templating; however the main interest is that it is cross-compiled for JVM and Scala.js. The documentation states that the JVM dependency is “com.lihaoyi” %% “scalatags” % “0.5.4” whereas the Scala.js dependency is “com.lihaoyi” %%% “scalatags” % “0.5.4”. Let's update build.sbt to reflect this (as well as add some common scalacOptions as mentioned previously):

lazy val foo = crossProject.in(file(".")).
  settings(
    name := "foo",
    version := "0.1-SNAPSHOT",
    scalaVersion := "2.11.6",
          scalacOptions ++= Seq(
               "-encoding", "UTF-8",
         "-deprecation", // warning and location for usages of deprecated APIs
         "-feature", // warning and location for usages of features that should
           be imported explicitly
         "-unchecked", // additional warnings where generated code depends
           on assumptions
         "-Xlint", // recommended additional warnings
         "-Xcheckinit", // runtime error when a val is not initialized due to
            trait hierarchies (instead of NPE somewhere else)
         "-Ywarn-adapted-args", // Warn if an argument list is modified to match
           the receiver
         "-Ywarn-value-discard", // Warn when non-Unit expression results are
            unused
         "-Ywarn-inaccessible",
         "-Ywarn-dead-code"
          )
  ).
  jvmSettings(
    libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.5.4",
          scalacOptions ++= Seq(
               "-target:jvm-1.8"
          )
  ).
  jsSettings(
    libraryDependencies += "com.lihaoyi" %%% "scalatags" % "0.5.4"
  )

lazy val fooJVM = foo.jvm
lazy val fooJS = foo.js

In the preceding example, Scalatags is added as a dependency for both JVM and Scala.js, and specific settings for just compiling on JVM (in our case, we are setting the target to be JDK 1.8) have also been added. This is easy to do because Scalatags is a cross compiled project. If you open up shared/src/main/scala/MyLibrary.scala, you will see a trivial implementation.

class MyLibrary {
    def sq(x: Int): Int = x * x
}

Let's extend this, and generate some HTML using ScalaTags:

import scalatags.Text.all._

object MyLibrary {
def template: String = (
      html(
        div(
          p("This is my template")
        )
      )
    ).render
}

Since this is contained in shared, it will be compiled for both targets. You also have to make this visible to both targets. Let's modify js/src/main/scala/Main.scala to look like this:

import scala.scalajs.js

object Main extends js.JSApp {
def main(): Unit = {
    val lib = new MyLibrary
    println(lib.sq(2))
    println(MyLibrary.template)
}
}

And let's modify jvm/src/main/scala/Main.scala to look like this:

object Main extends App {
def main(): Unit = {
    val lib = new MyLibrary
    println(lib.sq(2))
           println(MyLibrary.template)
}
}

What you have just done is modify the entry points for the project. Just as Scala has App to specify how the application is run when you execute the .jar, Scala.js has a js.JsApp to specify the entry point for the JavaScript application.

In SBT, if you fooJS/run you will see the output for js/src/main/scala/Main.scala, whereas running fooJVM/run will provide the output for jvm/src/main/scala/Main.scala. Extending js.jsApp also dictates the entry point for JavaScript, and hence how the .js file is actually created. Running fastOptJS will create the .js file in js/target/scala-2.11/foo-fastopt.js, which you can directly include in the header in any HTML. fullOptJS runs a full optimization that creates a file in js/target/scala-2.11/foo-opt.js. This takes much longer than fastOptJS; however it also generates a much smaller file. It's recommended to use fastOptJS when developing locally, since file size is not a concern there.

At this point, you now know the basics of how Scala.js works. We have set up an example that uses cross compilation technique (using SBT's aggregate), and we have also learned how to separate and share SBT settings (including dependencies). In addition we have shown how to run the program (both in JVM and JavaScript), as well as how to generate the .js file, which is what is included in the site.

SCALA.JS PECULIARITIES

Scala.js does a fantastic job in compiling most Scala code; however, there are corner cases that must be investigated. These corner cases exist because JavaScript isn't a very low level VM, and hence there are restrictions on how certain code can be produced if you also want to maintain reasonable performance.

One very good example is how numbers are treated. In the JavaScript specification, there is only one number type. However in the JVM (and hence Scala), there are numerous number types, such as Long/Int/Float/Double. In order to avoid boxing and its associated performance penalties, Scala.js has had to make compromises in this area. With the previous examples of numbers, Scala.js will map most of the Scala primitive number types to the same JavaScript number type, which means precision (and hence certain math operations) may be defined differently on separate platforms.

Scala.js also handles opaque types. These are types that have no actual representation in JavaScript (the most common example of this is Char). Opaque types are essentially types that can only be exported to JavaScript. They can't be directly used unless you explicitly code conversions and there also isn't any way to manipulate the type in JavaScript. More information on this can be found here http://www.scala-js.org/doc/interoperability/types.html.

Another area in which a difference can be found is exceptions. The JavaScript platform doesn't provide native support for all of the types of exceptions that can be found on the JVM, so implementing them manually in JavaScript is very expensive from a performance perspective. Scala.js provides three ways of dealing with this issue: Compliant, Unchecked and Fatal. Compliant provides a full JVM specification; however it is very slow. Unchecked means that exceptions will be thrown, so the runtime behavior is completely undefined. Fatal will throw an exception; however it will be an UndefiniedBehaviourError, instead of the original exception. More details about dealing with exceptions can be found at http://www.scala-js.org/doc/semantics.html. The link also describes instances where pattern matching is different among other sematic differences.

WEBJARS AND DEALING WITH THE FRONTEND ECOSYTEM

As mentioned previously, the current web ecosystem is heavily fragmented, especially when it comes to dealing with JavaScript modules, and their representation in files. It isn't unusual for web frameworks to create their own asset management systems. As an example, Ruby on Rails has already gone through two different ways of managing assets. There are also competing ways to load files (manually, using tools like require.js/webpack).

This problem is further complicated when having to deal with other JavaScript style languages such as CoffeeScript, TypeScript or newer versions of EMCAScript. The combination of all of these has also created quite complex tooling/build tools (Grunt.js, yeoman, NPM)

In Java/Scala with Webjars, these issues are practically nonexistent. Maven/Ivy already provides all of the tooling and dependency management necessary for working with build/module management.

Just as with Scala.js, Webjars are just Maven packages. More specifically, they are Maven packages with resources that are installed with specific paths (so that they can be discovered by various methods). Due to this simplicity, Webjars are very easy to work with. They just contain the compiled assets that are needed, and any dependencies are treated just as normal Maven dependencies. Furthermore, since Maven relies on immutable artifacts as part of its core design, it avoids a lot of issues that happen when using repositories as packages (which is common in frontend development).

The most powerful feature of Webjars, however, is the fact that they can be automatically generated from Bower packages (as well as being manually created, or what is referred to as classic Webjars). Webjars has a website that will convert, on demand, valid Bower packages to a Maven style package, with all of its dependencies defined and deploy it onto Bintray. The website (see Figure 12.1) for converting bower packages to Webjars can be found at http://www.webjars.org/bower.

Snapshot of a website that converts bower packages to Webjars.

Figure 12.1

As can be seen by the screenshot, using the Webjars service to convert bower packages is very easy. To include a Webjar into a project (regardless of whether it's a bower Webjar or a normal classic Webjar) you simply need to add it as a dependency in libraryDependencies. As an example, if you want to add the latest current version of AngularJS, you simply need to add “org.webjars.bower” % “angularjs” % “1.4.8” in your libraryDependencies in build.sbt.

At its lowest level, you can access the contents of a Webjar using .getResource. To load the angular.js file from the above example, you can do the following:

scala.io.Source.fromURL(getClass
.getResource("/META-INF/resources/webjars/angularjs/1.4.8/angular.js")
).mkString

This is quite a manual process and wouldn't typically be done (it's to demonstrate that Webjars are just standard resources). You should use one of the helpers that are provided by various web frameworks, and the list can be seen at http://www.webjars.org/documentation. It is recommended that you use the web framework integration if it's available, since the helpers often provide integrations and tooling features like caching and minification.

One caveat that you need to be aware of is that sometimes the dependencies that automatically get added into the Maven package may not actually be available. This can happen due to several reasons. One is that a dependency is not available on Bower because it hasn't been converted yet. The solution to this is issue is simple; you just need to convert the dependency to a bower Webjar using the same process. The other cause could be that the dependency could not be converted due to a licensing issue. Since Bower Webjars deploy onto Bintray, the system requires a valid license to be defined. Bower package management allows you to specify Github repos as dependencies (and it doesn't mandate that a proper license be provided).

There are a couple of ways to resolve this issue. The most immediate one is to make an issue on the relevant project's website and ask them to provide a valid Bower license file. Another is to have a look if a classic Webjar exists for that dependency. Often people use Bower dependencies that just point to a Github repo that happens to be missing the license (even though the dependency in question has a valid license). Thankfully, SBT gives us the tools to deal with this situation.

If you have a look at the bower Webjar for foundation, you will see that it depends on modernizr, which isn't available as a bower Webjar (however, it is available as a classic Webjar).

libraryDependencies ++= Seq(
  "org.webjars.bower"      % "foundation" % "5.5.4" exclude
   ("org.webjars.bower", "modernizr"),
  "org.webjars"            % "modernizr" % "2.8.3"
)

As you can see, we use the exclude option to prevent a dependency from being loaded by Webjars. You can then manually load that dependency as a classic Webjar. Although this process is reminiscent of manual dependency management, it's also quite rare.

The last resort is to create your own Webjar and deploy it either locally or to your own Maven repository. Since Webjars are just Maven projects, creating them is very easy, and templates for the current class Webjars can be found on the github organization page: https://github.com/webjars.

SUMMARY

For those of you who love to use JavaScript, this chapter has detailed some ways that you can interact with Scala using the JavaScript language. By using Scala.js you now know that you can compile Scala code straight to JavaScript. This allows you to share code between the server and the client, which can dramatically reduce boilerplate.

This also happens to be the last chapter in this book. We hope that it has proven useful for you and that it helps you program with Scala.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.181