This is a step-by-step tutorial where we start with the setup of a Scala.js sbt project and end up having some user interaction and unit testing. The code created in this tutorial is available with one commit per step in the scalajs-tutorial repository on GitHub.
To go through this tutorial, you will need to download & install sbt. Note that no prior sbt knowledge (only a working installation) is required to follow the tutorial.
You will also need to download & install Node.js.
To run the complete tutorial application, you will also need to install jsdom as explained below.
First create a new folder where your sbt project will go.
To setup Scala.js in a new sbt project, we need to do two things:
- Add the Scala.js sbt plugin to the build
- Enable the plugin in the project
Adding the Scala.js sbt plugin is a one-liner in
project/plugins.sbt (all file names we write in this tutorial are relative to the project root):
We also setup basic project settings and enable this plugin in the sbt build file (
build.sbt, in the project root directory):
Last, we need a
project/build.properties to specify the sbt version (you can find the latest version here):
That is all we need to configure the build.
If at this point you prefer to use an IDE, you can import the build into VS Code with Metals (or any other editor supported by Metals) or IntelliJ IDEA (see “Installation” here). Note that for compiling and running your application, you will still need to use sbt from the command line.
For starters, we add a very simple
TutorialApp in the
tutorial.webapp package. Create the file
As you expect, this will simply print “HelloWorld” when run. To run this, simply launch
sbt and invoke the
$ sbt sbt:Scala.js Tutorial> run [info] Compiling 1 Scala source to (...)/scalajs-tutorial/target/scala-2.13/classes ... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-fastopt [info] Running tutorial.webapp.TutorialApp. Hit any key to interrupt. Hello world! [success] (...)
Source maps in Node.js: To get your stack traces resolved on Node.js, you will have to install the
$ npm install source-map-support
- Create an HTML page which includes that file
> fastLinkJS [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-fastopt [success] (...)
This will perform some fast optimizations and generate the
(It is possible that the
[info] does not appear, if you have just run the program and not made any change to it.)
Create the HTML Page
scalajs-tutorial-fastopt.html (or whatever name you prefer, for example
index-dev.html) in the project root with the following content. We will go in the details right after.
The script tag simply includes the generated code (attention, you might need to adapt the Scala version from
2.12 (or even
2.11) here if you are using an older version of Scala).
Since we have set
scalaJSUseMainModuleInitializer := true in the build, the
TutorialApp.main(args: Array[String]) method is automatically called at the end of the
-fastopt.js file (with an empty array as argument).
If you now open the newly created HTML page in your favorite browser, you will see … nothing. The
println in the
Adding the DOM Library
To use the DOM, it is best to use the statically typed Scala.js DOM library. To add it to your sbt project, add the following line to your
sbt-savvy folks will notice the
%%% instead of the usual
%%. It means we are using a Scala.js library and not a
normal Scala library. Have a look at the Dependencies guide for details. Don’t forget
to reload the build file if sbt is still running:
sbt:Scala.js Tutorial> reload [info] Loading settings for project global-plugins from plugins.sbt ... [info] Loading global plugins from (...)/.sbt/1.0/plugins [info] Loading settings for project scalajs-tutorial-build from plugins.sbt ... [info] Loading project definition from (...)/scalajs-tutorial/project [info] Loading settings for project scala-js-tutorial from build.sbt ... [info] Set current project to Scala.js Tutorial (in build file:(...)/scalajs-tutorial/)
If you are using an IDE plugin, you will also have to reimport the build for autocompletion to work.
Using the DOM Library
Now that we added the DOM library, let’s adapt our HelloWorld example to add a
<p> tag to the body of the page, rather than printing to the console.
First of all, we import a couple of things:
We additionally import
document (which corresponds to
We now create a method that allows us to append a
<p> tag with a given text to a given node:
Replace the call to
println with a call to
appendPar in the
sbt:Scala.js Tutorial> fastLinkJS [info] Compiling 1 Scala source to (...)/scalajs-tutorial/target/scala-2.13/classes ... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-fastopt [success] (...)
As you can see from the log, sbt automatically detects that the sources must be recompiled before fast optimizing.
You can now reload the HTML in your browser and you should see a nice “Hello World” message.
fastLinkJS each time you change your source file is cumbersome. Luckily sbt is able to watch your files and recompile as needed:
sbt:Scala.js Tutorial> ~fastLinkJS [success] (...) [info] 1. Monitoring source files for scalajs-tutorial/fastLinkJS... [info] Press <enter> to interrupt or '?' for more options.
From this point in the tutorial we assume you have an sbt with this command running, so we don’t need to bother with rebuilding each time.
This step shows how you can add a button and react to events on it by still just using the DOM. We want to add a button that adds another
<p> tag to the body when it is clicked.
We start by adding a method to
TutorialApp which will be called when the button is clicked:
You will notice the
onclick attribute (make sure to add the button before the
Reload your HTML page (remember, sbt compiles your code automatically) and try to click the button. It should add a new paragraph saying “You clicked the button!” each time you click it.
Previously, we have prepared the UI as an HTML document, then manipulated it from Scala.js code.
That approach required us to manually export the method used for the
onclick event of the button, which is cumbersome.
We can avoid this issue by building the UI directly from the Scala.js code.
Instead of preparing the button in the HTML, we can add the following code to the
main method in Scala.js:
This uses an anonymous function that we give to
We can remove the
<button> tag from the HTML file, and hence remove the
@JSExportTopLevel annotation on
addClickedMessage() (even though it will be indirectly called through the anonymous function).
As a last touch, we extract the setup of the UI in a separate method
def setupUI(), which we will call only once the DOM is loaded, instead of synchronously from the
You can now refresh the webpage to test the above changes.
We now have an application whose UI is completely setup from within Scala.js. The next step will show how we can test this application.
In this section we will show how such an application can be tested using uTest, a tiny testing framework which compiles to both Scala.js and Scala JVM. As a note aside, this framework is also a good choice to test libraries that cross compile. See our cross compilation guide for details.
Supporting the DOM
Before we start writing tests which we will be able to run through the sbt console, we first have to solve another
issue. Remember the task
run? If you try to invoke it now, you will see something like this:
sbt:Scala.js Tutorial> run [info] Running tutorial.webapp.TutorialApp. Hit any key to interrupt. (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-fastopt/main.js:819 $thiz.Lorg_scalajs_dom_package$__f_window = window; ^ ReferenceError: window is not defined at $p_Lorg_scalajs_dom_package$__window$lzycompute__Lorg_scalajs_dom_raw_Window ((...)/org/scalajs/dom/package.scala:219:40) ... at $c_Ltutorial_webapp_TutorialApp$.main__AT__V ((...)/tutorial/webapp/TutorialApp.scala:8:5) ... [error] org.scalajs.jsenv.ExternalJSRun$NonZeroExitException: exited with code 1 [error] at org.scalajs.jsenv.ExternalJSRun$$anon$1.run(ExternalJSRun.scala:186) [error] stack trace is suppressed; run last Compile / run for the full output [error] (Compile / run) org.scalajs.jsenv.ExternalJSRun$NonZeroExitException: exited with code 1 [error] Total time: (...)
The issue we encounter is that our
main method tries to access DOM functionality, which is not available in Node.js.
To make the DOM available, add the following to your
and the following to your
This will use the
jsdom library to simulate a DOM in Node.js.
You need to install it separately.
It is usually best to install it locally for this project.
You can initialize a private
package.json as follows:
$ npm init private
Then you can install jsdom:
$ npm install jsdom
After reloading, you can invoke
> run [info] Running tutorial.webapp.TutorialApp [success] (...)
Using a testing framework in Scala.js is not much different than on the JVM.
It typically boils down to two sbt settings in the
For uTest, these are:
We are now ready to add a first simple test suite (
This test uses
querySelectorAll to find all the
<p> elements in the document, and
count those whose
textContent is the
count method is part of the Scala collections API, and is provided on DOM
NodeLists by the
To run this test, simply invoke the
> test [info] Compiling 1 Scala source to (...)/scalajs-tutorial/target/scala-2.13/test-classes... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-test-fastopt -------------------------------- Running Tests -------------------------------- + tutorial.webapp.TutorialTest.HelloWorld 2ms Tests: 1, Passed: 1, Failed: 0 [success] Total time: 14 s, completed 16-mars-2018 20:04:28
We have successfully created a simple test.
test task uses Node.js to execute your tests.
A more complex test
We also would like to test the functionality of our button:
After defining a helper method that counts the number of messages, we retrieve the button from the DOM and verify we have exactly one button and no messages. In the loop, we simulate a click on the button and then verify that the number of messages has increased.
You can now call the
test task again:
> test [info] Compiling 1 Scala source to (...)/scalajs-tutorial/target/scala-2.13/test-classes... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-test-fastopt -------------------------------- Running Tests -------------------------------- + tutorial.webapp.TutorialTest.HelloWorld 3ms + tutorial.webapp.TutorialTest.ButtonClick 6ms Tests: 2, Passed: 2, Failed: 0 [success] Total time: 15 s, completed 16-mars-2018 20:07:33
This completes the testing part of this tutorial.
Here we show a couple of things you might want to do when you promote your application to production.
uses the advanced optimizations of the Google Closure Compiler. To run
full optimizations, simply use the
> fullLinkJS [info] Full optimizing (...)/scalajs-tutorial/target/scala-2.13/scala-js-tutorial-opt [info] Closure: 0 error(s), 0 warning(s) [success] (...)
Note that this can take a while on a larger project (tens of seconds), which is why we typically don’t use
during development, but
fastLinkJS instead. If you want to
test the full-optimized version from sbt,
you need to change the stage using the following sbt setting:
> set scalaJSStage in Global := FullOptStage
(by default, the stage is
We also need to create our final production HTML file
scalajs-tutorial.html which includes the fully optimized code:
If you serve your Scala.js application from a web server, you should additionally
gzip the resulting
.js files. This step might reduce the size of your application down
to 20% of its original size.
This completes the Scala.js tutorial. Refer to our documentation page for deeper insights into various aspects of Scala.js.