summaryrefslogtreecommitdiff
path: root/book/src/main/scalatex/book/handson/ClientServer.scalatex
blob: 247d9acb677742a6527538a5b6c4847ea050f91e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@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{A Client-Server Setup}
  @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 (@lnk.misc.Play, @lnk.misc.Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. Our project will look like this:

  @hl.bash
    $ tree
    .
    ├── build.sbt
    ├── client
    │   ├── shared/main/scala/simple/FileData.scala
    │   └── src/main/scala/simple/Client.scala
    ├── project
    │   └── build.sbt
    └── server
        ├── shared -> ../client/shared
        └── src/main/scala/simple
                           ├── Page.scala
                           └── Server.scala

  @p
    First, let's do the wiring in @code{build.sbt}:

  @hl.ref("examples/crossBuilds/clientserver/build.sbt")

  @p
    We have two projects: @code{client} and @code{server}, one of which is a Scala.js project (indicated by the presence of @hl.scala{scalaJSSettings}). Both projects share a number of settings: the presence of the @code{shared/} folder, which shared code can live in (similar to what we saw in @sect.ref{Cross Publishing Libraries}) and the settings to add @lnk.github.Scalatags and @lnk.github.uPickle to the build. Note that those two dependencies use the triple @code{%%%} instead of the double @code{%%} to declare: this means that for each dependency, we will pull in the Scala-JVM or Scala.js version depending on whether it's being used in a Scala.js project.
  @p
    The @code{client} subproject is uneventful, with a dependency on the by-now-familiar @code{scalajs-dom} library. The @code{server} project, on the other hand, is interesting: it contains the dependencies required for us to set up out Spray server, and one additioanl thing: we add the output of @code{fastOptJS} from the client to the @code{resources} on the server. This will allow the @code{server} to serve the compiled-javascript from our @code{client} project from its resources.

  @p
    Next, let's kick off the Spray server in our Scala-JVM main method:

  @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala")

  @p
    This is a not-very-interesting @lnk("spray-routing", "http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve the main page on GET, and have other GET 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 list files various directories.

  @p
    The HTML template @hl.scala{Page.skeleton} is not shown above; I put it in a separate file for neatness:

  @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala")

  @p
    This is a typical @lnk.github.Scalatags HTML snippet. Note that since we're serving it directly from the server in Scala code, we do not need to leave a @code{.html} file somewhere on the filesystem! We can declare all HTML, including the skeleton of the page, in Scalatags. Otherwise it's the same as what we saw in earlier chapters: A simple HTML page which includes a script tag to run our Scala.js application.
  @p
    Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{<script>} tag above to kick off the client-side application.

  @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala")

  @p
    Again this is a simple Scala.js application, not unlike what we saw in earlier chapters. However, there is one difference: earlier, we made our Ajax calls to @code{api.openweathermap.org/...}. Here, we're making it to @code{/ajax}: the same server the page is served from!

  @p
    You may have noticed in both client and server, we have made reference to a mysterious @hl.scala{FileData} type which holds the name and size of each file. @hl.scala{FileData} is defined in the @code{shared/} folder, so it can be accessed from both Scala-JVM and Scala.js:

  @hl.ref("examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala")

  @p
    Now, if we go to the browser at @code{localhost:8080}, we should see our web-page!

@sect{Client-Server Reflections}
  @p
    By now you've already set up your first client-server application. However, it might not be immediately clear what we've done and why it's interesting! Here are some points to consider.

  @sect{Shared Templating}

    @p
      In both the client code and the server code, we made use of the same Scalatags HTML generation library. This is pretty neat: transferring rendering logic between client and server no longer means an annoying/messy rewrite! You can simply C&P the Scalatags snippet over. That means it's easy if you want to e.g. shift the logic from one side to the other in order to optimize for performance or time-to-load or other things.
    @p
      One thing to take note of is that we're actually using subtly @i{different} implementations of Scalatags on both sides: on the server, we're importing from @hl.scala{scalatags.Text}, while on the client we're using @hl.scala{scalatags.JsDom}. The @hl.scala{Text} backend renders directly to Strings, and is available on both Scala-JVM and Scala.js. The @hl.scala{JsDom} backend, on the other hand, renders to @lnk.dom.HTMLElement-s which only exist on Scala.js. Thus while on the client you can do things like attach event listeners to the rendered @lnk.dom.HTMLElement objects, or checking their runtime @code{.value}, on the server you can't. And that's exactly what you want!

  @sect{Shared Code}
    @p
      One thing that we skimmed over is the fact that we could easily define our @hl.scala{case class FileData(name: String, size: Long)} in the @code{shared/} folder, and have it instantly and consistently available on both client and server. This perhaps does not seem so amazing: we've already done many similar things earlier when we were building Cross-platform Modules. Nevertheless, in the context of web development, it is a relatively novel idea to be able to ad-hoc share bits of code between client and server.
    @p
      Sharing code is not limited to class definitions: @i{anything} can be shared. Objects, classes, interfaces/traits, functions and algorithms, constants: all of these are things that you will likely want to share at some point or another. Traditionally, people have simply re-implemented the same code twice in two languages, or have resorted to awkward Ajax calls to push the logic to the server. With Scala.js, you no longer need to do so: you can easily, create ad-hoc bits of code which are available on both platforms.

  @sect{Boilerplate-free Serialization}
    @p
      The Ajax/RPC layer is one of the more fragile parts of web applications. Often, you have your various Ajax endpoints written once on the server, have a set of routes written to connect those Ajax endpoints to URLs, and client code (traditionally Javascript) made calls to those URLs with "raw" data: basically whatever you wanted, packed in an ad-hoc mix of CSV and JSON and raw-strings.

    @p
      This has always been annoying boilerplate, and Scala.js removes it. With @lnk.github.uPickle, you can simply call @hl.scala{upickle.write(...)} and @hl.scala{upickle.read[T](...)} to convert your collections, primitives or case-classes to and from JSON. This means you do not need to constantly re-invent different ways of making Ajax calls: you can just fling the data right across the network from client to server and back again.

@hr

@p
  Hopefully this chapter has given you a glimpse of how a basic client-server application works using Scala.js. Although it is specific to a Spray server, there isn't any reason why you couldn't set up an equivalent thing for your Play, Scalatra or whichever other web framework that you're using.

@p
  It's probably worth taking a moment to play around with the existing client-server system you have set up. Ideas for improvement include:

@ul
  @li
    Try adding additional functionality to the client-server interface: what about making it show the contents of a file if you've entered its full path? This can be added as a separate Ajax call or as part of the existing one.
  @li
    How about setting up the build.sbt so it serves the fully-optimized Scala.js blob, @code{client-opt.js}? This is probably what you want before deployment into production, and the same technique as we used to serve the fast-optimized version applies here too.
  @li
    What if you wanted to use another server rather than Spray? How about trying to set up a Play or Scalatra server to serve our Scala.js application code?