ScalaJS Getting Started

Manual Setup

Introduce Vite

Create Vite project
$ npm create vite@4.1.0
Create Vite project
npm create vite@4.1.0
 Project name: myapp
 Select a framework: > Vanilla
 Select a variant: > JavaScript
 
Scaffolding project in /.../myapp...
 
Done. Now run:
 
  cd myapp
  npm install
  npm run dev
vite.svg
.gitignore
counter.js
index.html
javascript.svg
main.js
package.json
style.css

Introduce Scala.js

In the subdirectory myapp/project, we add two files: build.properties and plugins.sbt

Create build.properties and plugins files
mkdir project
touch project/build.properties
touch project/plugins.sbt
project/build.properties
sbt.version=1.9.9
project/plugins.sbt
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
Create build.sbt
touch build.sbt
build.sbt
import org.scalajs.linker.interface.ModuleSplitStyle
 
lazy val myapp = project
  .in(file("."))
  .enablePlugins(ScalaJSPlugin)
  .settings(
    scalaVersion := "3.3.3",
    scalaJSUseMainModuleInitializer := true,
    scalaJSLinkerConfig ~= {
      _.withModuleKind(ModuleKind.ESModule)
        .withModuleSplitStyle(
          ModuleSplitStyle.SmallModulesFor(List("myapp"))
        )
    },
 
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "2.4.0",
    )
  )

Meet Scala.js Code

Create src/main/scala/myapp/App.scala file:

Create App.scala file
mkdir -p src/main/scala/myapp && touch $_/App.scala
App.scala
package myapp
 
import scala.scalajs.js
import scala.scalajs.js.annotation.*
 
import org.scalajs.dom
 
// import javascriptLogo from "/javascript.svg"
@js.native @JSImport("/javascript.svg", JSImport.Default)
val javascriptLogo: String = js.native
 
@main
def MyApp(): Unit =
  dom.document.querySelector("#app").innerHTML = s"""
    <div>
      <a href="https://vitejs.dev" target="_blank">
        <img src="/vite.svg" class="logo" alt="Vite logo" />
      </a>
      <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
        <img src="$javascriptLogo" class="logo vanilla" alt="JavaScript logo" />
      </a>
      <h1>Hello Scala.js!</h1>
      <div class="card">
        <button id="counter" type="button"></button>
      </div>
      <p class="read-the-docs">
        Click on the Vite logo to learn more
      </p>
    </div>
  """
 
  setupCounter(dom.document.getElementById("counter"))
end MyApp
 
def setupCounter(element: dom.Element): Unit =
  var counter = 0
 
  def setCounter(count: Int): Unit =
    counter = count
    element.innerHTML = s"count is $counter"
 
  element.addEventListener("click", e => setCounter(counter + 1))
  setCounter(0)
end setupCounter

Compile scala code to javascript and also watch for changes:

Compile scala code
$ sbt
[...]
sbg:myapp> ~fastLinkJS

Integrate Scala with Vite

npm install @scala-js/vite-plugin-scalajs@1.0.0
Create vite.config.js
touch vite.config.js
import { defineConfig } from "vite";
import scalaJSPlugin from "@scala-js/vite-plugin-scalajs";
 
export default defineConfig({
  plugins: [scalaJSPlugin()],
});

Update main.js file as follow:

main.js
import "./style.css";
import "scalajs:main.js";

Now, start the dev server:

Start the dev server
$ npm run dev

Visit: http://localhost:5173

Production Build

Production Build
npm run build
npm run preview

Final Project Structure

Your project structure should look like this:

build.properties
plugins.sbt
vite.svg
.gitignore
build.sbt
counter.js
index.html
javascript.svg
main.js
package.json
style.css
vite.config.js

Template Setup

You can run the following command to create a new project using the template:

Create a new project
$ sbt new scala-js/vite.g8

Source code on github: https://github.com/scala-js/vite.g8

Work with Laminar

Update the build.sbt file to add laminar dependency:

build.sbt
import org.scalajs.linker.interface.ModuleSplitStyle
 
lazy val myapp = project
  .in(file("."))
  .enablePlugins(ScalaJSPlugin)
  .settings(
    scalaVersion := "3.3.3",
    scalaJSUseMainModuleInitializer := true,
    scalaJSLinkerConfig ~= {
      _.withModuleKind(ModuleKind.ESModule)
        .withModuleSplitStyle(
          ModuleSplitStyle.SmallModulesFor(List("myapp"))
        )
    },
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "2.4.0",
      "com.raquo" %%% "laminar" % "16.0.0"
    )
  )

Then, update our App.scala file to use Laminar:

App.scala
package myapp
 
import org.scalajs.dom
 
import com.raquo.laminar.api.L.{*, given}
 
@main
def MyApp(): Unit =
  lazy val root = dom.document.querySelector("#app")
  renderOnDomContentLoaded(root, div("Hello world"))

Run concurrently $ sbt "~fastLinkJS" and $ npm run dev to see the changes.

Work with ScalaJS-React

Update the build.sbt file to add scalajs-react dependency:

build.sbt
import org.scalajs.linker.interface.ModuleSplitStyle
 
lazy val myapp = project
  .in(file("."))
  .enablePlugins(ScalaJSPlugin)
  .settings(
    scalaVersion := "3.3.3",
    scalaJSUseMainModuleInitializer := true,
    scalaJSLinkerConfig ~= {
      _.withModuleKind(ModuleKind.ESModule)
        .withModuleSplitStyle(
          ModuleSplitStyle.SmallModulesFor(List("myapp"))
        )
    },
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "2.4.0",
      "com.github.japgolly.scalajs-react" %%% "core" % "2.1.1"
    )
  )
npm install react@17.0.2 react-dom@17.0.2

Update our App.scala file to use ScalaJS-React:

App.scala
package myapp
 
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
 
@main
def MyApp(): Unit =
  val root = dom.document.querySelector("#app")
  val HelloWorld = ScalaComponent
    .builder[Unit]
    .renderStatic(<.div("Hello world"))
    .build
  HelloWorld().renderIntoDOM(root)

Run concurrently $ sbt "~fastLinkJS" and $ npm run dev to see the changes.

Add TailwindCSS

npm install tailwindcss postcss autoprefixer
Initialize Tailwindcss config
$ npx tailwindcss init -p

Update style.css file:

style.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Update tailwind.config.js file:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.scala"],
  theme: {
    extend: {},
  },
  plugins: [],
};

Update App.scala file:

Laminar

App.scala
package myapp
 
import org.scalajs.dom
 
import com.raquo.laminar.api.L.{*, given}
 
@main
def MyApp(): Unit =
  lazy val root = dom.document.querySelector("#app")
  renderOnDomContentLoaded(root, div("Hello world", cls := "text-red-600")) 

ScalaJS-React

App.scala
package myapp
 
import org.scalajs.dom
 
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
 
@main
def MyApp(): Unit =
  val root = dom.document.querySelector("#app")
  val HelloWorld = ScalaComponent
    .builder[Unit]
    .renderStatic(<.div("Hello world", ^.cls := "text-red-600")) 
    .build
  HelloWorld().renderIntoDOM(root)
 
Last Update: 08:02 - 26 April 2024

On this page