This 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 (>= 0.13.0). 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.
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 (>= 0.13.7):
That is all we need to configure the build.
If at this point you prefer to use Eclipse or IDEA as your IDE, you may use sbteclipse to generate an Eclipse project, or import the sbt build from IDEA. 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 > run [info] Compiling 1 Scala source to (...)/scala-js-tutorial/target/scala-2.11/classes... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.11/scala-js-tutorial-fastopt.js [info] Running tutorial.webapp.TutorialApp Hello world! [success] (...)
last command in sbt:
> last (...) [info] Running tutorial.webapp.TutorialApp [debug] with JSEnv ExternalJSEnv for Node.js [debug] Starting process: node [success] (...)
So your code has actually been executed by Node.js.
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 and calls the application
> fastOptJS [info] Fast optimizing (...)/scala-js-tutorial/target/scala-2.11/scala-js-tutorial-fastopt.js [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 first script tag simply includes the generated code (attention, you might need to adapt the Scala version from
2.10 here if you are using Scala 2.10.x instead of 2.11.x).
In the second script tag, we first get the
TutorialApp object. Note the
main method on the
JSApp, the object itself and its
main method are automatically made available to
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:
> reload [info] Loading global plugins from (...) [info] Loading project definition from (...)/scala-js-tutorial/project [info] Set current project to Scala.js Tutorial (in build (...)/scala-js-tutorial/)
If you are using an IDE plugin, you will also have to regenerate the project files 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
> fastOptJS [info] Compiling 1 Scala source to (...)/scala-js-tutorial/target/scala-2.11/classes... [info] Fast optimizing (...)/scala-js-tutorial/target/scala-2.11/scala-js-tutorial-fastopt.js [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.
fastOptJS each time you change your source file is cumbersome. Luckily sbt is able to watch your files and recompile as needed:
> ~fastOptJS [success] (...) 1. Waiting for source changes... (press enter to interrupt)
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 will use jQuery in the next step). 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.
Depending on jQuery
Just like for the DOM, there is a typed library for jQuery available in Scala.js. Replace the
libraryDependencies += .. line in your
Since we won’t be using the DOM directly, we don’t need the old library anymore. Note that the jQuery library internally depends on the DOM, but we don’t have to care about this. sbt takes care of it automatically.
Don’t forget to reload the sbt configuration now:
- Hit enter to abort the
Again, make sure to update your IDE project files if you are using a plugin.
TutorialApp.scala, remove the imports for the DOM, and add the import for jQuery:
This allows you to easily access the
jQuery object (usually referred to as
We can now remove
appendPar and replace all calls to it by the simple:
[message] is the string originally passed to
If you try to reload your webpage now, it will not work (typically a
TypeError would be reported in the console). The
An option is to include
jquery.js from an external source, such as jsDelivr.
After reloading and rerunning
fastOptJS, this will create
Setup UI in Scala.js
We still want to get rid of the
onclick attribute of our
<button>. After removing the attribute, we add the
setupUI method, in which we use jQuery to add an event handler to the button. We also move the “Hello World” message
into this function.
Since we do not call
@JSExport annotation (and
the corresponding import).
Finally, we add a last call to
jQuery in the main method, in order to execute
setupUI, once the DOM is loaded:
Again, since we are not calling
jQuery will call it).
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:
> run [info] Running tutorial.webapp.TutorialApp [error] TypeError: (0 , $m_Lorg_scalajs_jquery_package$(...).jQuery$1) is not a function [error] at $c_Ltutorial_webapp_TutorialApp$.main__V (.../scalajs-tutorial/src/main/scala/tutorial/webapp/TutorialApp.scala:9:12) [error] at $c_Ltutorial_webapp_TutorialApp$.$$js$exported$meth$main__O (https:/raw.githubusercontent.com/scala-js/scala-js/v0.6.13/library/src/main/scala/scala/scalajs/js/JSApp.scala:18:4) [error] at $c_Ltutorial_webapp_TutorialApp$.main (.../scalajs-tutorial/src/main/scala/tutorial/webapp/TutorialApp.scala:7:8) [error] ... [trace] Stack trace suppressed: run last compile:run for the full output. [error] (compile:run) org.scalajs.jsenv.ExternalJSEnv$NonZeroExitException: Node.js exited with code 1 [error] Total time: 1 s, completed Oct 13, 2016 3:06:00 PM
What basically happens here is that jQuery (which is automatically included because of
jsDependencies) cannot properly load, because there is no DOM available in Node.js.
To make the DOM available, add the following to your
This will use the
jsdom library to simulate a DOM in Node.js.
Note that you need to install it separately using
$ npm install jsdom
After reloading, you can invoke
> run [info] Running tutorial.webapp.TutorialApp [success] (...)
Just like other library dependencies,
jsDependencies += RuntimeDOM applies transitively: if you depend on a library that depends on the
DOM, then you depend on the DOM as well.
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 jQuery to verify that our page contains exactly one
<p> element which contains the text “Hello World”
after the UI has been set up.
To run this test, simply invoke the
> test [info] Compiling 1 Scala source to (...)/scalajs-tutorial/target/scala-2.11/test-classes... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.11/scala-js-tutorial-test-fastopt.js [info] ------------------Starting Suite tutorial.webapp.TutorialTest------------------ [info] tutorial.webapp.TutorialTest.HelloWorld Success [info] tutorial.webapp.TutorialTest Success [info] -----------------------------------Results----------------------------------- [info] tutorial.webapp.TutorialTest Success [info] HelloWorld Success [info] [info] Tests: 2 [info] Passed: 2 [info] Failed: 0 [success] (...)
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. For this we face another small issue: the button doesn’t
exist when testing, since the tests start with an empty DOM tree. To solve this, we create the button in the
method and remove it from the HTML:
This brings another unexpected advantage: We don’t need to give it an ID anymore but can directly use the jQuery object to install the on-click handler.
We now define the
ButtonClick test just below the
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.11/test-classes... [info] Fast optimizing (...)/scalajs-tutorial/target/scala-2.11/scala-js-tutorial-test-fastopt.js [info] ------------------Starting Suite tutorial.webapp.TutorialTest------------------ [info] tutorial.webapp.TutorialTest.HelloWorld Success [info] tutorial.webapp.TutorialTest.ButtonClick Success [info] tutorial.webapp.TutorialTest Success [info] -----------------------------------Results----------------------------------- [info] tutorial.webapp.TutorialTest Success [info] HelloWorld Success [info] ButtonClick Success [info] [info] Tests: 3 [info] Passed: 3 [info] Failed: 0 [success] (...)
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
> fullOptJS [info] Full optimizing (...)/scala-js-tutorial/target/scala-2.11/scala-js-tutorial-opt.js [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
fastOptJS 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
Automatically Creating a Launcher
feature of the sbt plugin. Since the sbt plugin is able to detect the
JSApp object of the application, there is no
need to repeat this in the HTML file. If you add the following setting to your
build.sbt, sbt will create a
scala-js-tutorial-launcher.js file which calls the main method:
In our HTML page, we can now include this file instead of the manual launcher:
If we rename our
JSApp object, we need not change our HTML at all anymore. Note that the launcher generation only
works if you have a single
JSApp object. If you happen to have multiple
JSApp objects but still would like to
generate a launcher, you can set the
JSApp object explicitly. Have a look at the Compiling, Running, Linking,
Optimizing guide for more details.
Putting it all Together
We can now create our final production HTML file
scalajs-tutorial.html which includes the fully optimized code:
This completes the Scala.js tutorial. Refer to our documentation page for deeper insights into various aspects of Scala.js.