Scala.jsにLaminarを導入

# 環境
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として許されないかな…