From 52306f10ce3d1e462b171688de04b37174c0b74a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 9 Nov 2014 17:10:11 -0800 Subject: update --- book/src/main/scalatex/book/Index.scalatex | 2 +- .../scalatex/book/handson/ClientServer.scalatex | 142 ++++++++++++++++++++- examples/crossBuilds/clientserver/build.sbt | 25 ++++ .../js/shared/main/scala/simple/Simple.scala | 7 + .../js/shared/test/scala/simple/SimpleTest.scala | 13 ++ .../js/src/main/scala/simple/Platform.scala | 31 +++++ .../jvm/shared/main/scala/simple/Simple.scala | 7 + .../jvm/shared/test/scala/simple/SimpleTest.scala | 13 ++ .../jvm/src/main/scala/simple/Platform.scala | 59 +++++++++ .../crossBuilds/clientserver/project/build.sbt | 4 + examples/crossBuilds/clientserver2/build.sbt | 28 ++++ .../js/shared/main/scala/simple/Simple.scala | 7 + .../js/shared/test/scala/simple/SimpleTest.scala | 13 ++ .../js/src/main/scala/simple/Platform.scala | 34 +++++ .../jvm/shared/main/scala/simple/Simple.scala | 7 + .../jvm/shared/test/scala/simple/SimpleTest.scala | 13 ++ .../jvm/src/main/scala/simple/Platform.scala | 61 +++++++++ .../crossBuilds/clientserver2/project/build.sbt | 4 + 18 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 examples/crossBuilds/clientserver/build.sbt create mode 100644 examples/crossBuilds/clientserver/js/shared/main/scala/simple/Simple.scala create mode 100644 examples/crossBuilds/clientserver/js/shared/test/scala/simple/SimpleTest.scala create mode 100644 examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala create mode 100644 examples/crossBuilds/clientserver/jvm/shared/main/scala/simple/Simple.scala create mode 100644 examples/crossBuilds/clientserver/jvm/shared/test/scala/simple/SimpleTest.scala create mode 100644 examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala create mode 100644 examples/crossBuilds/clientserver/project/build.sbt create mode 100644 examples/crossBuilds/clientserver2/build.sbt create mode 100644 examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala create mode 100644 examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala create mode 100644 examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala create mode 100644 examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala create mode 100644 examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala create mode 100644 examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala create mode 100644 examples/crossBuilds/clientserver2/project/build.sbt diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex index 3cb8c2a..3f0c69f 100644 --- a/book/src/main/scalatex/book/Index.scalatex +++ b/book/src/main/scalatex/book/Index.scalatex @@ -39,7 +39,7 @@ @sect("Publishing Modules") @handson.PublishingModules.template - @sect("Integrating Client and Server") + @sect("Integrating Client-Server") @handson.ClientServer.template @sect("Scala.js in Depth", "Exploring Scala.js") diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex index 30404ce..b936482 100644 --- a/book/src/main/scalatex/book/handson/ClientServer.scalatex +++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex @@ -1 +1,141 @@ -TODO \ No newline at end of file + +@p + Historically, sharing code across client & server has been a holy-grail for web development. There are many things which have made it hard in the past: + +@ul + @li + Javascript on the client v.s. PHP/Perl/Python/Ruby/Java on the client + @li + Most back-ends make heavy use of C extensions, and front-end code was tightly coupled to the DOM. Even if you manage to port the main language +@p + There have been some attempts in recent years with more traction: Node.js, for example, has been very successful at running Javascript on the server, the Clojure/Clojurescript community has their own version of cross-built code, and there are a number of smaller, more esoteric platforms. + +@p + Scala.js lets you share code between client and server relatively straightforwardly. As we saw in the previous chapter, where we made a shared module. Let's work to turn that shared module into a working client-server application! + +@sect{Client-Server Configuration} + @p + Getting started with client-server integration, let's go with the simplest configuration possible: a Spray server and a Scala.js client. Most of the other web-frameworks (Play, Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. + + @p + First, let's do the wiring in @code{build.sbt} to add Spray as a dependency, + + @hl.ref("examples/crossBuilds/clientserver/build.sbt") + + @p + This does the standard boilerplate for including dependencies via SBT, and does on additional thing: we add the output of @code{fastOptJS} from the client to the @code{resources} on the server. + + @p + Next, let's kick off the Spray server in our Scala-JVM main method: + + @hl.ref("examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala") + + @p + This is a not-very-interesting @a("spray-routing", href:="http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve a HTML string, and have other URLs serve resources. This includes the @code{js-fastopt.js} file that is now in our resources because of our @code{build.sbt} config earlier! We also add a POST route to allow the client ask the server to format dates any time it wants via Ajax. + + @p + Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{ + + +
+

Hello from Scala-JVM!

+

$msg

+
+ + + """ + ) + } + } ~ + getFromResourceDirectory("") + } ~ + post{ + path("formatDates"){ + extract(_.request.entity.asString) { e => + complete { + Simple.formatTimes(e.split(",").map(_.toLong)) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver/project/build.sbt b/examples/crossBuilds/clientserver/project/build.sbt new file mode 100644 index 0000000..5bd83ce --- /dev/null +++ b/examples/crossBuilds/clientserver/project/build.sbt @@ -0,0 +1,4 @@ +/*project/build.sbt*/ +addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5") + +addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.4") \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/build.sbt b/examples/crossBuilds/clientserver2/build.sbt new file mode 100644 index 0000000..7e5bddb --- /dev/null +++ b/examples/crossBuilds/clientserver2/build.sbt @@ -0,0 +1,28 @@ +import utest.jsrunner.JsCrossBuild +import scala.scalajs.sbtplugin.ScalaJSPlugin._ +import ScalaJSKeys._ + +val cross = new JsCrossBuild( + libraryDependencies ++= Seq( + "com.scalatags" %%% "scalatags" % "0.4.2", + "com.lihaoyi" %%% "upickle" % "0.2.5" + ) +) + +lazy val js = cross.js.settings( + libraryDependencies ++= Seq( + "org.scala-lang.modules.scalajs" %%% "scalajs-dom" % "0.6" + ) +) + +lazy val jvm = cross.jvm.settings( + libraryDependencies ++= Seq( + "io.spray" %% "spray-can" % "1.3.2", + "io.spray" %% "spray-routing" % "1.3.2", + "com.typesafe.akka" %% "akka-actor" % "2.3.6" + ), + (resources in Compile) += { + (fastOptJS in (js, Compile)).value + (artifactPath in (js, Compile, fastOptJS)).value + } +) diff --git a/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala new file mode 100644 index 0000000..d3b0278 --- /dev/null +++ b/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala @@ -0,0 +1,7 @@ +/*shared/main/scala/simple/Simple.scala*/ +package simple +object Simple{ + def formatTimes(timestamps: Seq[Long]): String = { + timestamps.map(Platform.format).mkString("\n") + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala new file mode 100644 index 0000000..ec6b29f --- /dev/null +++ b/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala @@ -0,0 +1,13 @@ +/*js/shared/test/scala/simple/SimpleTest.scala*/ +/*jvm/shared/test/scala/simple/SimpleTest.scala*/ +package simple +import utest._ +object SimpleTest extends TestSuite{ + val tests = TestSuite{ + 'format{ + 'nil - assert(Simple.formatTimes(Nil) == "") + 'timeZero - assert( + Simple.formatTimes(Seq(0)) == "December 31, 1969 4:00:00 PM PST") + } + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala new file mode 100644 index 0000000..906329e --- /dev/null +++ b/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala @@ -0,0 +1,34 @@ +//js/src/main/scala/simple/Platform.scala +package simple + +import scala.scalajs.js +import org.scalajs.dom +import dom.extensions.Ajax +import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow +import scala.scalajs.js.annotation.JSExport +import scalatags.JsDom.all._ +@JSExport +object Platform extends{ + def format(ts: Long) = { + new js.Date(ts).toLocaleString() + } + @JSExport + def main(container: dom.HTMLDivElement) = { + container.appendChild( + div( + h1("Hello from Scala.js!"), + p(Simple.formatTimes(Seq(0, 1 << 30))) + ).render + ) + val payload = upickle.write(Seq(0L, 1L << 30)) + Ajax.post("/formatDates", payload).foreach{ xhr => + container.appendChild( + div( + h1("Hello from Ajax!"), + p(xhr.responseText) + ).render + ) + } + + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala new file mode 100644 index 0000000..d3b0278 --- /dev/null +++ b/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala @@ -0,0 +1,7 @@ +/*shared/main/scala/simple/Simple.scala*/ +package simple +object Simple{ + def formatTimes(timestamps: Seq[Long]): String = { + timestamps.map(Platform.format).mkString("\n") + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala new file mode 100644 index 0000000..ec6b29f --- /dev/null +++ b/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala @@ -0,0 +1,13 @@ +/*js/shared/test/scala/simple/SimpleTest.scala*/ +/*jvm/shared/test/scala/simple/SimpleTest.scala*/ +package simple +import utest._ +object SimpleTest extends TestSuite{ + val tests = TestSuite{ + 'format{ + 'nil - assert(Simple.formatTimes(Nil) == "") + 'timeZero - assert( + Simple.formatTimes(Seq(0)) == "December 31, 1969 4:00:00 PM PST") + } + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala new file mode 100644 index 0000000..f4ef986 --- /dev/null +++ b/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala @@ -0,0 +1,61 @@ +//jvm/src/main/scala/simple/Platform.scala +package simple + +import java.text.SimpleDateFormat +import akka.actor.ActorSystem +import spray.http.{HttpEntity, MediaTypes} +import spray.routing.SimpleRoutingApp +import scalatags.Text.all._ + +object Static{ + val msg = Simple.formatTimes(Seq(0, 1 << 30)) + val boot = + "Platform().main(document.getElementById('contents'))" + val page = html( + head( + script(src:="/js-fastopt.js") + ), + body( + onload:=boot, + div(id:="contents")( + h1("Hello from Scala-JVM!"), + p(msg) + ) + ) + ) +} +object Platform extends SimpleRoutingApp{ + def format(ts: Long) = { + val fmt = + "MMMM d, yyyy h:mm:ss aaa z" + new SimpleDateFormat(fmt).format( + new java.util.Date(ts) + ) + } + + def main(args: Array[String]): Unit = { + implicit val system = ActorSystem() + startServer("localhost", port = 8080){ + get{ + pathSingleSlash{ + complete{ + HttpEntity( + MediaTypes.`text/html`, + Static.page.render + ) + } + } ~ + getFromResourceDirectory("") + } ~ + post{ + path("formatDates"){ + extract(_.request.entity.asString) { e => + complete { + Simple.formatTimes(upickle.read[Seq[Long]](e)) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/project/build.sbt b/examples/crossBuilds/clientserver2/project/build.sbt new file mode 100644 index 0000000..5bd83ce --- /dev/null +++ b/examples/crossBuilds/clientserver2/project/build.sbt @@ -0,0 +1,4 @@ +/*project/build.sbt*/ +addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5") + +addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.4") \ No newline at end of file -- cgit v1.2.3