Surely there’s something in JavaScript that Scala.js can’t handle? If there is, I’ve not found it yet. Each time I’ve faced some JavaScript obstacle I’ve been overjoyed to find an answer in Scala.js.
In this post I’ll highlight 3 features from Scala.js 1.x that I’ve recently used to dig myself out of a hole.
The features are: representing JavaScript global scope values, importing Node modules,
and working with JavaScript’s this
.
The common thread is that these features let you safely model JavaScript concepts in Scala.
Scala.js
Scala.js is a Scala to JavaScript compiler.
In practical terms this means:
- you write regular code, using cross-compiled Scala libraries if you like;
- then run
fastOptJs
in sbt to have your program output as JavaScript; and - run that code in a browser, with Node, or wherever you find a JavaScript environment.
That’s the basics. If you’d like a little bit more of an introduction, I’ve written and spoken about this before (video), as have many others.
Using Scala to write JavaScript is only half the story. Scala.js also has wonderful features to interface to the rest of the JavaScript ecosystem. In other words, Scala.js lets you model JavaScript concepts in Scala.
An example application
We need an example to help make sense of the three features I want to describe.
I’ve been working on serverless application that runs under Node. The plan is: use Scala to write the application, but deploy it to a Node environment.
In the Node environment there are libraries I need. Specifically, I want to use the Amazon Alexa APIs. We don’t need to go into that too much, except for two things.
First, we’ll need to reference existing JavaScript library code from our Scala code. That means importing a library and working with global JavaScript scope.
The second thing to know is that we’re writing a Request => Response
function.
The request will be an “intent” (such as “TellJoke”) and the response will be the text to be spoken (such as a terrible Dad joke).
However, we’re going to have to turn that into something that looks like a () => Unit
.
The side-effect in there is calling the Node APIs to read values and set the response.
Not nice, but the point here is that Scala.js can represent the JavaScript API we’ve been handed.
Feature 1: JSGlobalScope – representing global JavaScript values to Scala
To get started, we need to access the JavaScript global state. The Alexa/Lambda platform expects you to register a handler. This happens in JavaScript as something like this:
// This is JavaScript, using the Alexa v1 API
exports.handler = function (event, context) {
// Set up our handler, which we'll see later.
};
This registration happens when the JavaScript loads and runs.
What’s the Scala.js equivalent of that going to be? It’ll be a main
method:
def main(args: Array[String]): Unit =
exports.handler = (event, context) => registrationCodeHere()
So the first trick is how that exports.handler
is represented in Scala:
@js.native
@JSGlobal("exports")
object exports extends js.Object {
var handler: js.Function2[RequestBody[Request], Context, Unit] = js.native
}
What’s happening here is that we’re declaring, typing, and positioning the exports.handler
.
Here’s what I mean by that…
For Scala, we’re defining an object with a variable so we can write exports.handler = ...
.
The type of that value is a JavaScript function, as that’s what the JavaScript environment is expecting.
The type parameters come from a facade to the Alexa Node library, which is detail I’m mostly skipping in this post.
The implementation of that handler
value is provided by the JavaScript environment.
It has, in other words, a js.native
implementation.
The exports
object itself is also provided for our program at runtime,
and is annotated as @js.native
.
The @JSGLobal
means it’s living outside of the modular world Scala.js creates for our programme,
and won’t be created by Scala.js: it had better exist when the program runs.
In other words, we can model the environment we’re running in, reach out and modify it.
Feature 2: JSImport – referencing provided node libraries
We can now register a function, so let’s take another step and implement the Alexa pattern for responding to an intent:
// This is JavaScript, using the Alexa v1 API
// Load the Node module for Alexa SDK:
const Alexa = require('alexa-sdk');
exports.handler = function (event, context) {
const alexa = Alexa.handler(event, context);
alexa.registerHandlers(ourCodeHere);
alexa.execute();
};
This is boilerplate for getting ourCodeHere
called.
The hurdle for our Scala code is how to get access to the alexa-sdk
from Scala.js.
The way I like to do that is to annotate the Scala facade with an import name.
This is a second feature to help us model JavaScript in a Scala programme.
Here’s the relevant part of the facade:
@js.native
@JSImport("alexa-sdk", JSImport.Namespace)
object Alexa extends js.Object {
def handler[T <: Request](e: RequestBody[T], c: Context): AlexaObject[T] =
js.native
}
Again, we’re modelling a provided (js.native
) object.
This time we’re also marking it as a module that will be imported.
The module is called alexa-sdk
and we’re importing the module itself (JSImport.Namespace
),
rather than say a specific member of the module.
What that means in practice is that the JavaScript output from fastOptJs
will end up containing something like this:
// This is JavaScript, using the Alexa v1 API
var Alexa = require("alexa-sdk");
…effectively loading the library and giving us access to it as Alexa
.
Now, in my particular case, the node module is provided already, so this is enough. I know it will be there when my JavaScript is loaded.
However, for packing and checking Node libraries scalajs-bundler looks like the right solution (at the time of writing not available for Scala.js 1.x).
Feature 3: ThisFunction – modelling the type changing under you
At this point we have loaded a library and registered a function. In Scala this is:
def main(args: Array[String]): Unit = {
exports.handler = (event, context) => {
val alexa = Alexa.handler(event, context)
alexa.registerHandlers( ??? )
alexa.execute()
}
}
Now we need to figure out what to put in place of the ???
in alexa.registerHandlers
.
A JavaScript handler might look something like this:
// This is JavaScript, using the Alexa v1 API
const handlers = {
Greet() {
this.emit(":tell", "Hello Sailor");
}
}
Seems straightforward, but where is that emit
method coming from?
It’s not a parameter (there are none!), it’s not on a class, it’s not in scope of handlers
.
What’s happening is Greet
is being placed into a context that has emit
,
and many other fields, down in the JavaScript library code.
We don’t need to worry about the JavaScript native implementation, but we do need to model this in Scala.
I initially thought that perhaps a self type would solve this.
That is, maybe my Scala version of Greet
could be mixed in with something that has an emit
method.
But the problem with that is we are not implementing emit
in Scala. We just need to model it from JavaScript.
The solution Scala.js provides is neat: we are given the this
type as a parameter to the Scala function.
It is modelled as ThisFunction
.
This is like Function0
, Function1
and friends, but with an extra type parameter to represent the changed value for this
.
In this specific example, the Scala.js model for Greet
becomes a…
js.ThisFunction0[Handler[Request], Unit]
This is a () => Unit
method, with an addition parameter.
The additional parameter is Handler[Request]
which (in the facade) is the model for the JavaScript this
in this context.
I.e., Handler[Request]
happens to be the thing with that emit
method we wanted.
Finally, the Scala implementation of Greet
might be:
def greet(handler: Handler[Request]): Unit =
handler.emit("Hello Sailor")
This means we can write a sane looking Scala method: it takes an explicit dependency we need.
Behind the scenes, Scala.js arranges for this
to be passed even though the JavaScript API
provides no parameters.
Summary
When interfacing to a JavaScript environment, the question to ask is: how do I model the JavaScript interface in Scala? Scala.js has a set of tools to help with this.
We’ve seen three techniques in this post:
- global scope access;
- depending on existing Node libraries; and
- modelling the dynamic
this
value.
The Scala.js types page describe more useful tricks for JavaScript interop.