Skip to content

Commit 101b9a1

Browse files
committed
Intial commit
1 parent 92c9c38 commit 101b9a1

File tree

16 files changed

+381
-0
lines changed

16 files changed

+381
-0
lines changed

activator.properties

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name=scalajs-play-core
2+
title=ScalaJS with React and Play backend
3+
description=Base project using MacWire, ScalaJS (with React) and Play backend
4+
tags=scala,scalajs,scalajs-react,playframework,autowire
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package hu.demo.client
2+
3+
import scala.scalajs.js
4+
import scala.scalajs.js.annotation.JSExport
5+
6+
/**
7+
* Created by Janos on 12/9/2015.
8+
*/
9+
object SPA extends js.JSApp {
10+
@JSExport
11+
override def main(): Unit = {
12+
//TODO: Render component
13+
}
14+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package service
2+
3+
import java.nio.ByteBuffer
4+
5+
import boopickle.Default._
6+
import org.scalajs.dom
7+
8+
import scala.concurrent.Future
9+
import scala.scalajs.js.typedarray.{ArrayBuffer, TypedArrayBuffer}
10+
import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
11+
/**
12+
* Created by Janos on 12/9/2015.
13+
*/
14+
abstract class AbstractClient(name: String) extends autowire.Client[ByteBuffer, Pickler, Pickler] {
15+
override def doCall(req: Request): Future[ByteBuffer] = dom.ext.Ajax.post(
16+
url = s"/api/${name}/${req.path.mkString("/")}",
17+
data = Pickle.intoBytes(req.args),
18+
responseType = "arraybuffer",
19+
headers = Map("Content-Type" -> "application/octet-stream")
20+
).map(r => TypedArrayBuffer.wrap(r.response.asInstanceOf[ArrayBuffer]))
21+
22+
override def read[Result: Pickler](p: ByteBuffer) = Unpickle[Result].fromBytes(p)
23+
24+
override def write[Result: Pickler](r: Result) = Pickle.intoBytes(r)
25+
}

project/Build.scala

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import org.scalajs.sbtplugin.ScalaJSPlugin
2+
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
3+
import play.sbt.{PlayLayoutPlugin, PlayScala}
4+
import play.twirl.sbt.SbtTwirl
5+
import playscalajs.PlayScalaJS.autoImport._
6+
import playscalajs.ScalaJSPlay
7+
import sbt.Keys._
8+
import sbt._
9+
10+
object ScalaJSPlayCore extends Build {
11+
12+
lazy val root = project.in(file("."))
13+
.aggregate(
14+
sharedJVM,
15+
sharedJS,
16+
client,
17+
server
18+
)
19+
.settings(
20+
publish := {},
21+
publishLocal := {}
22+
)
23+
24+
lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared"))
25+
.settings(
26+
scalaVersion := versions.common.scala,
27+
libraryDependencies ++= dependencies.sharedDependencies.value
28+
)
29+
.jsConfigure(_ enablePlugins ScalaJSPlay)
30+
.jvmSettings()
31+
.jsSettings()
32+
33+
lazy val sharedJVM = shared.jvm
34+
lazy val sharedJS = shared.js
35+
36+
lazy val client = project.in(file("client"))
37+
.settings(Settings.clientSettings)
38+
.enablePlugins(ScalaJSPlugin, ScalaJSPlay)
39+
.dependsOn(sharedJS)
40+
41+
lazy val clients = Seq(client)
42+
43+
lazy val server = project.in(file("server"))
44+
.settings(Settings.serverSettings ++ Seq(
45+
scalaJSProjects := clients
46+
))
47+
.enablePlugins(SbtTwirl,PlayScala)
48+
.disablePlugins(PlayLayoutPlugin)
49+
.aggregate(client)
50+
.dependsOn(sharedJVM)
51+
52+
// loads the Play server project at sbt startup
53+
onLoad in Global := (Command.process("project server", _: State)) compose (onLoad in Global).value
54+
}

project/Settings.scala

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import com.typesafe.sbt.less.Import.LessKeys
2+
import com.typesafe.sbt.web.Import._
3+
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
4+
import play.sbt.routes.RoutesKeys._
5+
import playscalajs.PlayScalaJS.autoImport._
6+
import sbt.Keys._
7+
import sbt._
8+
9+
object Settings {
10+
val applicationName = "scalajs-play-demo"
11+
val applicationVersion = "1.0.0"
12+
lazy val elideOptions = settingKey[Seq[String]]("Set limit for elidable functions")
13+
14+
lazy val applicationSettings = Seq(
15+
name := applicationName,
16+
version := applicationVersion
17+
)
18+
19+
val sharedSettings = Seq(
20+
scalaVersion := versions.common.scala,
21+
scalacOptions ++= Seq(
22+
"-Xlint",
23+
"-unchecked",
24+
"-deprecation",
25+
"-feature"
26+
),
27+
resolvers ++= Seq(Resolver.jcenterRepo)
28+
)
29+
30+
lazy val clientSettings = applicationSettings ++ sharedSettings ++ Seq(
31+
libraryDependencies ++= dependencies.clientDependencies.value,
32+
elideOptions := Seq(),
33+
scalacOptions ++= elideOptions.value,
34+
jsDependencies ++= dependencies.jsDependencies.value,
35+
skip in packageJSDependencies := false,
36+
persistLauncher := true,
37+
persistLauncher in Test := false,
38+
testFrameworks += new TestFramework("utest.runner.Framework")
39+
)
40+
41+
lazy val serverSettings = applicationSettings ++ sharedSettings ++ Seq(
42+
libraryDependencies ++= dependencies.serverDependencies.value,
43+
commands += ReleaseCmd,
44+
pipelineStages := Seq(scalaJSProd),
45+
LessKeys.compress in Assets := true,
46+
includeFilter in(Assets, LessKeys.less) := "*.less",
47+
excludeFilter in(Assets, LessKeys.less) := "_*.less",
48+
routesGenerator := InjectedRoutesGenerator
49+
)
50+
51+
// Command for building a release
52+
lazy val ReleaseCmd = Command.command("release") {
53+
state => "set elideOptions in client := Seq(\"-Xelide-below\", \"WARNING\")" ::
54+
"client/clean" ::
55+
"client/test" ::
56+
"server/clean" ::
57+
"server/test" ::
58+
"server/dist" ::
59+
"set elideOptions in client := Seq()" ::
60+
state
61+
}
62+
}
63+
64+
object dependencies {
65+
val sharedDependencies = Def.setting(Seq(
66+
"com.lihaoyi" %%% "autowire" % versions.common.autowire,
67+
"me.chrons" %%% "boopickle" % versions.common.booPickle,
68+
"com.lihaoyi" %%% "scalarx" % versions.common.scalaRx,
69+
"com.lihaoyi" %%% "utest" % versions.common.uTest
70+
))
71+
72+
val serverDependencies = Def.setting(Seq(
73+
"com.softwaremill.macwire" %% "macros" % versions.server.macwire % "provided",
74+
"com.softwaremill.macwire" %% "util" % versions.server.macwire,
75+
"com.softwaremill.macwire" %% "proxy" % versions.server.macwire,
76+
77+
"com.mohiva" %% "play-silhouette" % versions.server.silhouette,
78+
"com.mohiva" %% "play-silhouette-testkit" % versions.server.silhouette % "test",
79+
80+
"com.vmunier" %% "play-scalajs-scripts" % versions.server.playScripts
81+
))
82+
83+
val clientDependencies = Def.setting(Seq(
84+
"com.github.japgolly.scalajs-react" %%% "core" % versions.client.scalajsReact,
85+
"com.github.japgolly.scalajs-react" %%% "extra" % versions.client.scalajsReact,
86+
"com.github.japgolly.scalajs-react" %%% "ext-scalaz71" % versions.client.scalajsReact,
87+
"com.github.japgolly.scalajs-react" %%% "ext-monocle" % versions.client.scalajsReact,
88+
"com.github.japgolly.scalacss" %%% "ext-react" % versions.client.scalaCSS,
89+
"org.scala-js" %%% "scalajs-dom" % versions.client.scalaDom
90+
))
91+
92+
val jsDependencies = Def.setting(Seq(
93+
"org.webjars.npm" % "react" % versions.js.react / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React",
94+
"org.webjars.npm" % "react-dom" % versions.js.react / "react-dom.js" commonJSName "ReactDOM" minified "react-dom.min.js" dependsOn "react-with-addons.js",
95+
"org.webjars" % "jquery" % versions.js.jQuery / "jquery.js" minified "jquery.min.js",
96+
RuntimeDOM % "test"
97+
))
98+
}
99+
100+
101+
object versions {
102+
103+
object common {
104+
val scala = "2.11.7"
105+
val scalaRx = "0.2.8"
106+
val autowire = "0.2.5"
107+
val booPickle = "1.1.0"
108+
val uTest = "0.3.1"
109+
}
110+
111+
object client {
112+
val scalaDom = "0.8.2"
113+
val scalajsReact = "0.10.1"
114+
val scalaCSS = "0.3.1"
115+
}
116+
117+
object js {
118+
val jQuery = "2.1.4"
119+
val react = "0.14.2"
120+
}
121+
122+
object server {
123+
val silhouette = "3.0.4"
124+
val macwire = "2.1.0"
125+
val playScripts = "0.3.0"
126+
}
127+
128+
}

project/build.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version = 0.13.8

project/plugins.sbt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
logLevel := Level.Warn
2+
3+
resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/"
4+
5+
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6")
6+
7+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5")
8+
9+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0")
10+
11+
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.3")
12+
13+
addSbtPlugin("com.vmunier" % "sbt-play-scalajs" % "0.2.8")

server/src/main/assets/main.less

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
play.application.loader = "GlobalApplicationLoader"
2+
//play.i18n.langs = [ "en" ]
3+
play.http.parser.maxMemoryBuffer = 512k
4+
play.http.parser.maxDiskBuffer = 1g
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import com.softwaremill.macwire._
2+
import controllers.{ApiController, Assets, DemoController}
3+
import play.api.ApplicationLoader.Context
4+
import play.api.routing.Router
5+
import play.api.routing.sird._
6+
import play.api.{Application, ApplicationLoader, BuiltInComponents, BuiltInComponentsFromContext}
7+
8+
/**
9+
* Global application context
10+
*/
11+
class GlobalApplicationLoader extends ApplicationLoader {
12+
override def load(context: Context): Application = (new BuiltInComponentFromContextWithPlayWorkaround(context) with ApplicationComponents).application
13+
}
14+
15+
abstract class BuiltInComponentFromContextWithPlayWorkaround(context: Context) extends BuiltInComponentsFromContext(context) {
16+
17+
import play.api.inject.{Injector, NewInstanceInjector, SimpleInjector}
18+
import play.api.libs.Files.DefaultTemporaryFileCreator
19+
20+
lazy val defaultTemporaryFileCreator = new DefaultTemporaryFileCreator(applicationLifecycle)
21+
22+
override lazy val injector: Injector = new SimpleInjector(NewInstanceInjector) + router + crypto + httpConfiguration + defaultTemporaryFileCreator
23+
}
24+
25+
trait ApplicationComponents extends BuiltInComponents with Controllers {
26+
lazy val assets: Assets = wire[Assets]
27+
lazy val router: Router = Router.from {
28+
case GET(p"/") => applicationController.index
29+
}
30+
}
31+
32+
trait Controllers extends BuiltInComponents {
33+
lazy val applicationController = wire[DemoController]
34+
lazy val apiController = wire[ApiController]
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package controllers
2+
3+
import demo.SampleApi
4+
import play.api.mvc.Action
5+
import service.SampleApiImpl
6+
7+
8+
/**
9+
* API Controller
10+
* Each request will create a SampleApiImpl instance.
11+
* It's necessary if you want to set the user in the constructor, otherwise you can use singleton
12+
*/
13+
class ApiController extends ServiceController {
14+
def userApi(path: String) = Action.async(parse.raw) { implicit request =>
15+
internalRoute(path, request) {
16+
Router.route[SampleApi](new SampleApiImpl())
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package controllers
2+
3+
import play.api.mvc._
4+
5+
import scala.concurrent.ExecutionContext.Implicits.global
6+
import scala.concurrent.Future
7+
8+
class DemoController extends Controller {
9+
10+
def index = Action.async(Future(Ok(views.html.index("SPA tutorial"))))
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package controllers
2+
3+
import java.nio.ByteBuffer
4+
5+
import boopickle.Default._
6+
import boopickle.Pickler
7+
import play.api.mvc.{Controller, RawBuffer, Request}
8+
9+
import scala.concurrent.ExecutionContext.Implicits.global
10+
/**
11+
* Autowire router
12+
*/
13+
object Router extends autowire.Server[ByteBuffer, Pickler, Pickler] {
14+
15+
override def read[R: Pickler](p: ByteBuffer) = Unpickle[R].fromBytes(p)
16+
17+
override def write[R: Pickler](r: R) = Pickle.intoBytes(r)
18+
}
19+
20+
trait ServiceController extends Controller {
21+
/**
22+
* Helper for internal routing
23+
* @param path
24+
* @param request
25+
* @param router
26+
* @return
27+
*/
28+
protected def internalRoute(path: String, request: Request[RawBuffer])(router: => Router.Router) = {
29+
val b = request.body.asBytes(parse.UNLIMITED).get
30+
router(
31+
autowire.Core.Request(path.split("/"), Unpickle[Map[String, ByteBuffer]].fromBytes(ByteBuffer.wrap(b)))
32+
).map(buffer => {
33+
val data = Array.ofDim[Byte](buffer.remaining())
34+
buffer.get(data)
35+
Ok(data)
36+
})
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package service
2+
3+
import demo.SampleApi
4+
5+
/**
6+
* Created by Janos on 12/9/2015.
7+
*/
8+
class SampleApiImpl extends SampleApi {
9+
override def echo(name: String): String = s"Echoed: ${name}"
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@(title: String)
2+
<!DOCTYPE html>
3+
4+
<html lang="en">
5+
<head>
6+
<meta charset="UTF-8">
7+
<title>@title</title>
8+
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
9+
<link rel="stylesheet" media="screen" href="/assets/stylesheets/main.min.css">
10+
<link rel="stylesheet" href="/assets/material/material.css">
11+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
12+
<link rel="shortcut icon" type="image/png" href="/assets/images/favicon.png">
13+
</head>
14+
<body>
15+
<div id="container">
16+
</div>
17+
@playscalajs.html.scripts("client")
18+
<script src="/assets/material/material.js"></script>
19+
</body>
20+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package demo
2+
3+
trait SampleApi {
4+
def echo(name: String): String
5+
}

0 commit comments

Comments
 (0)