LoginSignup
2
0

Scala.jsにLaminarを導入

Posted at

環境

OS: Windows 11
Java: OpenJDK 17.0.7
Scala : 3.3.0
Scala.js : 1.13.0
ScalaHTML: 0.1.1
ScalaCSS : 0.8.0-RC1
Akka: 2.7.0
Akka-HTTP: 10.5.2

諸々の設定は下記のようです。

build.sbt
val scala3Version = "3.3.0"
val AkkaVersion = "2.7.0"
val AkkaHttpVersion = "10.5.2"

ThisBuild / organization := "$organization$"
ThisBuild / scalaVersion := scala3Version
ThisBuild / version := "0.1.0-SNAPSHOT"

lazy val root = (project in file(".") withId "$name$")
  .aggregate(server, client, shared.jvm, shared.js)
  .settings(Compile / bgRun := (server / Compile / bgRun).evaluated)


lazy val server = project
  .dependsOn(shared.jvm)
  .dependsOn(client)
  .enablePlugins(JavaAppPackaging, SbtWeb)
  .settings(
    libraryDependencies ++= Seq(
      "org.scalameta" %% "munit" % "0.7.29" % Test,
      "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
      "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
      "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
    )
  )

lazy val client = project
  .dependsOn(shared.js)
  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)
  .settings(
    scalaJSUseMainModuleInitializer := true,
    libraryDependencies  ++= Seq( 
      "org.scala-js" % "scalajs-dom_sjs1_2.13" % "2.4.0",
      "com.raquo" %%% "laminar" % "15.0.0",
      "com.github.japgolly.scalacss" %% "core" % "0.8.0-RC1",
      "io.github.yrichika" %% "scalahtml" % "0.1.1"
    )
  )


lazy val shared = crossProject(JSPlatform, JVMPlatform)
  .crossType(CrossType.Pure)
  .in(file("shared"))
  .jsConfigure(_.enablePlugins(ScalaJSWeb))

plugin.sbt
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16")
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.2.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")

導入

"com.raquo" %%% "laminar" % "15.0.0"をsbtのlibraryDependenciesに追加します。

Scala.jsのソースコードを書き換えます。

ClientApplication.scala
package com.isageek.hakushimeita.xnote.client

import scala.scalajs.js.annotation._
import org.scalajs.dom
import com.raquo.laminar.api.L.{*, given}

val nameVar = Var(initial = "world")

val rootElement = div(
  label("Your name: "),
  input(
    onMountFocus,
    placeholder := "Enter your name here",
    onInput.mapToValue --> nameVar
  ),
  span(
    "Hello, ",
    child.text <-- nameVar.signal.map(_.toUpperCase)
  )
)
  // In most other examples, containerNode will be set to this behind the scenes
  val containerNode = dom.document.getElementById("app")


//  render(containerNode, rootElement)
//    render(containerNode, test)

@JSExportTopLevel("HelloWorld")
object HelloWorld {
  @JSExport
  def sayHello(): Unit = {
    println("Hello world!")
  }

  def main(args:Array[String]):Unit ={
    //containerNode.innerHTML = "Hello Scala.js"
    render(containerNode, rootElement)
    sayHello()
  }
}

ちなみに、ScalaHTML, ScalaCSS, Akka-HTTPは次のよう

HTML.scala
package com.isageek.hakushimeita.xnote.client

import scalahtml.Tags._

object MyHtml {
    val htmlHead = head() {
        title(){"ScalaHTML"}
    }

    val htmlBody = body(){
        main_(){
            div(){"Hello Scala HTML"} \\
            div(attributes("id" -> "app")) {""}
        } \\
        script(
            attributes("src"->"http://localhost:8080/main.js")
        ) { "" }

    }

    val html = html5() {
        htmlHead \\
        htmlBody
    }

    def render() = html 
}

Style.scala
package com.isageek.hakushimeita.xnote.client

import scala.language.postfixOps
import scalacss.DevDefaults._

object MyStyles extends StyleSheet.Standalone {
  import dsl._

  "div.std" - (
    margin(12 px, auto),
    textAlign.left,
    cursor.pointer,

    &.hover -
      cursor.zoomIn,

    media.not.handheld.landscape.maxWidth(640 px) -
      width(400 px),

    &("span") -
      color.red
  )

  "h1".firstChild -
    fontWeight.bold

  for (i <- 0 to 3)
    s".indent-$i" -
      paddingLeft(i * 2.ex)

  def myRender() = this.render.toString
}
Main.scala
/*
@main def hello: Unit =
  println("Hello world!")
  println(msg)

def msg = "I was compiled by Scala 3. :)"
*/

/*
 * Copyright (C) 2020-2023 Lightbend Inc. <https://www.lightbend.com>
 */

//package docs.http.scaladsl
package com.isageek.hakushimeita.xnote.server

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import scala.io.StdIn
import scala.io.Source

import com.isageek.hakushimeita.xnote.client.MyHtml
import com.isageek.hakushimeita.xnote.client.MyStyles

object HttpServerRoutingMinimal {

  def main(args: Array[String]): Unit = {

    implicit val system = ActorSystem(Behaviors.empty, "my-system")
    // needed for the future flatMap/onComplete in the end
    implicit val executionContext = system.executionContext

    val route =
      concat(
        path("") {
          get {
  //          complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
            complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, MyHtml.render()))
          }
        },
        path("Style.css"){
          get {
            complete(MyStyles.myRender())
          }
        },
        path("main.js"){
          get{
            val iter = Source.fromFile(
              "C:\\Users\\owken\\Documents\\GitHub\\xnote\\client\\target\\scala-3.3.0\\client-fastopt\\main.js"
            ).getLines()

            var content: String = ""
            for(line <- iter) {
              content += line
            }

            complete(content)
          }
        }
      )

    val bindingFuture = Http().newServerAt("localhost", 8080).bind(route)

    println(s"Server now online. Please navigate to http://localhost:8080/hello\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
  }
}

Javascriptファイルを生成

sbt fastLinkJS を実行します。
clientのプロジェクトのtarget/scala-3.3.0/client-fastopt
にmain.jsができます。
後はこれを、リクエストがあったときに、返してあげればOK.

個人的にてこずった点

生成されたmain.jsはHTMLの最後にscriptタグで読み込まないとうまくいきません。
最初、headタグ内でscriptタグで読みこんでいたのですが、上手くいかず、時間をかけてしまいました。

予定

あとは、LaminarとScala.jsでガリガリ書いて、フロントエンドを実装という感じ。
サーバサイドもWEB APIという形式で、処理を書いていきたい。
Slickあたりを使ってDBアクセスできるようになりたいですね。
Akka-HTTPについては、とりあえずは現状のものを使っていく予定です。
商用利用ではないからnon-productとして許されないかな…

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0