Laminar 101: A “Hello, world” example (static)

I recently started a new Scala project that uses the Scala/Scala.js Laminar library for frontend development (i.e., as a JavaScript replacement).

Laminar is a library that’s built on top of Scala.js to let you build “reactive” web applications with observables, and in this tutorial I’ll show how to create a static “Hello, world” application from scratch. Once we get past these basics, I’ll show in Part 2 of this series how to create a reactive single-page application (SPA) with Laminar.

If you’re interested in the source code for this example, you can find it as a little Github project here:

So let’s get started building our static “Hello, world” app!

What you need

For this “Hello, world” tutorial you’ll need to create these files within an sbt project:

  • project/plugins.sbt
  • build.sbt
  • Laminar101.scala
  • index.html

Once those are in place you’ll compile the Laminar application with sbt and Scala.js, and then view it in your browser.

project/plugins.sbt

First, add this code to a project/plugins.sbt file:

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

This is just some boilderplate stuff you need to use Scala.js in an sbt project.

build.sbt

Next, create a build.sbt file with these contents:

// enable the Scala.js plugin that’s in 'project/plugins.sbt'
enablePlugins(ScalaJSPlugin)

// this states that this project has a Scala.js application with a `main` method
scalaJSUseMainModuleInitializer := true

// this specifies the name of the `main` method
Compile/mainClass := Some("alvin.Laminar101")

// these are basic settings that you’ll see in every sbt project.
// they specify the project name, version, and version of Scala you want
// to use.
lazy val root = project
    .in(file("."))
    .settings(
        name := "Laminar101",
        version := "0.1",
        scalaVersion := "2.13.8"
    )

// this specifies that Laminar is a dependency for this project.
// note that Laminar depends on Scala.js and Airstream, but we don’t need
// to specify those versions here, because they are dependencies of Laminar.
libraryDependencies ++= Seq(
    "com.raquo" %%% "laminar" % "0.14.2"
)

Laminar101.scala

Next, create this file named Laminar101.scala in the src/main/scala directory:

package alvin

// you’ll use these two import statements all the time.
// notice that the 'org.scalajs.dom' project is also used by Laminar.
import com.raquo.laminar.api.L._
import org.scalajs.dom

// the name 'Laminar101' matches the 'main' method setting in the
// build.sbt file (along with the package name 'alvin').
object Laminar101 {

    // the 'main' method
    def main(args: Array[String]): Unit = {

        // create a <div> that contains an <h1> tag. these methods come from
        // the 'com.raquo.laminar.api.L._' import.
        val rootElement: HtmlElement = div(
            h1("Hello, world")
        )

        // `#root` here must match the `id` in index.html
        val containerNode = dom.document.querySelector("#root")

        // this is how you render the rootElement in the browser
        render(containerNode, rootElement)
    }

}

This is basically the simplest-possible Laminar “Hello, world” example I can create. It doesn’t contain any dynamic parts; instead, it just displays an <h1> tag with the “Hello, world” text in it. The comments

An important thing to note is that the #root selector in this code must match the id in the index.html file that follows.

index.html

The final step is to create this index.html file in the src/main/resources directory:

<!DOCTYPE html>
<html>

<head>
    <title>Hello Laminar World</title>
    <link rel="stylesheet" href="./app.css">
</head>

<body>
    <div id="root"></div>

    <script type="text/javascript"
            src="../../../target/scala-2.13/laminar101-fastopt.js"></script>
</body>
</html>

When you look at the Girhub project you’ll see that there is also an app.css file in the src/main/resources directory, but it isn’t important for our purposes.

The important thing here is this code:

<script type="text/javascript"
        src="../../../target/scala-2.13/laminar101-fastopt.js"></script>

The laminar101-fastopt.js file will be created in the step, and it’s generated from our Laminar101.scala source code. (Technically, the Scala code is transpiled (or compiled) into JavaScript code.)

Run your Laminar application

With that setup we’re ready to run our project. Start sbt in root directory of the project, and once it starts up, run this ~fastOptJS command at the sbt prompt:

sbt:Laminar101> ~fastOptJS

This tells sbt to compile our Laminar101.scala code into the laminar101-fastopt.js JavaScript file.

When you run that command you should see sbt output that looks like this:

sbt:Laminar101> ~fastOptJS
[info] compiling 1 Scala source to /Users/al/Projects/Scala/Laminar/Tutorials/Laminar101/target/scala-2.13/classes ...
[info] Fast optimizing /Users/al/Projects/Scala/Laminar/Tutorials/Laminar101/target/scala-2.13/laminar101-fastopt
[success] Total time: 4 s, completed Jun 18, 2022, 12:25:30 PM
[info] 1. Monitoring source files for root/fastOptJS...
[info]    Press <enter> to interrupt or '?' for more options.

Finally, open a new terminal window and cd to the src/main/resources directory, and open the index.html file. On macOS you do that with the open command:

src/main/resources> open index.html

When you do that, you should see the “Hello, world” text rendered in your browser.

If you’re not using a Mac, you can open that file in other ways, including using a URL in your browser that will look something like this:

file:///Users/alvin/Projects/Scala//Laminar101/src/main/resources/index.html

Discussion

If you want to experiment with this code a little bit, try adding some other HTML tags in the Laminar101.scala file, like this:

div(
    h1("Hello, world"),
    p("It’s me!"),
    p("Yo!")
)

As you’ll see, this is very similar to writing HTML code.

This article shows everything you need to get started with Laminar. In the next tutorial in this series I show how to use Laminar’s “reactive” features to develop single-page web applications.