JavaScript, originally tailored for making user interactions possible on websites and adding browser effects like animations, has become one of the most important and widely used programming languages today.
As the world’s reliance on the web grew, however, JavaScript was forced to do more than basic browser effects, and so far, the language has done a fair job of meeting expectations. Undoubtedly, the language would have been better suited to meet these new demands had that level of responsibility been understood from the outset.
You might not question JavaScript’s competence until you start using it to build large, complex web applications. Dynamic typing, initially intended to be an advantage for JavaScript, has become one of its huge disadvantages in modern web development.
Leaving type inference to the interpreter during runtime can lead to a lot of bugs caused by incorrect assumptions. This introduces a lot of apprehension during development since devs may not be sure whether they’ve done everything right when it comes to variable declarations, value assignments, or proper use of data types.
This is where Scala.js shines. Its strong typing system in will prevent you from making these mistakes during compilation, and when you install the Scala plugin in VS Code or IntelliJ IDEA, your IDE is able to point them out before you even compile your code. Having this type of assistance during development makes it easy to write and refactor your code with less fear.
You can use Scala for frontend development, but you’re no longer forced to work with Node.js for the backend because you want to maintain shared code between your server and your frontend. Now you can write both your backend and frontend code in Scala and leverage all Scala’s advantages as a programming language, as well as JVM libraries and even npm tools for web development.
Let’s learn Scala.js by building a web-based countdown timer. Our final app will look like this:
To set up Scala.js, we’ll need to install sbt, our Scala build tool and compiler. To get sbt working, we’ll also need to install the Java Development Kit (JDK).
Next, we’ll confirm that we have Node.js installed by running the following code on our terminal:
node -v
This should return the Node.js version currently installed on your machine. Here’s the download link if you get an error instead.
After we’ve successfully set up sbt, we’ll go ahead and create a new directory for our app. Next we’ll create a folder named project
inside our application’s root directory, then create a file for our plugins inside the project
folder: ./project/plugins.sbt
.
Let’s paste the following line in our plugins.sbt
file. This will add the sbt
plugin to our project:
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.0")
It’s important to note that our sbt
compiler will be looking for a ./project/plugins.sbt
file. Also remember that project
does not refer to our root directly; rather, it’s a folder inside our root directory.
Next, in our root directory, we’ll create a build.sbt
file. This is like the package.json
file if you’re coming from a Node/JavaScript background. Here, we’ll house our app description and project dependencies:
name := "Scala.js test-app" scalaVersion := "2.13.1" enablePlugins(ScalaJSPlugin)
The name
and scalaVersion
we’ve defined here are important because these will determine the file name/path that sbt will use when compiling our Scala code to JavaScript. From what we’ve defined here, our JavaScript code will be generated in ./target/scala-2.13/scala-js-test-app-fastopt.js
. We’ll address the fastopt
suffix in a second.
We’ve also enabled the ScalaJSPlugin
that we initially added in our ./project/plugins.sbt
file.
Now that we know what our JavaScript file path will look like when compiled, we can go ahead and create our index.html
file and reference our JS file as a <script />
. Let’s create the index.html
file in our root directory and paste the following code:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Scala JS App</title> <link rel="stylesheet" href="./assets/index.css"> </head> <body> <div id="root"></div> <script type="text/javascript" src="./target/scala-2.13/scala-js-test-app-fastopt.js"></script> </body> </html>
Notice that we also linked a stylesheet
file in ./assets/index.css
. We’ll create this file after we’re done building our timer app with Scala.
Next, we’ll create an index.scala
file in the directory ./src/main/scala
. This directory is where sbt
will go to when looking for our Scala code to compile.
In our index.scala
file, let’s define a function, main
. This is where we want to house our code. In Scala, Values, functions and variables need to be wrapped in a class. Since we only need this class to be instantiated once, we’ll use the object
keyword to create a singleton instead:
object Main { def main(args: Array[String]): Unit = { println("Hey there!") } }
In the above block, we defined a Main
object and then a main
method inside it. The def
keyword is used to define functions in Scala and println()
will work like console.log()
when compiled to JavaScript.
Let’s run our sbt
build tool using the following code on our terminal:
$ sbt
If sbt
starts successfully, you should get a message similar to this:
[info] sbt server started at local:///.../.sbt/1.0/server/a1b737386b81d864d930/sock sbt:Scala.js test-app>
Next, to run our index.scala
file on our terminal, we’ll use the run
command inside the sbt
shell. Our compiler will look for a main
module with an args
parameter of type Array[String]
. The return Unit
type works just like the void
type in TypeScript. Other types you’ll commonly find in Scala are the Int
, String
, Boolean
, Long
, Float
, and Any
types.
sbt:Scala.js test-app> run
Our terminal should show us something similar to this:
[info] Running Main. Hit any key to interrupt. Hey there [success] Total time: 6 s, completed Jun 15, 2020, 1:01:28 PM sbt:Scala.js test-app>
Before we test out our code in the browser, let’s tell sbt
to initialize (or call) our main
function at the end of our compiled JavaScript code by adding the following line at the end of our ./build.sbt
file:
scalaJSUseMainModuleInitializer := true
Next, we’ll run fastOptJS
in our sbt
shell to recompile our JavaScript file. We’ll be using the fastOptJS
command to compile our code whenever we make changes to our Scala file:
sbt:Scala.js test-app> fastOptJS
We can also use ~fastOptJS
instead if we want to automatically recompile whenever we make changes — this works like nodemon
for Node.js. If our compilation is successful, we should get a response similar to this:
sbt:Scala.js test-app> ~fastOptJS [success] Total time: 1 s, completed Jun 15, 2020, 1:39:54 PM [info] 1. Monitoring source files for scalajs-app/fastOptJS... [info] Press <enter> to interrupt or '?' for more options.
Now, when we run our index.html
file on the browser and open the developer console, we should see our logged message: Hey there!
Note that fastOptJS
represents “fast optimize JS.” This is best to use during development because of the compilation speed. For our production environment, we’ll have to use the fully optimized version of our compiled JS.
If you guessed right, instead of running fastOptJS
in our sbt
shell, we’ll run fullOptJS
. This compiles to 5.42KB instead of the 54.25KB we get from fastOpt
. We’ll also need to change our script src
in the index.html
to ./target/scala-2.13/scala-js-test-app-opt.js
.
Using Scala.js on the browser does not make much sense without the Document Object Model (DOM) unless we want to tell our users to open the developer console whenever they visit our site. To set up Scala.js for browser DOM manipulation, let’s add the scalajs-dom
dependency to our ./build.sbt
file:
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "1.0.0"
After adding the dependency, our ./build.sbt
file should look like this:
name := "Scala.js test-app" scalaVersion := "2.13.1" enablePlugins(ScalaJSPlugin) scalaJSUseMainModuleInitializer := true libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "1.0.0"
Just like we would import modules in JavaScript, let’s import the document
object in our index.scala
file:
import org.scalajs.dom.{ document, window }
Destructuring in Scala.js is also similar to JavaScript. Now, we can access the browser document properties through the document
object, and the window
properties through the window
object.
Let’s test this by displaying our welcome message in the browser page instead of the developer console:
import org.scalajs.dom.{ document, window, raw } object Main { def main(args: Array[String]): Unit = { val title: raw.Element = document.createElement("h1") title.textContent = "Hey there! Welcome!" document.body.appendChild(title) } }
In the above block, we used val
to define a variable title
. In Scala, the val
keyword is used to define an immutable variable, while the var
keyword defines a mutable variable — just like the const
and let
keywords in JavaScript. We also added the raw
property to our dom
imports and used its Element
property as the type for title
.
Now when we compile our code and open ./index.html
in our browser, we should see a welcome header with the message, “Hey there! Welcome!”
There’s an even simpler way to work with the DOM in Scala. Let’s see how we can use template engines to write our HTML in Scala and even modularize our code. For this part of the tutorial, we’ll be working with the scalatags
template engine to build a countdown application.
We’ll also show how we can break our code into modules by writing the navbar and countdown sections as different packages in their own Scala files.
Let’s start by adding the scalatags
dependency to our ./build.sbt
file:
libraryDependencies += "com.lihaoyi" %%% "scalatags" % "0.9.1"
Pay attention the version we used here, 0.9.1
. Depending on when you follow this tutorial, this version might not be compatible with the latest version of Scala.js. In that case, you’ll need to either use a later version of scalatags
or the same version of Scala.js that we’ve used here.
Next, we’ll create the navbar package in the ./src/main/scala
folder. Let’s name the file nav.scala
. Inside nav.scala
, we’ll paste the following code:
package nav import scalatags.JsDom.all._ object nav { val default = div(cls := "nav", h1(cls := "title", "Intro to Scala JS") ) }
On line 1, we started by using the package
keyword to name our nav
module. We’ll need this when we want to import nav
in the index.scala
file. Also, we’ve imported scalatags
. The ._
suffix in the import statement imports the DOM properties inside scalatags.JsDom.all
as destructured variables.
In scalatags
, every tag is a function. Parent-children relationships are defined as function-arguments, and siblings are separated with a comma
inside the function arguments. We also declare attributes as function arguments. Here, we’ve used the cls
keyword to define the classes for our div
and h1
tags.
Since we’ve already named our module using the package
keyword on line 1, there’ll be no need to export the object. We can use it in any of our Scala files by importing nav
. Replace the existing code in the index.scala
file with the following to see how this is done:
import org.scalajs.dom.document import scalatags.JsDom.all._ import nav._ object Main { def main(args: Array[String]): Unit = { val content = div(cls:="container", nav.default, ) val root = document.getElementById("root") root.appendChild(content.render) } }
In the above code, we imported our nav
package on line 3, and then we used the default variable we created in the nav.scala
file as the content for our div
on line 9. On line 12, we’ve appended the content
variable to the div with ID "root"
in our index.html
file.
While we’re still here, let’s create our style file, index.css
, inside a ./assets
folder in our root directory. Next, paste the following code inside it:
* { margin: 0; } .container { font-family: Arial, Helvetica, sans-serif; text-align: center; margin: unset; } .nav { background: linear-gradient(90deg, #04adad 0%, #00bdff); color: #fff; padding: 1.5em; } .nav .title { font-size: 2em; } #timer { font-size: 7em; margin-top: 20%; }
Now, when we recompile our code and run the index.html
file in our browser, we should see something similar to this:
Next, we’ll work on our countdown timer module. Let’s create a new file, countdown.scala
, in the ./src/main/scala
folder. We’ll paste the following code inside our new file:
package countdown import org.scalajs.dom.{ document, window } object countdown { def timer(): Unit = { var time: Int = document.getElementById("timer").innerHTML.toInt if (time > 0) time -= 1 else { window.alert("times up!") time = 60 } document.getElementById("timer").innerHTML = time.toString; } }
As you can see from our code, there are a lot of similarities between JavaScript and Scala. On line 7, we declared the time
variable using the var
keyword. This references a div
with an ID of "timer"
, which we’ll be creating in a second. Since .innerHTML
returns a String
, we used the .toInt
method to convert the time value to an Integer
.
On lines 8–12, we used the if-else
conditionals, which are quite similar to those of JavaScript. We want to reduce the time value by 1
if it’s greater than 0
and display an alert box with message, “times up!” when it’s equal to 0
. Then we reset the time value to 60
.
Now that we’ve created our countdown
package, we’ll use the setInterval
method to run our timer
function every 1
second. Let’s use the countdown
package in our index.scala
file:
import org.scalajs.dom.document import scalatags.JsDom.all._ import nav._ import countdown._ object Main { def main(args: Array[String]): Unit = { val content = div(cls:="container", nav.default, div(id:="timer", 60) ) val root = document.getElementById("root") root.appendChild(content.render) } }
In the above code, we’ve added the import statement for countdown
using import countdown._
. With this, we can call the timer
function using countdown.timer()
.
Alternatively, we can import the timer function with import countdown.countdown.timer
and just call it directly as timer()
in our index.scala
file. We’ve also added the div
with ID "timer"
and have given it a default value of 60
.
Next we’ll import the setInterval
method from scala.scalajs.js.timers
and use it to call the countdown.timer()
method:
... import scala.scalajs.js.timers.setInterval ... root.appendChild(content.render) setInterval(1000) {countdown.timer}
Finally, our index.scala
file should look like this:
import org.scalajs.dom.document import scalatags.JsDom.all._ import scala.scalajs.js.timers.setInterval import nav._ import countdown._ object Main { def main(args: Array[String]): Unit = { val content = div(cls:="container", nav.default, div(id:="timer", 60) ) val root = document.getElementById("root") root.appendChild(content.render) setInterval(1000) {countdown.timer} } }
Now, when we recompile our code using fastOptJS
and run our index.html
file in the browser, we should see something similar to this:
In this article, we’ve introduced Scala.js by building a web-based countdown timer. We covered the basics of the Scala programming language, how to work with the DOM, browser APIs, working with template engines like scalatags
, and the different options for compiling our Scala code to JavaScript.
Scala.js also has support for frontend frameworks like React, Vue, and Angular. You’ll find the setup quite similar to what we’ve done in this article, and working with them shouldn’t be all that different from what you’re used to in JavaScript.
Compared to type-checking tools like TypeScript, Scala.js might be a little more difficult to set up and get used to if you’re coming from a JavaScript background, but this is because Scala is a whole different language. Once you get past the initial stage of setting up and learning a new programming language, you’ll discover that it has a lot more to offer than just type checking.
Lastly, here’s a link to the GitHub repo for our demo application.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
9 Replies to "Strongly typed frontend code with Scala.js"
Im sorry, but this is awful. scala.js capabilities are no where near modern frontend frameworks such as react, vue or angular.
If one have a large web application and would like some help with typing, would highly recommend using typescript.
Scala is a great language, not for frontend web development. Sorry dude 🙂
Hello Ebenezer! First off all, congratulations for the nice article.
If I may recommend an addition to it, in the Setup Scala.js section, I would reword to mention that you can install Scala platform in a single operation as described here: https://www.scala-lang.org/2020/06/29/one-click-install.html
Thanks, Oswaldo! Didn’t come across this when I was writing.
It’s a really nice addition!
Hi, Matan Rabi. Scala.js is no rival to front-end frameworks. In fact, it has support for React, Vue and Angular. I mentioned this in the article.
Great article! But I have to say, it bothers me so much watching these libraries try to turn JavaScript into something so rigid. Is it just an unwillingness to change when coming from a backend framework? Where does that leave all of the front end developers that strongly support the language as it was intended?
Reply to Ryan:
Very valid concern, Ryan. I don’t think this affects JavaScript so much or is trying to turn the language into something it’s not.
Rather, I think it’s a good thing to give web development more options and “break-in” pathways for non-traditional front-end developers. This way, instead of trying to make JavaScript do what it was not tailored for, we can easily turn to alternatives that meet those specific needs.
Respected Ebenezer sir,
I have read your block which is really helpful for beginners like me in Scala.js. But when I run this code on my sbt , it gives me errors like
TypeError: Cannot read property ‘appendChild’ of null
TypeError: Cannot read property ‘appendChild’ of null
I don’t understand where i was wrong. Please help
Thanks, Sanjay. Can you point me to the block you’re talking about?
:\pro\scala-js-timer-master>sbt
[info] welcome to sbt 1.3.13 (Oracle Corporation Java 14.0.2)
[info] loading settings for project scala-js-timer-master-build from metals.sbt,plugins.sbt …
[info] loading project definition from G:\pro\scala-js-timer-master\project
[info] loading settings for project scala-js-timer-master from build.sbt …
[info] set current project to Scala.js test-app (in build file:/G:/pro/scala-js-timer-master/)
[info] sbt server started at local:sbt-server-42dfb677228b318cbf0d
sbt:Scala.js test-app> run
[info] Compiling 3 Scala sources to G:\pro\scala-js-timer-master\target\scala-2.13\classes …
[warn] 1 deprecation (since 2.13.3); re-run with -deprecation for details
[warn] one warning found
[info] Fast optimizing G:\pro\scala-js-timer-master\target\scala-2.13\scala-js-test-app-fastopt.js
[info] Running main.Main. Hit any key to interrupt.
TypeError: Cannot read property ‘appendChild’ of null
TypeError: Cannot read property ‘appendChild’ of null
at $c_Lmain_Main$.main__AT__V (file:///G:/pro/scala-js-timer-master/target/scala-2.13/scala-js-test-app-fastopt.js:1088:10)
at $s_Lmain_Main__main__AT__V (file:///G:/pro/scala-js-timer-master/target/scala-2.13/scala-js-test-app-fastopt.js:1074:20)
at file:///G:/pro/scala-js-timer-master/target/scala-2.13/scala-js-test-app-fastopt.js:11498:1
at file:///G:/pro/scala-js-timer-master/target/scala-2.13/scala-js-test-app-fastopt.js:11499:4
at Script.runInContext (vm.js:130:18)
at Object.runInContext (vm.js:293:6)
at processJavaScript (G:\pro\scala-js-timer-master\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:241:10)
at HTMLScriptElementImpl._innerEval (G:\pro\scala-js-timer-master\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:176:5)
at onLoadExternalScript (G:\pro\scala-js-timer-master\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:98:12)
at onLoadWrapped (G:\pro\scala-js-timer-master\node_modules\jsdom\lib\jsdom\browser\resources\per-document-resource-loader.js:53:33)
[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: 13 s, completed Oct 17, 2020, 6:11:34 AM
sbt:Scala.js test-app> fastOptJS
[success] Total time: 0 s, completed Oct 17, 2020, 6:11:53 AM
When index.html browse , it works as we expect. But when I run this file , it gives me error.
Thanks for your replying Sir.