環境
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
諸々の設定は下記のようです。
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))
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のソースコードを書き換えます。
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は次のよう
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
}
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 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として許されないかな…