From 428976e0c29599e29623c63ba22a12a53a342b1e Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 12 Nov 2014 00:04:54 -0800 Subject: Lots of link insertion and standardization --- book/src/main/scala/book/Utils.scala | 51 +++++++++++++++++++++- book/src/main/scalatex/book/Index.scalatex | 2 +- book/src/main/scalatex/book/Intro.scalatex | 20 ++++----- .../main/scalatex/book/handson/CanvasApp.scalatex | 22 +++++----- .../scalatex/book/handson/ClientServer.scalatex | 10 ++--- .../scalatex/book/handson/CommandLine.scalatex | 8 ++-- .../scalatex/book/handson/GettingStarted.scalatex | 16 +++---- .../book/handson/PublishingModules.scalatex | 6 +-- .../main/scalatex/book/handson/WebPage.scalatex | 7 ++- .../book/indepth/CompilationPipeline.scalatex | 12 ++--- .../scalatex/book/indepth/DesignSpace.scalatex | 4 +- .../book/indepth/SemanticDifferences.scalatex | 20 ++++----- examples/demos/Controller.scala | 47 +++++++++++--------- .../src/main/scala/canvasapp/FlappyLine.scala | 1 - 14 files changed, 139 insertions(+), 87 deletions(-) diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala index ab6005b..07f462c 100644 --- a/book/src/main/scala/book/Utils.scala +++ b/book/src/main/scala/book/Utils.scala @@ -3,6 +3,8 @@ package book import acyclic.file import scala.collection.mutable import scalatags.Text.all._ +import scalatags.text.Builder + case class pureTable(header: Frag*){ def apply(content: Frag*) = { table(cls:="pure-table pure-table-horizontal half-table")( @@ -36,9 +38,9 @@ object sect{ val usedRefs = mutable.Set.empty[String] - def ref(s: String) = { + def ref(s: String, txt: String = "") = { usedRefs += s - a(s, href:=s"#${munge(s)}") + a(if (txt == "") s else txt, href:=s"#${munge(s)}") } def munge(name: String) = { @@ -80,6 +82,51 @@ object lnk{ usedLinks.add(url) a(name, href:=url) } + object dom{ + class MdnThing(name: String = toString) extends Frag{ + def render = lnk.apply(name, "https://developer.mozilla.org/en-US/docs/Web/API/" + name).render + def applyTo(t: Builder) = t.addChild(render) + } + class MdnEvent extends Frag { + def render = lnk.apply(toString, "https://developer.mozilla.org/en-US/docs/Web/Events/" + toString).render + def applyTo(t: Builder) = t.addChild(render) + } + object CanvasRenderingContext2D extends MdnThing() + object HTMLCanvasElement extends MdnThing() + object Element extends MdnThing() + object HTMLElement extends MdnThing() + object HTMLInputElement extends MdnThing() + object HTMLSpanElement extends MdnThing() + object XMLHttpRequest extends MdnThing() + object getElementById extends MdnThing("document.getElementById") + object setInterval extends MdnThing("WindowTimers.setInterval") + object mousedown extends MdnThing + object mouseup extends MdnThing + object onclick extends MdnThing + object onkeyup extends MdnThing + val JSONparse = lnk("Json.parse", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse") + } + object scala{ + def apply(s: String) = { + lnk(s, "http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#" + s) + } + } + object misc{ + val IntelliJ = lnk("IntelliJ", "http://blog.jetbrains.com/scala/") + val Eclipse = lnk("Eclipse", "http://scala-ide.org/") + val Rhino = lnk("Rhino", "https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino") + val Nodejs = lnk("Nodejs", "http://nodejs.org/") + val PhantomJS = lnk("PhantomJS", "http://phantomjs.org/") + val Play = lnk("Play", "https://www.playframework.com/") + val Scalatra = lnk("Scalatra", "http://www.scalatra.org/") + val ScalaTest = lnk("ScalaTest", "http://www.scalatest.org/") + val Scalate = lnk("Scalate", "https://github.com/scalate/scalate") + } + object github{ + val Scalatags = lnk("Scalatags", "https://github.com/lihaoyi/scalatags") + val uPickle= lnk("Scalatags", "https://github.com/lihaoyi/upickle") + val scalaPickling = lnk("scala-pickling", "https://github.com/scala/pickling") + } } object hl{ diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex index 7d342cd..59d474e 100644 --- a/book/src/main/scalatex/book/Index.scalatex +++ b/book/src/main/scalatex/book/Index.scalatex @@ -12,7 +12,7 @@ @lnk("Scala.js", "http://www.scala-js.org/") is a compiler that compiles Scala source code to equivalent Javascript code. That lets you write Scala code that you can run in a web browser, or other environments (Chrome plugins, Node.js, etc.) where Javascript is supported. @p - This book contains something for all levels of experience with Scala.js: absolute beginners can get started with the @sect.ref{Intro to Scala.js} and @sect.ref{Hands On} tutorial, people who have used it before can skip ahead to the later parts of the tutorial, @sect.ref{Making a Canvas App} or @sect.ref{Interactive Web Pages}. Intermediate users will find the chapters on @sect.ref{Cross Publishing Libraries} with Scala.js or @sect.ref{Integrating Client-Server}, and even experienced users will find the @sect.ref{In Depth} documention useful. + This book contains something for all levels of experience with Scala.js: absolute beginners can get started with the @sect.ref{Intro to Scala.js} and @sect.ref{Hands On} tutorial, people who have used it before can skip ahead to the later parts of the tutorial: @sect.ref{Making a Canvas App} or @sect.ref{Interactive Web Pages}. Intermediate users will find interest in the chapters on @sect.ref{Cross Publishing Libraries} with Scala.js or @sect.ref{Integrating Client-Server}, and even experienced users will find the @sect.ref{In Depth} documention useful. @p This book does not spend time on pontifying a philosophy or ideology behind Scala.js or Scala. It instead spends its words on hands-on tutorials and in-depth dives into parts of the Scala.js platform, to try and get you acquainted with Scala.js as soon as possible, so you can make your own decisions about its merits or qualities. diff --git a/book/src/main/scalatex/book/Intro.scalatex b/book/src/main/scalatex/book/Intro.scalatex index 1b95c32..33e0188 100644 --- a/book/src/main/scalatex/book/Intro.scalatex +++ b/book/src/main/scalatex/book/Intro.scalatex @@ -94,7 +94,7 @@ While not useful for small applications, where most of the logic is gluing together external APIs, this comes in very useful in large applications where a lot of the complexity and room-for-error is entirely internal. With larger apps, you can no longer blame browser vendors for confusing APIs that make your code terrible: these confusing APIs only lurk in the peripherals around a larger, complex application. One thing you learn working in large-ish web client-side code-bases is that the bulk of the confusion and complexity is no-one's fault but your own, as a team. @p - At this point, all of Google, Facebook, and Microsoft have all announced work on a typed variant of Javascript. These are not academic exercises: Dart/AtScript/Flow/Typescript are all problems that solve a real need, that these large companies have all faced once they've grown beyond a certain size. Clearly, Javascript isn't cutting it anymore, and the convenience and "native-ness" of the language is more than made up for in the constant barrage of self-inflicted problems. Scala.js takes this idea and runs with it! + At this point, all of Google, Facebook, and Microsoft have all announced work on a typed variant of Javascript. These are not academic exercises: @lnk("Dart", "https://www.dartlang.org/")/@lnk("AtScript", "https://docs.google.com/document/d/11YUzC-1d0V1-Q3V0fQ7KSit97HnZoKVygDxpWzEYW0U/edit")/@lnk("Flow", "https://lobste.rs/s/fp9ibi/flow_facebook_s_new_javascript_type_checker")/@lnk("Typescript", "http://www.typescriptlang.org/") are all problems that solve a real need, that these large companies have all faced once they've grown beyond a certain size. Clearly, Javascript isn't cutting it anymore, and the convenience and "native-ness" of the language is more than made up for in the constant barrage of self-inflicted problems. Scala.js takes this idea and runs with it! @sect{Sharing Code} @p @@ -116,7 +116,7 @@ Not having to resort to awkward Ajax-calls or pre-computation to avoid duplicating logic between the client and server @p - Shared code doesn't just mean sharing pre-made libraries between the client and server. You can easily publish your own libraries that can be used on both Scala-JVM and Scala.js. This means that as a library author, you can at once target two completely different platforms, and (with some work) take advantage of the intricacies of each platform to optimize your library for each one. Take Scalatags as an example: as the first client-server Scala.js-ScalaJVM shared libraries, it enjoys a roughly event split of downloads from people using it on both platforms: + Shared code doesn't just mean sharing pre-made libraries between the client and server. You can easily @sect.ref("Cross Publishing Libraries", "publish your own libraries") that can be used on both Scala-JVM and Scala.js. This means that as a library author, you can at once target two completely different platforms, and (with some work) take advantage of the intricacies of each platform to optimize your library for each one. Take Scalatags as an example: as the first client-server Scala.js-ScalaJVM shared libraries, it enjoys a roughly event split of downloads from people using it on both platforms: @img(src:="images/Scalatags Downloads.png", width:="100%") @@ -132,7 +132,7 @@ @p One common theme in all these platforms is that their main selling point is their tight, seamless client-server integration, to the point where you can just make method calls across the client-server boundary and the platform/language/compiler figures out what to do. @p - With Scala.js and Scala-JVM, such conveniences like making method calls across the client-server boundary is the boring reality. Not only are the calls transparent, they are also statically checked, so any mistake in the route name or the parameters it expects, or the result type it returns to you, will be caught by the compiler long before even manual testing. + With Scala.js and Scala-JVM, such conveniences like making method calls across the client-server boundary is the @sect.ref("Integrating Client-Server", "boring reality"). Not only are the calls transparent, they are also statically checked, so any mistake in the route name or the parameters it expects, or the result type it returns to you, will be caught by the compiler long before even manual testing. @hr @@ -149,27 +149,27 @@ @sect{Part 1: Hands On} @p - A whirlwind tour of the various things that Scala.js can be used for. We will cover: + A @sect.ref("Hands On", "whirlwind tour") of the various things that Scala.js can be used for. We will cover: @ul @li - Your first Scala.js application: setting up your development environment, cloning the example repository, debugging and finally publishing your first toy application + @sect.ref("Getting Started"): setting up your development environment, cloning the example repository, debugging and finally publishing your first toy application @li - An interactive web app using Scala.js: how you interact with the HTML DOM, how you utilize Ajax calls and other browser APIs that are common in Javascript-heavy applications + @sect.ref("Interactive Web Pages"): how you interact with the HTML DOM, how you utilize Ajax calls and other browser APIs that are common in Javascript-heavy applications @li - A Scala.js library: how to write a module that can be depended on by applications both your own and by others, and be used both with Scala.js and Scala-on-the-JVM + @sect.ref("Cross Publishing Libraries"): how to write a module that can be depended on by applications both your own and by others, and be used both with Scala.js and Scala-on-the-JVM @li - Client-Server integration: We will build a simple web application with a Scala server and Scala.js client. In the process, we'll explore how to share code between client and server, how to get compiler-checked/boilerplate-free Ajax calls between client and server, and many other long-standing holy-grails of web development + @sect.ref("Client-Server Integration"): We will build a simple web application with a Scala server and Scala.js client. In the process, we'll explore how to share code between client and server, how to get compiler-checked/boilerplate-free Ajax calls between client and server, and many other long-standing holy-grails of web development @p After going through this chapter and following along with the exercises, you should have a good sense of how Scala.js works and how it feels building things in Scala.js. You would not be an expert, but you'll know where to get started if you decide to try out Scala.js for your next project. @sect{Part 2: In Depth} @p - This section of the book will cover lots of content that does not fit in the earlier Hands-On portion of the book. Things that aren't immediately necessary to get something up and running, things that only advanced users would care about, things that you probably don't need to know but you'd like to know out of intellectual curiosity. + @sect.ref("In Depth", "This section of the book") will cover lots of content that does not fit in the earlier Hands-On portion of the book. Things that aren't immediately necessary to get something up and running, things that only advanced users would care about, things that you probably don't need to know but you'd like to know out of intellectual curiosity. @p - In general, this section of the book will go much deeper into Scala.js, much more than is necessary to get your first applications built. We will talk about the small number of semantic differences between Scala.js and Scala, details of the foreign-function-interface with Javascript, the various optimization levels and what they do. Nothing pressing or urgently needed, but all very interesting, and worth reading if you want to really understand Scala.js in depth. + In general, this section of the book will go much deeper into Scala.js, much more than is necessary to get your first applications built. We will talk about the small number of @sect.ref("Deviations from Scala-JVM", "Semantic Differences") between Scala.js and Scala, details of the foreign-function-interface used for @sect.ref("Javascript Interoperability"), @sect.ref("The Compilation Pipeline") with its various optimization levels and what they do. Nothing pressing or urgently needed, but all very interesting, and worth reading if you want to really understand Scala.js in depth. @hr diff --git a/book/src/main/scalatex/book/handson/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex index e352e0e..662f541 100644 --- a/book/src/main/scalatex/book/handson/CanvasApp.scalatex +++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex @@ -24,7 +24,7 @@ @hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*setup*/", end = "/*code*/") @p - As described earlier, this code uses the @hl.javascript{document.getElementById} function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page. + As described earlier, this code uses the @lnk.dom.getElementById} function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page. @p Next, let's set some event handlers on the canvas: @@ -38,10 +38,10 @@ @script("ScratchPad().main(document.getElementById('canvas2'))") @p - This code sets up the @code{mousedown} and @code{mouseup} events to keep track of whether or not the mouse has currently been clicked. It then draws black squares any time you move the mouse while the button is down. This lets you basically click-and-drag to draw pictures on the canvas. Try it out! + This code sets up the @lnk.dom.mousedown and @lnk.dom.mouseup events to keep track of whether or not the mouse has currently been clicked. It then draws black squares any time you move the mouse while the button is down. This lets you basically click-and-drag to draw pictures on the canvas. Try it out! @p - In general, you have access to all the DOM APIs through the @hl.scala{dom} package as well as through Javascript objects such as the @hl.scala{dom.HTMLCanvasElement}. Setting the @code{onmouseXXX} callbacks is just one way of interacting with the DOM. With Scala.js, you also get a very handy autocomplete in the editor, which you can use to browse the various other APIs that are available for use: + In general, you have access to all the DOM APIs through the @hl.scala{dom} package as well as through Javascript objects such as the @lnk.dom.HTMLCanvasElement. Setting the @code{onmouseXXX} callbacks is just one way of interacting with the DOM. With Scala.js, you also get a very handy autocomplete in the editor, which you can use to browse the various other APIs that are available for use: @img(src:="images/Dropdown.png", maxWidth:="100%") @@ -51,7 +51,7 @@ @sect{Making a Clock using setInterval} @p - You've already seen this in the previous example, but @hl.scala{dom.setInterval} can be used to schedule recurring, periodic events in your program. Common use cases include running the event loop for a game, making smooth animations, and other tasks of that sort which require some work to happen over a period of time. + You've already seen this in the previous example, but @lnk.dom.setInterval can be used to schedule recurring, periodic events in your program. Common use cases include running the @lnk("event loop for a game", "http://gameprogrammingpatterns.com/game-loop.html"), making smooth animations, and other tasks of that sort which require some work to happen over a period of time. @p Again, we need roughly the same boilerplate as just now to set up the canvas: @@ -73,7 +73,7 @@ @script("Clock().main(document.getElementById('canvas3'))") @p - As you can see, we're using more @lnk("Canvas APIs", "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D"), in this case dealing with rendering text on the canvas. Another thing we're using is the Javascript @lnk("Date", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date") class, in Scala.js under the fully name @hl.scala{scala.scalajs.js.Date}, here imported as @hl.scala{js.Date}. + As you can see, we're using more @lnk("Canvas APIs", "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D"), in this case dealing with rendering text on the canvas. Another thing we're using is the Javascript @lnk("Date", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date") class, in Scala.js under the full name @hl.scala{scala.scalajs.js.Date}, here imported as @hl.scala{js.Date}. @sect{Tying it together: Flappy Box} @@ -114,7 +114,7 @@ This is where we start defining things that are relevant to Flappy Box. There are roughly two groups of values here: immutable constants in the top group, and mutable variables in the bottom. The rough meaning of each variable is documented in the comments, and we'll see exactly how we use them later. @p - One notable thing is that we're using a @hl.scala{collection.mutable.Queue} to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js. + One notable thing is that we're using a @lnk("collection.mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html") to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js. @sect{Game Logic} @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def runLive", "def runDead") @@ -146,7 +146,7 @@ @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def run()") @p - And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @hl.scala{canvas.onclick} handler to make the player jump by tweaking his velocity, and we call @code{setInterval} to run the @hl.scala{run} function every 20 milliseconds. + And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @lnk.dom.onclick handler to make the player jump by tweaking his velocity, and we call @lnk.dom.setInterval to run the @hl.scala{run} function every 20 milliseconds. @p At almost 100 lines of code, this is quite a meaty example! Nonetheless, when all is said and done, you will find that the example actually works! Try it out! @@ -163,7 +163,7 @@ @p We've by now written a good chunk of Scala.js code, and perhaps debugged some mysterious errors, and tried some new things. One thing you've probably noticed is the efficiency of the process: you make a change in your editor, the browser reloads itself, and life goes on. There is a compile cycle, but after a few runs the compiler warms up and the compilation cycle drops to less than a second. @p - Apart from the compilation/reload speed, you've probably noticed the benefit of tooling around Scala.js. Unlike Javascript editors, your existin Scala IDEs like IntelliJ or Eclipse can give very useful help when you're working with Scala.js. Autocomplete, error-highlghting, jump-to-definition, and a myriad other modern conveniences that are missing when working in dynamically-typed languages are present when working in Scala.js. This makes the code much less mysterious: you're no longer trying to guess what methods a value has, or what a method returns: it's all laid out in front of you in plain sight. + Apart from the compilation/reload speed, you've probably noticed the benefit of tooling around Scala.js. Unlike Javascript editors, your existin Scala IDEs like @lnk.misc.IntelliJ or @lnk.misc.Eclipse can give very useful help when you're working with Scala.js. Autocomplete, error-highlghting, jump-to-definition, and a myriad other modern conveniences that are missing when working in dynamically-typed languages are present when working in Scala.js. This makes the code much less mysterious: you're no longer trying to guess what methods a value has, or what a method returns: it's all laid out in front of you in plain sight. @sect{Full Scala} @p @@ -178,9 +178,9 @@ @ul @li - @hl.scala{obstacles} is a Scala @hl.scala{mutable.Queue}, as we defined it earlier, and all the methods on it are Scala method calls + @hl.scala{obstacles} is a Scala @lnk("mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html"), as we defined it earlier, and all the methods on it are Scala method calls @li - @hl.scala{renderer} is a Javascript @hl.javascript{CanvasRenderingContext2D}, and all the methods on it are Javascript method calls directly on the Javascript object + @hl.scala{renderer} is a Javascript @lnk.dom.CanvasRenderingContext2D, and all the methods on it are Javascript method calls directly on the Javascript object @li @hl.scala{frame} is a Scala @hl.scala{Int}, and obeys Scala semantics, though it is implemented as a Javascript @hl.javascript{Number} under the hood. @li @@ -189,7 +189,7 @@ @p This reveals something pretty interesting about Scala.js: even though Scala at-first-glance is a very different language from Javascript, the interoperation with Javascript is so seamless that you can't even tell from the code which values/methods are defined in Scala and which values/methods come from Javascript! @p - These two classes of values/methods are treated very differently by the compiler when it comes to emitting the executable Javascript blob, but the compiler does not need extra syntax telling it which things belong to Scala and which to Javascript: the types are sufficient. @hl.scala{renderer}, for example is of type @hl.scala{dom.CanvasRenderContext2D} which is a subtype of @hl.scala{scalajs.js.Object}, indicating to the compiler that it needs special treatment. Primitives like @hl.scala{Double}s and @hl.scala{Int}s have similar treatment + These two classes of values/methods are treated very differently by the compiler when it comes to emitting the executable Javascript blob, but the compiler does not need extra syntax telling it which things belong to Scala and which to Javascript: the types are sufficient. @hl.scala{renderer}, for example is of type @lnk.dom.CanvasRenderingContext2D which is a subtype of @hl.scala{scalajs.js.Object}, indicating to the compiler that it needs special treatment. Primitives like @hl.scala{Double}s and @hl.scala{Int}s have similar treatment @p Overall, this seamless mix of Scala and Javascript values/methods/functions is a common theme in Scala.js applications, so you should expect to see more of it in later chapters of the book. diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex index 29b253d..247d9ac 100644 --- a/book/src/main/scalatex/book/handson/ClientServer.scalatex +++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex @@ -15,7 +15,7 @@ @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 (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. Our project will look like this: + 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 @@ -38,7 +38,7 @@ @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 Cross-platform Modules) and the settings to add Scalatags and 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. + 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. @@ -56,7 +56,7 @@ @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala") @p - This is a typical 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. + 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{